Unreleasedin progress on main
1 added2 fixed

Added

  • new block that renders one Group (HW) per entry picked from a card_object metaobject list, so the repeated card is designed once and bound to each entry's fields with the dynamic source picker instead of building every card by hand. Manually added Group blocks render first, ahead of the dynamic ones. Standalone alternative to Loop – Card Object (HW) for when the cards need to sit inside an existing section/Group/Grid rather than get their own carousel/grid section.

    Layout-transparent (display: contents) — it adds no wrapper of its own, so the row/column direction of the repeated cards comes from whatever Group/Grid/section the block is placed inside, not from a setting on this block.

Fixed

  • content children placed directly on the card now respect their own Width setting (Fit content / Custom / percent) instead of always filling the card, and stay inside the card instead of being cut off when Max width is wider than the card itself.

    The card was forcing a full width on every content child, overriding the child's own width. The card now only caps width, and that cap is clamped to the card so a Fit content, Custom or content-width child can no longer overflow a card narrower than Max width and get clipped.

  • removed the Uncaught (in promise) Error: Invalid element types in <OverflowList /> console error that appeared on every page load.

    The block's inline <overflow-list> shadow template contained only the list and default slot, but assets/overflow-list.js requires the more and overflow slots, the placeholder and the overflow container during initialisation, even when the list is disabled. The template now includes them, hidden by its existing inline style, so the menu looks and behaves exactly as before.

v4.1.0

13 added3 changed1 fixed1 removed

Added

  • new product popup that merchants compose with ordinary blocks, fetched for the clicked product rather than baked into every card. Independent of Theme settings → Quick add (the overlay on the card). Edit the contents on the quick-add product template; Theme settings → Quick Add Popup (HW) owns the shell (size, overlay, close button).

    Ships with a default layout: product image, title, variant picker, quantity, line price and add button. The section accepts every public theme block, so the tree is free to rearrange.

    The first open learns the section id and later opens are a cheap section fetch; Preload popup on hover on the trigger (on by default) means a second click is instant.

    Adding this section to an ordinary page from Add section will not open as a popup — it only works through the quick-add template and the trigger block.

  • new trigger for that popup. Drop it on Product card (HW), or over the image via Absolute (HW) inside Product card media (HW). The block's presence is the opt-in; the older overlay quick add is unchanged.

    Label / icon / icon-only, button presets, Show on card hover only, and a separate label for single-variant products (for example "Add" rather than "Choose").

  • new product-form block that holds quantity, line price and the add button in any order or nesting — they share the form rather than being wired parent-to-child. Do not nest it inside another buy-buttons form.

  • new styleable quantity selector for use inside Add area (width, button and icon size, colours). Behaviour and quantity rules are the same as the core selector.

  • new price that shows variant × quantity and updates as either changes. Can sit inside or outside the add area.

  • the picker to use in this popup — updates without a network request and works on collection, search and home pages, which the PDP picker does not.

  • new add-to-cart control using the same style surface as Button (HW), plus an icon-only mode. Place it inside Add area; Add to cart (HW) remains the PDP buy-buttons child.

  • new product-sourced image (same size tier as Image md), with Use selected variant image so the popup photo follows the picker. Falls back to the featured image when a variant has none; inside a product card it stays a static featured image because the card already owns its gallery.

  • now accepts Quick Add Button (HW) as a child, so the trigger can sit in the card flow beneath the media.

  • now accepts Quick Add Button (HW) and Absolute (HW), so the trigger can be placed at custom insets over the image.

  • new widest Group variant for the cases hw-group cannot express — top/bottom margin (with its own desktop override), per-side border switches over one shared thickness/colour/opacity, a custom border colour, and the shadow-blur / backdrop-blur pair already on hw-absolute. Everything else is identical to Group (HW), which stays the default: max is the step-up for one block, not a replacement, because every setting a merchant touches is stored in the template.

    Renders through the existing snippets/hw-group.liquid with a new is_max param, so hw-group and the -basic / -card / -link / -hover variants are byte-identical to before. New files: blocks/hw-group-max.liquid, snippets/hw-group-max-style.liquid.

    No schema changes to register it: @theme already admits every public block, so every host offering @theme picks it up automatically. The few older schemas that enumerate children by hand instead of using @theme do not offer it yet, which resolves when those gain @theme.

    Additive and backward compatible: no existing block, section, snippet or template changes behaviour.

  • the custom video play icon can now be rounded — a Play icon corner radius slider (0–60 px) with an optional Custom desktop corner radius override, so the icon can be squared on mobile and circular on desktop (or vice versa). Defaults to 0, so existing videos and card background videos are unchanged.

  • an Arrow border sides group — Top / Bottom / Left / Right switches over the existing thickness and the enabled/hover/disabled colour pickers, the same shape Group (HW, max) uses. Sits under the arrow styling group and only appears once Border thickness is above 0. These apply to the border drawn on each prev/next button itself, not to the navigation block's wrapper, which has no border of its own.

    The prev and next arrows can take different edges: a Set next arrow sides separately switch reveals a second Next arrow border sides group with its own four. Off — the default — the next arrow mirrors the four above, so the pair stays in step unless a merchant deliberately splits them. The case this exists for is a pair that meets in the middle: prev without its right edge, next without its left.

    All switches default to on, and an unset value reads as on, so every existing nav renders an unchanged all-round ring.

    The arrow border is an inset box-shadow, not a real CSS border, so there is no per-side form of it: new snippets/hw-carousel-nav-border-ring.liquid builds the shadow list for one arrow and is rendered once per arrow. It keeps emitting the single spread ring (inset 0 0 0 Npx) byte-for-byte while every edge is on, and only swaps to one directional inset shadow per enabled edge once an edge is switched off. A real border was rejected because it would eat into the icon box on a fixed-size control.

    Left and Right mean the edges the merchant sees, on both arrows. slideshow-arrow puts flip-x (scale: -1 1) on the previous control, which mirrors its box-shadow along with everything else, so the previous arrow's ring is built with left and right swapped. The custom-icon control is not flipped and takes them unswapped, which is why the emitted CSS addresses slideshow-control--previous and hw-carousel-navigation__control--previous in separate rules.

    Consequence of that technique, called out in the setting's help text: with an edge off, the remaining edges are drawn independently, so on heavily rounded controls the ring can thin out where two edges meet.

Changed

  • the pill settings are reorganised into one deliberate order — Content, Layout (small screens / desktop), Spacing, Pill style, Size override — and a single Style preset now decides which controls the merchant sees, instead of typography, button and colour settings all being live at once.

    Pill style → Button exposes an Active and an Inactive button preset + style, so the selected tab and the rest are two genuinely different buttons rather than one button dimmed by a hardcoded color-mix. The swap happens on activation in assets/hw-featured-collections.js.

    Pill style → Custom exposes a typography preset (full Horizon list, including Custom with the usual type fields), a border (None/Solid + thickness + opacity, the same shape Group (HW) uses), an active and an inactive background/text/border colour, and a corner radius. Colours swap in CSS off aria-selected, no JS involved.

    New snippets/util-color-with-opacity.liquid folds a percentage opacity into a picked colour and echoes nothing when the colour is blank, so a palette-token fallback survives — the channel math hw-group-max-style had inline.

    Inactive pill opacity is shared by both presets and sits at the end of the group.

    Size override is now its own group and applies to every pill style: minimum height (mobile/desktop) and pill padding on all four sides, each with a desktop value.

    Fixes two long-standing pill bugs: a chosen typography preset was out-specified by the block’s own label-font fallback and never applied, and a non-button pill inherited .text-block { width: 100% } and stretched to fill the row.

    Corner radius no longer applies to button-style pills — a button takes its radius from its button preset, as it does everywhere else in the theme.

    Breaking: button_preset, button_style_class, the button font override group (override_button_font, button_font_preset, btn_font_*) and the x/y padding pair (pill_padding_x_mobile and siblings) are removed. The section presets in hw-featured-collections are migrated; saved instances (template JSON, client stores) fall back to the new defaults for those controls and are re-saved from the theme editor.

    Migration: re-pick Active/Inactive button style on existing instances that used a non-default button_style_class; re-enter pill padding where the x/y override was on; a type_preset saved as a typography value (h3, paragraph_2, …) reads as Button and needs switching to Custom with the matching typography preset.

  • the footer-like wrapper that holds the carousel navigation and progress bar is now Group (HW, max) instead of Group (HW), across 19 sections — the carousel-control / carousel-controls group in hw-carousel-grid (3 presets), hw-col-carousel-grid, hw-col-carousel-grid-group, hw-collection-tabs-cards, hw-featured-blog, hw-featured-collections (2 presets) and hw-product-carousel-grid, and the static-footer slot in hw-collection, hw-fade-slider and the ten hw-loop* sections. The control row is exactly where per-side borders, a custom border colour and block margin are wanted, which is the case Group (HW) cannot express.

    Static footers are keyed on type *and* id, so each section's {% content_for 'block', type: 'hw-group', id: 'static-footer' %} moved to hw-group-max alongside its preset entry; hw-group-max was added to each section's accepted blocks list, with hw-group kept for headers and the groups nested inside cards.

    Safe as a swap because hw-group-max accepts an identical child-block list and its settings are a strict superset of Group (HW)'s, so preset values like content_direction and padding-block-end carry over untouched.

    Scope is deliberately the footer/control wrapper only: static-header and every group nested inside a card stay on Group (HW), since each setting a merchant touches on max is stored in the template.

    Migration: presets only. Sections already placed in a template keep the hw-group footer stored in their JSON and are not migrated here — re-add or re-save those instances from the theme editor to pick up the max footer.

  • locales/en.default.schema.json is deduplicated to buy back room against Shopify's 3,400-translation cap on a locale file — the file sat at 3,262 (96%), leaving 138 slots for everything still to be built. It is now 3,132 (92%), so headroom roughly doubles to 268.

    The cap that bites is the number of translations, not file size: at ~228 KB the file uses 15% of the 1.5 MB budget, and no value comes near the 1,000-character limit. Compacting the JSON or trimming the long info: help text would therefore have freed nothing that matters, so the merchant-facing documentation is left exactly as written. Only removing or merging keys returns slots.

    77 keys merged into 68, wherever two or more keys in the same namespace held a byte-identical value. The clearest case: five separate "Icon size" labels (settings.hw_quick_add_icon_size, settings.hw_header_utilities_icon_size, settings.hw_search_field_icon_size, settings.hw_qa_popup_close_icon_size, settings.hw_quantity_icon_size) collapse to one t:settings.icon_size. Likewise "Border color" ×4 → t:settings.border_color, "Corner radius" ×3 → t:settings.border_radius, "Button text color" ×3, "Icon color" ×3.

    Only settings:, content:, options:, names: and html_defaults: labels merge. The info: help texts are left untouched — each documents one specific setting, so none were duplicates in the first place, and their wording is what makes the theme editor usable.

    Canonicals were chosen to be generically named, not merely first-found, so a shared label is not parked on a feature-specific key where a later edit would silently change it everywhere. Nine were renamed to earn that: hw_quantity_icon_color → settings.icon_color, hw_quantity_icon_size → settings.icon_size, hw_menu_overlay_blur → settings.overlay_blur, hw_mobile_menu_drawer_text_color → settings.text_color, hw_qa_popup_close_size → settings.button_size, hw_search_icon_image → settings.search_icon_image, hw_showcase_title_metafield → settings.title_metafield, hw_text_price_product → settings.live_price_product, and content.hw_mobile_drawer_nav_item_chevron → content.chevron.

    187 `t:` references were remapped across 57 block, section and config files. No schema setting id, default, or behaviour changes — this is label wiring only, so nothing stored in a template JSON is affected and no merchant-visible string changes.

    hw_quick_add_offset_y ("Vertical offset") was deliberately not folded into the existing settings.vertical_offset: despite the identical English, that key means *vertical shadow offset* in the translated locales.

    Scope: only en.default.schema.json is touched. The 19 translated *.schema.json files are left alone, since they are managed by the Shopify admin language editor and upstream Horizon sync. They keep the now-unreferenced keys as harmless dead entries (each sits at 3,026, well inside the cap) and lack the new canonicals, so those few labels fall back to the English value for non-English admins until a sync pass.

    Migration: brand themes and client forks that reference any removed key from their own sections will render the raw t: string. The merged keys are listed above; the safe fix is to point at the canonical key rather than re-adding the old one, which would spend a slot again.

Fixed

  • Full width now fills the parent instead of shrinking to the label, and a button used outside Buy buttons is no longer stuck disabled — it reads availability from the selected variant.

Removed

  • 53 orphan keys in en.default.schema.json that no "t:…" reference in sections/, blocks/, snippets/, templates/, layout/ or config/ still used — leftovers from the removed hw-marquee section schema (marquee_layout, marquee_speed, repeat_items, split_static_justify and siblings), the retired hw_header_menu_2 / hw_header_actions_2 header pair, the content.button_preset_1/2/3 size labels, the hw_drawer_nav_* and hw_button_font_size* typography group, and the hw_collection_list / hw_collections_bento / hw_carousel_navigation block names. Every remaining key in the file is now referenced, and all 21,071 "t:…" references in the theme resolve.

    Note for tooling: schema keys in this theme use both _ and - (settings.show-placeholder, settings.media-sidebar-gap, settings.wrap-horizontal-row-large-screens). Any script that scans for "t:…" references must allow hyphens in the key pattern — a [A-Za-z0-9_.] character class silently misses those references and reports live keys as orphans.

v4.0.0

38 added6 changed19 fixed

Added

  • the FAQ item template's toggle icon can now be set to a custom SVG or image on the "Icon" setting, alongside the existing caret/plus choices; display size is controlled by each row's existing Toggle icon size setting.

  • the filter block gained a "Show All pill" toggle — off hides the catch-all pill so only one pill per category shows. Defaults to on, so every existing instance (product, collection and page templates) is unaffected.

  • Bundle Promo Tabs (HW): new metaobject-driven pill-toggle promo section for bundle products. sections/hw-bundle-promo-tabs.liquid renders a two-column image + content layout with a pill toggle (e.g. "Luxe Premium" / "Basic Premium") that cross-fades between an image panel and a content panel per entry - heading, colour swatches, sold count, USP chips, a real-photo/placeholder thumbnail stack, and a CTA linking straight to the tab's linked product.

    Content is driven entirely by a bundle_promo metaobject list setting (Bundle entries) - one entry per pill/tab, no bundle-building logic in the section itself.

    Layout: image always fills the section's full height (padding no longer carves a gap out of it, matching the split used by hw-feature-row.liquid - padding insets the content column only, and a separate, off-by-default margin setting owns real space between this section and its neighbours).

    Minimum height (mobile/desktop, free-text px/rem/% fields) lets the section be forced taller than its content, with the image stretching to match.

    Typography: heading and body (colour label + USPs) text each get a preset select (Paragraph / Paragraph 2-12 / Label / H1-H6, matching the scale used by blocks/_heading.liquid and blocks/text.liquid elsewhere in the theme), defaulting to Custom size so the section's own pixel sliders keep control until a preset is chosen.

    All colours (content background, pill states, colour swatches, sold badge, USP border, thumbnail placeholder, CTA) and remaining spacing are schema settings rather than hardcoded values, each defaulting to the section's original design so no visual change happens until a merchant overrides one.

    New metaobject type expected: bundle_promo (tab_label, hero_image, bundle_name, item_count, colors_label, sold_label, usps list, thumbnail_images list, product reference).

    Additive and backward compatible: this is a new section with no changes to any existing section, block, or snippet (only a new <script> tag added to hw-scripts.liquid to load its custom element).

  • Collection Hero (HW): new per-collection hero banner section, metaobject-driven, with editor-exposed layout/typography/color controls. sections/hw-collection-hero.liquid reads a collection.metafields.custom.hero reference to a Collection Hero metaobject entry (heading, subheading, image_desktop/image_mobile, button_label/button_link, optional badge text + style) and renders nothing when the current collection has no entry assigned - safe to include on any shared collection template.

    Content (heading/subheading/image/button/badge) is deliberately metaobject-driven rather than block-based: a shared collection template (e.g. a "cards" view used by every collection) has exactly one section instance, so per-collection content can't come from blocks, which are fixed to one instance per template file.

    Design (alignment, spacing, typography size, text color) is ordinary section settings instead, since it doesn't need to vary per collection: Layout (horizontal/vertical alignment, independently mobile and desktop, matching the hw-hero-card convention), Spacing (content max-width, gap, vertical padding, mobile/desktop pairs), Typography & color (heading size H1-H6, subheading size paragraph/paragraph_2-7, optional text color override), and Minimum height (mobile/desktop text inputs, in px) as an opt-in floor on top of the section's aspect-ratio frame - all default to values matching the section's original hardcoded styling, so an instance with nothing touched renders identically.

    Settings are wired through CSS custom properties on the rendered elements rather than static {% stylesheet %} rules, because {% stylesheet %} blocks are deduplicated globally per section *type*, not per instance - a static rule can't express "this instance's gap is 20px, that one's is 8px."

    Optional Trust bar: a full-bleed strip along the bottom edge with up to two icon+text items (checkbox to show/hide, icon picker, text field each) - section-level rather than metaobject-driven, since it's store-wide messaging ("14 day returns"), not per-collection content.

    Reuses assets/hw-hero.css's .hw-hero__* frame/media-fill/content-inner classes so it matches the visual rhythm of sections/hw-hero.liquid without pulling in that section's full block system.

  • Collection Tabs & Cards (HW): new tabbed collection browsing section with a right-pinned filter/sort drawer. sections/hw-collection-tabs-cards.liquid renders a row of collection tabs (each backed by a hw-collection-tab-card block sourcing its own collection), each showing a carousel of that collection's products via _hw-col-tab-carousel-content, with snippets/hw-tab-filter-drawer.liquid providing a slide-in-from-right drawer for Sort and per-tab Filter (color swatches, price range slider) gated behind explicit Apply/Clear rather than live-updating on every change.

    Each tab lazily fetches its own real filter list on tab select (not on drawer open), so the filter options shown always match the active tab's actual collection rather than a stale/shared list.

    Known gap, left unresolved on purpose: hw-collection-tab-card.liquid's swatch slot still targets ll-swatch, a block from the source client project that shows *sibling products* (metafield-driven: products_metafield + swatch_color_metafield) as colour swatches on a card - not the same thing as core's hw-swatches, which shows a single product's own variant options. Core has no equivalent for "metafield-sourced sibling-product swatches in a shared card/grid instance" yet (hw-product-siblings.liquid is the closest relative, but it's a PDP block with a static collection/product_list data source, which can't vary per-card in a grid the way a metafield can). See the PORT NOTE comment at the block's swatch content_for slot.

    Ported from thatworkagency/legend-london (feat/collections-cards-tab-page); templates/collection.cards.json is included as a starting preset with the source store's real collection handles stripped to "".

    Sort uses snippets/hw-tab-sorting.liquid (Clone & Own fork of core's sorting.liquid) rather than the core snippet directly, so the drawer's curated Sort list and Apply-gated (not live-updating) behavior actually work.

    Known gap: Apply-gated behavior currently only covers Sort - the color-swatch and price-range filters in this drawer still apply live on change rather than waiting for Apply.

  • Product Feature Row (HW): new section for a full-bleed image with an icon+label+description feature row, resolved by product/collection metaobject. sections/hw-feature-row.liquid renders a large product photo with a heading/subheading and a grid of feature tabs (icon, label, description), for the "Tailored Fit / All-Day Comfort / Wrinkle Resistant" style row near the top of a product page.

    Resolution order matches Compare (HW) and Loop – Card Object (HW)'s metaobject mode: a pdp_feature_row metafield override directly on the product, then whichever of the product's collections has that metafield set with the highest Priority, then the section's own Global (fallback) row setting, then a manual block-based fallback (enable with Enable block fallback, using _hw-feature-row-item blocks), then the section doesn't render at all.

    Strict all-or-nothing completeness check at every tier: a row only counts as resolved if heading, subheading and image are all present and it has at least one complete feature (icon, label and description all set); an incomplete row or item is treated as unresolved and falls through to the next tier rather than rendering with gaps. A "no data resolved" diagnostic only ever shows in the theme editor or on localhost, never on the live storefront.

    New metaobject types: pdp_feature_row (heading, subheading, image, features list of pdp_feature_item references, priority) and pdp_feature_item (icon, label, description).

    New restricted blocks: _hw-feature-row-item (one feature tab, dual-sourced from closest.metaobject.pdp_feature_item or block.settings in blocks-fallback mode) and _hw-feature-row-header (the heading/subheading, same dual-sourcing). Both delegate their rendering to the shared hw-group snippet, so they inherit its full layout/typography/color settings rather than a fixed, hand-rolled style.

    Content placement: heading/subheading and the feature grid can each be set to sit above the image (in normal document flow) or overlaid on it, independently per breakpoint. Desktop overlay is a fixed horizontal split (heading left, feature tabs right, not configurable); mobile overlay is a vertical stack (heading above the tabs). Both grow to fit their content rather than clipping or overlapping when copy runs long.

    Layout: always a CSS grid for the feature tabs (no separate grid/stack mode) — column count and gap are independently set per breakpoint, plus an optional max-width for the tab grid. All three vertical-spacing values (frame min-height, item gap, item max-width) are free-text px/rem fields rather than range sliders, for exact values a slider's step size can't reach.

    Width: the background image always spans the full viewport edge to edge; the section's Width setting ("Page"/"Full") controls the heading/feature content only, inset to the theme's normal page-width column or left to run edge to edge with the image.

    Padding vs. margin: section padding insets the heading/feature content within the image rather than adding a gap around the image itself; a new, separate Add section margin setting (mobile/desktop, off by default) adds real space between this section and its neighbours.

    Additive and backward compatible: this is a new section with no changes to any existing section, block, or snippet.

  • new block for Mobile menu drawer content (HW) that builds one mobile drawer nav row per block, instead of deriving every row from a linklist — the drawer's counterpart to Nav item (HW) on the desktop nav.

    A row with a Submenu renders as an accordion; a row without one renders as a plain link with the forward chevron. Markup mirrors hw-header-drawer-accordion-mode.liquid exactly, so the drawer's existing rules for rows, dividers, animation and colour apply with no new layout CSS.

    Per row: Badge (text, filled/outline, background, text colour, corner radius) and a Chevron override. Badge colours travel as inline custom properties on the block's own root — the reverse of Nav item (HW), where they live on the parent Menu (HW) block, because there the parent draws the badge and here the block draws its own. Nothing reads a parent block's settings, which is the lookup .agent/docs/blocks/loop/FINDINGS.md §2.2 documents as unreliable.

    Outline reuses the text colour for the ring rather than adding a fifth colour that would almost always be set to the same value, matching the Figma's red-on-red pill.

    Per row Divider: on/off, width and colour. It reuses the theme's .menu-drawer__list-item--divider class rather than drawing a private border, because that class also drives the childlist spacing rules in hw-header-drawer.liquid — a separate border would have missed them. Blank colour keeps --color-border. Turn it off on the last row so the list does not end on a line.

  • new Navigation rows and Nav typography groups.

    Nav rows come from — Menu (default, unchanged) or Nav item blocks. In block mode the linklist nav is skipped entirely rather than hidden with CSS: rendering both would duplicate every row for screen readers and double the drawer's height before the CSS loaded.

    Nav link size / color and Child link size / color are set once here rather than on each row block. Left at 0 / blank the property is not emitted at all, so each row keeps falling back to the drawer's own --menu-* pair and a block row renders identically to a linklist row out of the box.

  • new Padding group — top and bottom, on the nav block itself.

    .header-menu is height: 100% inside the header row and everything in it centres, so a symmetric pair tightens the nav's hover area and an uneven pair nudges the links up or down against the logo. That is the reason to reach for this rather than the row padding on the header section, which moves every column at once.

    box-sizing is border-box theme-wide, so the padding eats into the 100% rather than adding to it: the nav shrinks inside the row instead of growing the header.

  • new Show submenu link icon beside Show nav chevron, turning off the chevron on links inside dropdown and mega menu panels (.hw-dropdown-menu__link-icon-wrap).

    Those icons had no switch of their own: Show nav chevron drove both the top-level nav chevron and the panel link icons, so losing the panel icons meant losing the top-level chevron with them. The new setting hides the panel icons alone.

    The old coupling is left in place — Show nav chevron off still hides both — so existing headers render exactly as before. The new setting only ever subtracts.

  • new Overlay coverage beside the menu overlay colour/blur — Page below header (default, unchanged) or Header and page.

    .hw-menu-backdrop is pinned below the nav row, so with a transparent header the hero kept showing through it at full brightness while everything below was dimmed — a bright band across the header. Header and page starts the scrim at the top of <header-component> instead, so what shows through the header is covered too.

    top is host-relative because #header-component has contain: layout and is therefore the containing block for the fixed backdrop. The height cannot use the same origin — it is a viewport measurement, and the header is not always flush with the viewport top. It comes from the difference between the two anchor variables header-menu.js already publishes off one shared rect, so the two can never drift apart.

    z-index is unchanged at 0: above the underlays so what the header lets through is dimmed, below the row (raised to --layer-heightened while a submenu is open) so the logo, links, icons and the open panel all stay legible on top of the scrim.

  • new Selector style under the country/region settings — Flag + currency code (default, unchanged) or Currency symbol + square flag.

    The Figma desktop header reads ($) then an uncropped rectangular flag, with no ISO code. One switch rather than three toggles, because the three parts only make sense together: a symbol with no flag is an orphaned bracket, and the ISO code next to the symbol states the same fact twice.

    The symbol is emitted before the flag in the markup rather than reordered in CSS, so what a screen reader announces matches what is painted. The square flag rules carry both the modifier and the button class so they outrank the theme's clip-path: circle(50%) on specificity rather than on source order.

  • new header block that renders a full search pill — icon plus placeholder copy — instead of the icon-only search, for headers where search is a visible field rather than a hidden control.

    It is a trigger, not a second search form: it opens the same overlay the search icon opens, and reads the icon block's own Search style so the two controls cannot disagree about their target. No duplicate q input and no second search landmark for screen readers.

    Width, padding, corner radius, icon size, background (with blur), text and border colours are settings; text colour falls back to inherit so the field tracks a transparent header over a hero.

    Hidden on mobile by default — mobile search belongs in the menu drawer, and the header row has no column for a field at that width.

    Off by default on Header (HW); turn on Show search field and pick its column.

  • the country and language selector can now appear in the header row, and the one in the mobile menu drawer finally renders.

    The drawer's localization row had always been gated on section settings this section never had, so it was dead markup — adding Country/region, Language, Flag, font and size switches it on for both surfaces at once.

    Desktop placement is its own column setting, so the right bookend reads search field, then region, then account and cart.

    New Space between header items overrides the gap between the items in one bookend; left at 0 the header renders exactly as before.

  • a nav label can now carry a small pill badge, so a promotional top-level link reads as "Outlet · Up To 60% Off" in one item instead of needing a second link.

    Badge text and Filled / Outline style are per nav item; every colour, size, radius, padding and typography value is shared on the Menu (HW) block so all badges in a nav match.

    Works on plain links as well as dropdowns — a badge on a link with no chevron lays the item out horizontally the same way a submenu link does.

  • mega menu panels can be a card sized to their own content, not just full-bleed or page-width, with a corner radius to match.

    New Hug content width plus Corner radius. Hug shrinks the panel background itself and anchors it to the inline start of the nav row; Full and Page are unchanged, and still keep the background full width so no page shows through at the sides.

    New Promo width (px) sets the promo rail as a fixed length. Required with Hug — a percentage rail cannot resolve against a panel that is sized by its own content.

  • mega menu promo tiles take a description under the label, with its own shared typography group.

    New Promo 1 / 2 description on Nav item (HW) and a Promo descriptions typography group on Menu (HW), plus a Promo text gap between the two lines.

    Setting a Promo arrow background turns the arrow into a fixed, rounded, optionally blurred chip pinned to the bottom of the tile instead of a full-height divider cell; size, radius and blur are settings.

  • blocks added to this block now render inside the drawer, above the navigation list.

    The schema had always accepted children here but nothing output them, so anything a merchant added silently disappeared. Search fields, promo tiles and any @theme block now appear in the drawer with their own gap and padding settings.

  • the plain dropdown panel is now styleable from the editor — corner radius, padding, link gap, link inset, and resting/hover link colour.

    Every one of those was hard-coded in CSS, so a simple "Assistance"-style dropdown could not be matched to a design without a code change. Defaults are the exact former values, so an untouched header renders unchanged.

    The third level of a dropdown reads the same radius, padding and gap, so a nested panel matches its parent card. Compact dropdowns deliberately keep their own tighter 16px inset.

  • new Space between nav items overrides the theme gap between top-level nav items. Left at 0 the nav keeps the theme gap exactly as before.

  • nav label badges now show in the drawer, matched to a drawer row from the Nav item (HW) badge with the same label — one badge, typed once, in both navs.

    Appearance comes from the shared badge settings on Menu (HW), plus drawer-only style / background / text / border / border width / letter case overrides, because the same pill usually reads filled on a transparent header and outlined on the white drawer.

    A badge is matched by handleized label, so renaming a nav item's label without renaming the menu link stops the badge appearing — that coupling is deliberate.

  • new Chevron on links without a submenu gives a plain top-level link the same chevron as the rows that open something, so every row in the drawer reads as tappable. Off by default.

  • new Appearance group — background, text colour, corner radius and font size (mobile + desktop) per button.

    A button's look came only from the global style presets, so matching one button to a design meant restyling every button in the theme. The overrides are off by default and land in the block's existing 0,2,0 scope rule, so they beat .button and the size preset without !important; background also sets border-color so an outline preset does not keep its old ring around a new fill.

    First user is the drawer's Shop All button, now a black pill at the Figma's 14px.

  • new Country/language selector position under Drawer shell — Below menu links (default, unchanged) or Below footer.

    The Figma puts the flag row last in the drawer, under Sign In/Register, but the selector's markup is hard-coded between the nav and the footer blocks, so there was no way to get it there from the editor.

    The markup is captured once and echoed into whichever slot the setting names, rather than duplicated — the two positions render byte-identical markup apart from a modifier class.

    Below footer also re-aligns it: the default rule pins the block with an auto top margin and its own --padding-xl indent, neither of which lines up with footer content that pads by --drawer-padding. It hides with the footer when a sliding submenu is open, which the default position does not need to do.

  • new Text color under Drawer shell, alongside the background.

    Set on the drawer panel rather than per link, so nav items, footer links, the close button and every chevron follow it — the icons are stroke="currentColor" already. Blank keeps the theme foreground.

  • new Background color under Drawer shell, so the drawer can be dark or branded without overriding the theme's page background.

    Left blank the drawer keeps --color-background, so an untouched header renders exactly as before.

    It also covers the sliding submenu panels, which sit over the drawer at full height — colouring only the drawer would have flashed back to white the moment a submenu opened.

  • new block whose label and destination follow the customer — "Sign in / Register" to a visitor, "My account" once they are signed in.

    Nothing else in the theme could do this: a menu link's label is fixed, and Group (Conditional) keys off a dynamic source, not the customer. The mobile drawer's account row is the first user.

    Optional custom link overrides the destination; leave it blank and signed-out visitors go to the login page, signed-in customers to their account.

  • Search field (HW) can now be placed in the mobile menu drawer, not just the header row, so the drawer's search pill opens the same overlay instead of navigating to the search page.

    New Full width setting for that use: the header row's width lives in flex-basis, which reads as a *height* inside the drawer's column stack.

    Renamed from _hw-header-search-field to hw-search-field (dropping the _ is what makes it placeable). Not yet released, so nothing to migrate.

  • mega menu promo tiles can sit side by side as well as stacked, and each takes an editable badge.

    New Promo layout (Stacked / Side by side). Side by side is the Figma variation with two tiles; set Promo width to the pair's total, not one tile's.

    New Promo 1 / 2 badge text and per-tile badge background on Nav item (HW), with shared colour, size, case, radius, padding and blur on Menu (HW) so every badge in the nav matches.

    New Promo arrow background / colour (hover) turns the arrow chip white on hover to signal it is clickable. Blank keeps the resting colours, so an existing menu is unchanged.

  • Text (HW, background) and the mega menu promo badge take a Live price product; write {price} (or {compare_at_price} on the text block) and it renders that product's price in the visitor's market currency.

    An offer pill typed as "3 For £65" is wrong the moment someone shops another market. This keeps one badge correct everywhere.

  • Compare (HW): new "Content source" setting — Blocks or Metaobject (category table). Metaobject mode drives the whole table (heading, subheading, rows, columns) from a comparison_table metaobject entry, resolved per product so the same table swaps content by category with no per-product theme-editor work.

    Resolution order: a one-off comparison_table metafield override directly on the product, then whichever of the product's collections has that metafield set with the highest Priority, then the section's own Global fallback table setting. Collection-level is the intended primary path — tag a category's collection with its table once, every product in it picks it up automatically.

    New metaobject types: comparison_table (heading, subheading, rows, columns, priority), comparison_row (label, tooltip — same shape as the manual Compare Row block), comparison_column (title, logo, floating product image, badge text, highlight, background color, order-matched values list, CTA text/link).

    New dynamic blocks render the resolved entry in place of the section's normal manual/nested blocks: _hw-compare-row-dynamic, _hw-compare-column-dynamic (one per list entry), and _hw-compare-header-dynamic / _hw-compare-header-subheading-dynamic for the heading and subheading. The last two are full-settings text blocks (width, alignment, size preset, case override, color — same schema as Text (HW, basic)) rather than fixed styling, and are two separate block instances specifically so heading and subheading can each carry independent settings.

    The floating product image (metaobject mode only) fixes two real layout bugs found while building this: a circular shrink-to-fit/percentage-width dependency that silently rendered the image at roughly half its correct width, and a missing top-clearance case where the image could sit on top of whatever text precedes the table below the 990px side-layout breakpoint.

    New Bleed table to screen edge on mobile setting (mobile_edge_bleed, on by default, matches prior behavior): off keeps a table that already fits a phone screen inside the normal page margin instead of bleeding edge-to-edge, and lets the configured item gap apply on mobile instead of a small fixed default.

    Blocks mode (the section's original, fully manual behavior) is unchanged.

  • Product card quick add (HW): border, corner radius and an exact height per breakpoint. The button's outline (2px solid hsl(0 0% 0% / 0.15)), its 50px pill radius and its height all came from base Horizon CSS with nothing to change them from. All three are settings now, in Theme settings → Product cards → Quick add (HW) and mirrored on the Product card media (HW) block override.

    Height — an exact px value per breakpoint (1–100), mobile and desktop set separately. The icon cell scales with it, staying the height minus both border edges the way base Horizon does against --button-size-md, so the button stays balanced at any size. Full button stretches to its label, so the same number is a floor there rather than a fixed box.

    Border and corner radius — thickness 0–20px, colour with alpha (blank keeps the theme's hairline), radius 0–100px. The radius also lands on the inner .add-to-cart-text layer, whose own 50px corners would otherwise show through a less-rounded button. Both hide themselves for Full button, which spans the image edge to edge, and for Size picker, whose tile grid has a look of its own.

    Every one of these values travels as a CSS custom property rather than a variant class, so assets/hw-quick-add.css holds no merchant numbers and each var() fallback repeats the base-theme value — an unset setting renders exactly as base Horizon does.

  • Product card quick add (HW): icon and placement controls. The quick-add button's icon was hard-coded to the theme cart SVG and its position was fixed to the bottom-right corner. Both are now settings, in Theme settings → Product cards → Quick add (HW) and mirrored on the Product card media (HW) block override.

    Icon — Default (theme cart icon), Custom image, or Custom SVG, with its own size (8–48px). Picking a custom source and leaving it empty falls back to the default icon rather than rendering nothing. Icon position (left or right of the label) is available for every style except Icon only, which has no label.

    Placement — Any of the four corners plus bottom-centre, with horizontal and vertical offsets (0–48px) on top of the card's existing padding. Each edge reads the card padding on its own side: base Horizon defines only the bottom-right pair, so the top and left insets are built from the same parts rather than borrowing the opposite edge's value. The corner-radius compensation that pushes a pill clear of a rounded corner is dropped for bottom-centre, which sits nowhere near one, and the horizontal offset rides on the centring translate there so it still moves the control. Full button spans the image width, so it takes only the top/bottom half of the value; Size picker is a bottom strip and ignores placement entirely — those controls hide themselves for it.

    Both quick-add buttons ("Add" and "Choose") render one captured icon, so they cannot drift apart.

  • Div (HW): new block that renders a plain coloured box — no text, no nested blocks — for small square/dot accents and long horizontal or vertical bars.

    Width and height are set independently for mobile and desktop, each with its own unit: 0–100% or 0–100px, in steps of 1. 100% covers the fill-the-parent case, so there is no separate fill option. A percentage *height* only resolves once the parent has a height of its own (e.g. Group (HW) with a set height) — use px when the parent grows with its content.

    Background is a colour-or-gradient picker rather than a flat colour, so a bar can carry a gradient; left empty it falls back to the palette's primary colour instead of a hard-coded hex. No overlay and no text colour — there is nothing inside the block to tint.

    Corner radius takes px or percent, so 50% gives a pill or a circle at any size. Optional border (0–20px + colour), opacity, and −180°/180° rotation round out the shape controls.

    One Alignment setting maps to align-self/justify-self, which reads as left/centre/right inside a vertical Group and top/middle/bottom inside a horizontal one — the axis belongs to the parent, so it is one control rather than two.

    Available anywhere @theme blocks are allowed. Use Inset (HW) for spacing around it — Div (HW) deliberately ships no padding or margin settings.

  • Price (HW default): new price block that always follows Theme settings → Prices, with no per-instance typography or colour controls — use it when every price should stay on the store default, or when saved layout JSON is getting large.

    Same sale-first, “From”, tax and installments toggles as Price (HW); the two blocks render identically until you override styling on Price (HW).

    Available anywhere @theme blocks are allowed, and listed explicitly on Product card (HW) and the product media overlay blocks.

  • new split hero section — one or more hero columns side by side, each with its own background image, copy stack and frame height.

    Composed from existing blocks rather than new markup: an Image (HW) child acts as the card background and a Group (HW, basic) child carries the heading/paragraph/CTA as Text (HW, basic) + Button (HW). Gap, section width and palette colours are section settings.

    Columns are derived from the number of cards rather than set by hand: desktop always fits every card into one row of equal columns, so three cards means three thirds and five means five fifths, and every card in a row stretches to the same height.

    Two cards per row on mobile (off by default) puts small screens two across instead of stacked; with an odd number of cards the last one spans the full width. Off keeps the stacked layout, each card at its own height.

    Section width honours the theme's page width: Page constrains the row to the same centre column as any other page-width section, Full runs it edge to edge.

    Section preset ships two cards; templates/index.json now leads with a configured two-card instance matching the Legend London hero design — two full-bleed cards with centred copy, a per-column utility bar on desktop, and a single shared utility bar under the stack on mobile.

  • the column block for Hero Cards (HW) — sets card height per breakpoint, content max width, content alignment, an optional scrim, corner radius and a whole-card link with its own Link label for screen readers (falling back to the URL's last path segment, then to the theme's generic link label).

    Content placement chooses whether the copy floats over the background image or sits below it in normal flow. Editor block order decides which comes first. Either way the background image runs the full width of the column; the content max width only caps the copy.

    Content alignment is set per breakpoint: Layout (small screens) and Layout (desktop) each carry their own horizontal (left / center / right) and vertical (top / center / bottom) position, so a card can sit bottom-left on mobile and centred on desktop. Desktop falls back to the mobile value when left alone.

    Accepts Image (HW), the Group (HW) family, the Text (HW) family, Button (HW), Inset (HW), Flex spacer (HW) and Group Overlay (HW).

  • a strip that pins to the top or bottom edge of whatever contains it — nested in a Hero Card it covers that one column, added straight to the section it spans every column. Blur, background colour and opacity, strip width, padding and horizontal alignment are all settings.

    Options to distribute items evenly and show dividers between them, for utility bars like "14 Day Returns | Fast Priority Shipping".

    Accepts Text (HW), Text (HW, custom), Icon (HW) and Group (HW, basic) — the group variant is there so an icon and its label can travel as one item.

Changed

  • Icon with text (HW): the label field is rich text instead of a single line, so you can bold, italicize, or link parts of the label in the editor.

  • Menu (HW): the mega menu promo rail sizes itself to the number of promo tiles. Promo width (px) is gone.

    That setting was one shared number on Menu (HW) while the promo images are per Nav item (HW), so a nav item with one promo got the two-tile width (530px) and rendered half an empty rail. There was no value that could be right for both.

    The rail is now 265px for one tile and 530px for two side-by-side tiles. Two *stacked* tiles stay 265px, because stacked tiles sit on top of each other and the pair is still only one tile wide — which is why this is a CSS rule combining a per-nav-item count attribute with the parent's layout attribute, rather than a single length computed in Liquid: the count and the layout live on different blocks.

    The percentage Promo width setting remains only as the CSS fallback. It no longer applies when a rail renders, since a rail only exists when there is at least one tile. Stored dropdown-grid-promo-width-px values are ignored.

  • Quick add (HW) size is a px range instead of three fixed options. hw_quick_add_size (Compact / Standard / Large) and the data-hw-quick-add-size CSS variants it drove are gone, replaced by hw_quick_add_height_mobile and hw_quick_add_height_desktop (and quick_add_height_mobile / quick_add_height_desktop on the block override). Standard was 36px and that is the new default, so a store on Standard is unchanged; one that had picked Compact or Large needs to enter a height once.

  • Product card quick add (HW): one place decides, instead of three. The gate lived in blocks/hw-product-card-gallery.liquid, the visibility variables in snippets/hw-product-card.liquid, and the appearance in snippets/hw-quick-add-wrapper.liquid, with the three conditions disagreeing. Quick add is now resolved entirely in hw-quick-add-wrapper.liquid:

    Theme settings own whether quick add exists (quick_add, mobile_quick_add); the block override owns only how it looks, and applies as a complete set rather than a mix of block and theme values. The editor docs previously claimed a block could switch quick add back on when the theme had it off — it never could, and now says so.

    The gallery block renders the wrapper unconditionally and lets it decide, so there is no second gate to fall out of sync.

    Sold-out products no longer render quick-add markup or a pre-rendered modal template. The card already hid it; now it isn't emitted.

    Schema copy on the gallery block moved from hard-coded English to locale keys, matching the rest of the theme.

  • Prices (theme settings): Price typography presets (three named pixel scales) is replaced by Default price styling — one typography preset and one colour for the current price, compare-at price, save label, and “From” prefix. Prices now track the theme’s typography scale, including fluid sizing, instead of independent px values. Leaving a preset on Default still means inherit from the surrounding text.

    Save-label background, text colour and corner radius stay in the Savings label group and feed the same store-wide defaults.

    Migration: the eighteen Price 1 / Price 2 / Price 3 size ranges are removed, so stored pixel sizes are dropped on sync. Re-apply the look under Theme settings → Prices → Default price styling by picking matching paragraph or heading presets. Price color and Compare price color keep their existing ids.

  • Price (HW): each of the four price parts can override the theme default independently (preset + colour), plus save-label background/radius and a gap between elements. Colour and preset controls start blank, so an untouched block looks like the store default. Save-label radius and gap sit behind Override checkboxes — a range slider always has a value, and without the gate it would pin 50px / 8px on every instance. The old Price typography preset (None / Price 1 / 2 / 3) and the wrapper Preset (which only ever styled the tax note and installments) are gone.

    Compare-at colour is no longer dimmed to 40% when a colour is picked; the dim only applies when the picker is left blank, matching the previous unset look.

    Styling is applied on the price container so it survives a variant change (the script that swaps the inner HTML no longer wipes preset classes off the amounts).

    Migration: stored Price 1 / 2 / 3 and wrapper Preset / custom typography on Price (HW) are dropped. Re-set look on the block or in Default price styling. Product card, Featured Collections, Product Carousel/Grid and Product List presets no longer pin prices to a heading size. Compare (HW) product columns no longer force Price 2 — they inherit the theme default.

Fixed

  • filter pills with a custom background color silently lost their border — the border color defaulted to match the background instead of the text color, making an outline-style pill's border invisible. New Border color settings (inactive and active) default to the text color instead.

  • the default-active filter pill's category filter only applied after a click — a fresh page load showed every row regardless of which pill was marked active. The same filter now runs once on init.

  • Product Carousel/Grid (HW): recommendations mode broke the carousel's edge-to-edge bleed. The visibility wrapper added around the header + carousel to hide empty complementary results sat between .section and the carousel's own .force-full-width div — assets/base.css's .section > .force-full-width { grid-column: 1 / -1; } rule only matches a *direct* child, so the carousel silently lost its bleed and picked up normal page-gutter padding instead whenever recommendations mode was on. The wrapper is removed; the empty-state hide rule now targets .section.hw-product-carousel-grid directly instead of introducing a new element between it and the bleed div.

  • Loop – Card Object (HW): dividers broke both the header's gutter and the carousel's bleed. A single shared divider wrapper around the header, carousel and footer put all three one level deeper than .section, so the same .section > .force-full-width direct-child rule above misfired for both: the header inherited force-full-width it was never meant to have (losing its normal page-margin inset), and the carousel's own inner .force-full-width div was nested one level too deep (losing its bleed). Split into three separate divider wrappers — header, carousel, footer — matching the pattern already used by sections/hw-carousel-grid.liquid and sections/hw-product-carousel-grid.liquid.

  • Menu (HW): the desktop nav sat above the centre line of the header. The v1 Menu block (blocks/_header-menu.liquid) sets flex-direction: column on .menu-list__list-item, and Shopify compiles every block's stylesheet into the theme's global CSS whether or not the block renders — so the rule reached the HW nav even though _header-menu is not in header-group.json. Column direction top-packs the link inside a height: 100% list item and leaves the row's spare height below it, knocking the nav out of line with the centred logo and search.

    Undone in sections/hw-header.liquid's inline style block, next to the reset that already neutralises the same block's ::after bridge, rather than by editing the v1 block: base Horizon blocks must stay intact (.cursor/rules/horizon-works-layer.mdc), and _header-menu is still used by the base sections/header.liquid, so deleting the rule there would change the base header for every build. The override only renders when the HW header section does.

    Mega items are excluded, because those deliberately keep column layout for their own bridge.

  • Mobile menu drawer: the whole panel inherited the transparent header's colour pair. The drawer is nested inside the header row, which paints its own background/text inline — over a hero that is a transparent background and white text — and those land on --color-background / --color-foreground. The panel resolved transparent, and every nav link (--menu-top-level-font-color: var(--color-foreground)), the close button and the footer links came out white on white. The drawer now re-establishes its own colour pair from the block's Background/Text settings, falling back to the theme's page palette rather than whatever the header row happens to be using.

  • Mobile menu drawer: the nav font-size settings had no effect on the main nav. The settings live on Drawer menu links (HW), a *static* child block, and the drawer nav was reading them by looping block.blocks and pulling child.settings — the exact pattern .agent/docs/blocks/loop/FINDINGS.md §2.2 documents as unreliable: static children are not reliably listed, and their settings come back unreadable on the storefront. The lookup returned nil, so no typography classes were emitted on the main rows and every one of them fell back to the theme's --menu-font-2xl--size (1.75rem / 28px). The block now renders its own <style>, from its own block.settings, mapping the stored values onto the --menu-top-level-* / --menu-parent-* / --menu-child-* variables the base drawer rules already read — no cross-block lookup and no class plumbing in the path.

    Those variables are scoped to the <ul class="menu-drawer__menu"> rather than to nav.menu-drawer__navigation, because the nav renders menu-font-styles into an inline style attribute and an inline declaration beats any stylesheet rule on that same element regardless of selector. Declaring one level down wins on inheritance instead of needing !important.

  • Menu (HW): "Space between nav items" had no effect. --menu-horizontal-gap was set on .menu-list, but the flex container that actually lays the items out is [part='list'] inside <overflow-list>'s shadow root, where assets/overflow-list.css hard-codes column-gap: 1rem. Nothing read the variable. The gap only ever looked right because the default (16px) happens to equal 1rem. The ::part(list) rule now consumes it.

  • Header (HW): flooring the nav's grid track was not enough to stop it being squeezed. 1fr is minmax(auto, 1fr), and that auto minimum comes from the grid item's own automatic minimum — its min-content, i.e. the widest single nav item — not from the track's max-content. The menu's column now carries min-width: max-content, which sets that minimum directly instead of relying on intrinsic sizing propagating out of the overflow list's shadow root.

  • Header (HW): the nav column was capped at roughly half the header width, wrapping the menu onto a second row. With the menu in a side column and the logo in the centre, neither of hw-header.css's track carve-outs applied — one requires header-menu inside the centre column, the other requires no centre column at all — so the menu track stayed a bare 1fr and split space evenly with the actions column instead of sizing to the nav. Both side tracks are now floored at max-content when the menu is in a bookend, matching what the centred-menu layout already did.

  • Nav item (HW): "Hug content" mega menu width had no effect — the panel was always full width. The [data-mega-target] carve-out that makes full and page-width panels fill the fixed submenu pins the same wrapper to width: 100% !important at a higher specificity than the hug rule, so hug lost every time. Hug is now re-stated at a specificity that wins. The drop shadow moved with it: it is painted on the fixed submenu, which is always the full nav-row width, so a hugged card kept a full-width slab of shadow around it and still read as edge-to-edge.

  • Nav item (HW): the mega menu panel was far taller than its links. The promo tile's image was the only in-flow child of the tile, and height: 100% against an auto-height rail falls back to the asset's intrinsic height — around 1300px for a portrait crop — so the promo rail, not the link column, set the whole panel's height. The image is now out of flow, so the panel measures the links and the tiles crop to that height.

  • Nav item (HW): the block failed to upload to the theme entirely. visible_if was set on three resource-picker settings (menu, and both promo badge product pickers), which Shopify rejects — and a schema error rejects the whole file, so every nav item, mega menu grid, promo rail and badge silently stayed at the previously uploaded version. The attribute is not supported on those setting types and has been removed.

  • Nav item (HW): mega menu columns could not be set to a single column. The Columns range started at 2, but under Hug the value is applied literally (repeat(N, max-content)) rather than reflowed by auto-fit, and the design is one column. Minimum is now 1.

  • Docs: the Group (HW) developer guide documented an overflow / edge-bleed setting the block does not have. Sections 3 and 4 described overflow_x and edge_bleed_* as living on hw-group; both belong to Overflow (HW) (hw-overflow), with hw-grid carrying its own copy of the overflow pair. Corrected in place, because reading the guide as written leads straight to re-implementing a scroll toggle that already exists one block over.

  • Nav item (HW): the two mega menu promo description settings showed on every nav type. They were missing the visible_if their sibling promo settings all carry, so they appeared under a plain link or a product showcase where they do nothing.

  • Quick add (HW): the Size picker style ignored the theme's mobile toggle. The overlay size picker renders its own element and never saw the card's --quick-add-mobile-* variables, so Mobile quick add had no effect on it. It now sits inside the same .hw-quick-add-wrapper as the button and is gated from there. With On hover visibility it also stays reachable on touch screens, where there is no hover to trigger it.

  • Quick add (HW): Size picker on a product with no size option showed nothing at all. The size option is now resolved once in the wrapper, which falls back to the Icon + label button when the product has no matching option instead of leaving the card with no quick add.

  • Quick add (HW): the hover-visibility rules never matched. They targeted quick-add-component, but the HW card renders hw-quick-add-component; the transition they were meant to add was silently dead. Corrected to the element that is actually rendered.

v3.3.0

7 added2 changed5 fixed

Added

  • Hotspot (HW): new block that pins an image anywhere over a positioned parent and shows or hides the blocks nested inside it when the image is clicked.

    Hotspot placement uses the same free-text top/right/bottom/left insets as Absolute (HW) — px, %, or blank for auto — with an independent desktop override.

    Nested content is placed relative to the *centre* of the hotspot: a 9-way direction (top/center/bottom × left/center/right) picks which corner of the panel meets that point, then horizontal and vertical insets (−100 to +100px) nudge it. Separate direction and insets per breakpoint.

    Trigger size is a 1–100px width/height per breakpoint; the image fits inside that box without distortion, and leaving the image empty falls back to a simple round pin.

    Hover animation composes scale, opacity, a directional move, and rotation with a shared duration; an optional idle animation (pulse / float / breathe) draws the eye before anyone interacts. All motion is skipped under prefers-reduced-motion.

    Behaviour toggles for open-on-hover (desktop pointers only), close-on-outside-click, and closing sibling hotspots; shoppers also close a panel with Escape, which returns focus to the trigger.

    With open-on-hover on, hovering reveals the panel transiently and clicking the pin keeps it open until dismissed, so a click never reads as “dismiss the thing I was about to read”.

    No padding, background or appearance settings on the block itself — nest a Group (HW) for those, so the controls live in one place rather than being split across two blocks.

  • Image Small / Medium / Large (HW): three new blocks that size an image by exact pixel width and height, set separately for mobile and desktop — Small 1–100px in steps of 1, Medium 4–400px in steps of 4, Large 10–1000px in steps of 10.

    hw-image sizes its box by intent (fit-content / fill / custom %) and then caps it, so it can’t reliably land on a specific pixel size; these take literal px instead and drop the max-width, max-height, and aspect-ratio controls they no longer need. Corner radius, crop/fit, link, overlay, placeholder, and device visibility all carry over. Image (HW) stays the right choice when the image should scale with its column, needs an aspect-ratio crop, or is larger than 1000px.

    Each tier keeps its slider at 100 discrete values, so every value in its range is reachable — which is why none of them need Image (HW)’s parallel text inputs. Setting IDs are shared across tiers, so swapping one block for another preserves the values (clamped to the new tier’s range).

    Optional Auto height keeps the image’s own ratio at the set width. All three render through snippets/hw-image-fixed.liquid, so the family ships one copy of its CSS. Available anywhere @theme blocks are allowed, plus every slot that already listed Image (HW) explicitly.

  • Absolute (HW): new Backdrop blur setting frosts whatever sits behind the panel, separate from the existing shadow blur that softens the panel’s own contents.

    Default stays 0, so existing overlays are unchanged; a transparent or semi-transparent background is needed for the frost to show.

  • Product card (HW): new Height auto setting sizes that block’s cards to their content instead of always stretching them to fill the row.

    Default stays fill, so existing lists are unchanged; only cards from the block with the setting on are affected.

  • Header (HW): new Desktop menu breakpoint setting controls the width at which the header switches from hamburger/drawer to the full inline nav (750–1400px, default 990px).

    Previously hardcoded to 990px across four places (two {% stylesheet %} blocks, a shared JS breakpoint constant, and the pre-paint FOUC guard in theme.liquid); all four now read the section setting. Default behaviour is unchanged for existing stores.

  • Loop sections (HW): new Laptop layout band (990–1439px) with its own carousel/grid, slides per view, swipe, and gap controls, plus optional fixed card width (px) per breakpoint when in carousel mode — leave empty to keep sizing by slides per view.

    Same layout model on Carousel Grid, Product Carousel/Grid, Collections Carousel/Grid, Featured Collections, Featured Blog, and Collection (HW).

  • Collection Scroll (HW): collection titles can be placed independently on mobile and desktop — horizontal alignment, vertical position, gap, and separate horizontal/vertical inset from the frame edges.

    Previous shared Alignment / Position / Gap apply to mobile; matching desktop controls default to the same values. Uniform Mobile/Desktop inset is replaced by horizontal and vertical inset per breakpoint (defaults match the old 24px / 48px).

    Optional collection metafield paths can replace each slide’s title and mobile/desktop background images, with independent fallback to the standard collection title and image.

Changed

  • Buttons theme settings: Horizontal padding, Vertical padding and Minimum height (mobile + desktop) are now set per button size preset (Extra Small … Extra Large) instead of one global padding pair plus a minimum height on each of the twelve Fill/Outline colour presets.

    The split is now clean: a size preset owns the whole box (typography, padding, minimum height), a colour preset owns colour, border width and radius. Picking Fill 2 over Fill 1 no longer silently changes a button's height, and the five size presets are no longer forced to share one padding value.

    Section headers renamed Button size presets / Button shape to match. Minimum-height ranges are 0–100px step 1 throughout (Button (HW)'s own override was 0–120px step 2).

    A button with no size preset — base Horizon buttons, add-to-cart, and anything set to custom typography — follows the Small preset, matching how its typography already resolved.

    Migration: button_padding_inline / button_padding_block and all twenty-four colour-preset *_min_height_mobile / _desktop settings are removed, so their stored values are dropped on sync. New per-size padding defaults match the old global (24 / 16); new per-size minimum heights default to 0. Stores that had set a colour-preset minimum height must re-enter it on the size preset(s) those buttons use. config/settings_data.json carries the shipped 20 / 12 padding across to all five presets, so the reference theme is unchanged.

  • Loop sections (HW): Desktop layout settings now apply from 1440px up instead of 990px, so mid-width screens use the new Laptop controls rather than sharing Desktop.

    Migration: stores that tuned Desktop for laptop-width viewports should re-check Laptop settings after sync; schema defaults usually mirror the previous Desktop values, so most sections keep a sensible look without edits.

    Same breakpoint split on Carousel Grid, Product Carousel/Grid, Collections Carousel/Grid, Featured Collections, Featured Blog, and Collection (HW).

Fixed

  • Button (HW): the Padding sliders now actually inset the button when Override theme button padding is on, on every kind of button the block renders — including modal triggers, which were silently unaffected before.

    The override wrote padding onto a hand-listed set of elements (a and .hw-button__submit) and the modal trigger is neither, so its sliders did nothing. Padding is now resolved through --hw-button-pad-block-start / -end / -inline-start / -end on the control, consumed by one rule per shape in the block's stylesheet.

    Icon cell width and padding were also each written from two places at different specificities and breakpoints, so turning on padding override *and* Different icon width on desktop dropped the desktop icon width. Both now resolve from a single variable.

  • Buttons theme settings: Text case gained Capitalize, and Font gained Subheading and Heading — the button size presets previously offered only Default/UPPERCASE and Body/Accent while every other typography preset in the theme offered the full set.

  • Product Media Gallery Carousel (HW): fixed an iOS/WebKit-only bug where the rounded carousel corners showed black triangles instead of clipping cleanly, caused by WebKit failing to clip composited descendant layers via border-radius + overflow:hidden. Forces clipping via -webkit-mask-image instead. Sync-back from Glowwa.

  • Carousel/Grid (HW): fixed the intermittent "loads on last slide" race condition on mobile, wired via a <slideshow-component>-scoped MutationObserver on [in-viewport] that forces scrollLeft back to 0 once overflow actually becomes scrollable. Sync-back from Glowwa, revised after review: corrects via scrollLeft directly rather than slideshow.select(0), which guards on this.current already being 0 and so no-ops in exactly this scenario.

  • Collection Scroll (HW): Active title color now paints the current collection name — type presets (e.g. h3) were winning the cascade, so the setting had no effect. Control moved under Appearance next to Text color.

v3.2.0

3 added2 changed2 fixed

Added

  • Collection Scroll (HW): new full-bleed section that shows one collection background at a time — the stage parks in the screen under the header while scrolling steps through images and titles, then lets the page carry on after the last collection.

    Frame height is the visible stage (px / vh), capped to the space under the header so it always sits fully in view; the header height is measured automatically and Extra top offset only adds to it.

    Scroll distance per collection sets the pace, and Snap to one collection (on by default) glides to the nearest whole image once scrolling stops.

    Mouse wheel, trackpad, touch, arrow keys, Page Down and scrollbar drags all step through it — it rides ordinary page scroll rather than intercepting gestures.

    Transitions: crossfade, cross-zoom, push, or wipe. Optional progress bar, custom active-title icon, and Absolute (HW) overlay slot.

  • Header menu (HW): new Mega menu nav type — a full-width, column-grid dropdown (link columns + an optional promo tile) that sits alongside the existing Product showcase (renamed from the old "Mega menu" — collection grid + optional highlight) and plain/compact Dropdown types.

    Width: independent Full width / Page toggle (Mega menu → Width) lets the grid's columns and promo rail stop at the theme's normal page width and centre, matching Product showcase's page-width option — the panel's background, border, and shadow always stay full-bleed regardless of this setting, so only the content is ever constrained.

    Promo tile: up to two promo images with label, link, and position (left/right of the columns); configurable background/blur, label padding, arrow (default or a custom SVG), and divider colour.

    Grid heading typography: column headings get their own size preset (paragraph/heading/label scale or a fully custom font, size, line height, letter spacing, weight, and case) instead of inheriting the nav link style.

  • Header menu (HW): new unified Menu open style setting (hw-header section) — Page overlay (default, dims/blurs the page behind an open menu), Bottom border, or Drop shadow — applies consistently across Product showcase, Mega menu, and plain/compact Dropdown, replacing the old per-block "shadow on/off" toggle.

    Plain and compact dropdowns no longer carry an automatic subtle shadow; pick Drop shadow if that look is wanted.

    Migration: the old dropdown_show_shadow block setting is removed. Any store that had it switched on needs Menu open style set to Drop shadow after syncing, or it will silently fall back to the new default (Page overlay).

Changed

  • Icon with text (HW): Color is now always available for the label, so you can recolour the line without switching Text style to Custom.

    Blank still inherits the parent or theme; theme icons and custom SVGs that use current color follow the same value.

  • Sync Bot (CI): client-repo PR titles now carry a short description instead of just a commit sha and target branch — Flagship Sync (CORE): <description> (<branch>). Manual workflow_dispatch runs require a new description input; automatic merge-triggered syncs fall back to the merged core PR's own title (full sha/range detail still lives in the PR body). See .agent/docs/workflows/client-sync-target-branch.md §6 and MAINTAINER_GUIDE.md §4.

Fixed

  • Header menu (HW): an open menu's shadow no longer gets clipped by the panel's own scroll container (Chromium clips an element's box-shadow when it's also the scrollport) — shadow now paints on the outer, non-scrolling submenu box.

  • Header menu (HW): the page-overlay dim/blur now reliably appears for Product showcase panels whose content (e.g. a collection grid) hydrates asynchronously, instead of sometimes never activating.

v3.1.0

6 added4 fixed

Added

  • Announcement Bar (HW): new Display mode setting switches the bar between a rotating carousel, a static row showing every message at once, and a continuously scrolling marquee ticker, using the same slides either way.

    Switching modes needs no content rebuild and no saved-JSON migration; carousel remains the default, so existing stores are unchanged.

  • Announcement Bar (HW): Navigation style can now show a dot progress indicator instead of arrows, so autoplaying bars can hint at position without prev/next controls.

  • Announcement Bar (HW): dots can sit below, left, or right of the message, with alignment and gap controls for below and an edge inset for left/right.

  • Announcement Bar (HW): new Hug content width option for dots shrinks the message box to its real text so dots sit beside the words instead of out at the edge of a wide, mostly-empty bar.

  • Announcement Bar (HW): new Arrow layout option places arrows tight against the widest message rather than stretched to the bar edges, with a configurable gap, matching the BPerfect reference design.

    Hugging measures the real rendered text, not the Slides max width setting, so long messages are never forced to wrap.

  • Announcement Bar (HW): marquee mode's pause control can be placed left or right and sized independently, so it fits a thin announcement row rather than borrowing the Marquee section's larger default.

Fixed

  • Announcement Bar (HW): the dot corner radius slider now applies its value as a percentage as its label describes, instead of pixels — previously every setting above the lowest few rendered a fully round dot, making a 51-step slider behave as an on/off toggle.

  • Announcement Bar (HW): Arrow layout is now offered only with the Fade transition, since Slide gives every message an equal share of a wider track and leaves no width for arrows to hug.

  • Announcement Bar (HW): turning off Show arrows now hides dot navigation too, instead of leaving dots visible when the navigation style had previously been set to dots.

  • Announcement Bar (HW): two marquee schema labels referenced translation keys that did not exist, causing theme-check errors on the edge fade settings.

Show 9 earlier releases

v3.0.0

7 added4 changed1 fixed

Added

  • Typography: Theme settings → Typography now offers twelve paragraph presets (was six), so two different font weights at the same size are possible without duplicating text blocks or overriding weight per-instance.

  • Typography: Letter spacing and line height on every type preset (paragraphs, headings, label, link, and button sizes) now accept an optional exact override in px, %, em, or rem alongside the existing slider, for finer control than whole-percent steps allow.

  • Col Carousel Card (HW) / Col Carousel Group (HW): split into two sections so Content offers only Card or only Group — the theme editor no longer shows both layouts under one tree.

  • Collections Carousel/Grid (HW): sibling of Carousel Grid that loops a section collection list into slides with Closest collection; gutters match Carousel Grid for correct peek/edge width. Card overlay works for all background media; background image uses the block setting only.

  • Card (HW): optional Collection setting so nested blocks can connect to Closest collection (falls back to an inherited closest when blank).

  • Button (HW): Link style gains an optional colour picker for the link text; blank inherits the surrounding colour.

  • Button (HW): Theme settings → Buttons now include five size presets (Extra Small through Extra Large). Default is Middle; custom sizing still works where it already existed.

Changed

  • Icon with text (HW): block type renamed to hw-icon-text (editor name unchanged).

    Migration: update saved theme JSON from "type": "hw-card-icon-text" to "type": "hw-icon-text".

  • Button (HW): size presets renamed to Small / Middle / Large, with Extra Small and Extra Large added in theme settings. Default is Middle.

    Migration: saved 1 / 2 / 3 still map; re-check theme typography button sizes after sync (setting ids renamed).

  • Button (HW): Fill and Outline presets now use Fill 1–6 / Outline 1–6 naming. HW blocks and templates were migrated; base Horizon blocks keep the old option values; legacy values still accepted.

  • Styleguide: button showcase updated for the colour palette and twelve Fill / Outline presets (full style × size matrix).

Fixed

  • Carousel slides-per-view width: whole-number Desktop/Tablet/Mobile slides per view (e.g. 3) no longer reserves peek space for the next card, so N cards match the page-width controls row. Use a fraction (e.g. 3.2) when you want a next-card peek.

v2.2.0

2 added1 changed1 fixed1 removed

Added

  • Group (HW) / Group Hover (HW) / Group (Conditional): new Centre while scrolling (desktop) option keeps a column's contents vertically centred in the viewport as the shopper scrolls past, without sliding under the sticky header.

    Needs a taller sibling column to have room to travel; the sticky-centre shell preserves the group's gap, direction, and alignment.

  • Carousel navigation: Theme settings now set brand-wide defaults for prev/next arrows (style, size, gap, corner radius, border, and disabled opacity), so carousels look consistent without per-block tweaks.

    Carousel navigation (HW), Cart upsells (HW), and Product linked cards (HW) each gain an Override theme carousel navigation toggle for one-off instances; arrow colours stay block-level either way.

Changed

  • the default preset's heading/filters and sticky-image columns now use Centre while scrolling (desktop), matching the new Group (HW) behaviour.

Fixed

  • the "Ingredient card" preset no longer ships a leftover metaobject image binding, matching the unbound default of every other preset.

Removed

  • Group (HW) / Group Hover (HW) / Group (Conditional): the advanced Sticky on desktop setting is replaced by Centre while scrolling (desktop).

    hw-faq-sticky-group (a separate block used elsewhere in FAQ Split) is unaffected and still offers plain fixed-offset sticky.

v2.1.0

1 added3 changed4 fixed

Added

  • Expanding cards (HW): new footer slot below the card track (same nested text/group/button model as the header) so merchants can add CTAs or copy under the carousel without changing the section width.

Changed

  • Expanding cards (HW): section and card colours move onto the colour palette — an optional background/text pair (and divider pair) replaces the old colour schemes. Card content stays transparent so media shows through; white text remains the photo default until a text colour is set.

  • Expanding cards (HW): the card content panel now exposes the same mobile/desktop direction, alignment, gap, and padding controls as HW Group. Media, stretch link, and progress stay card-owned.

  • Expanding cards (HW): the pause control gains a corner position and a show-on breakpoint (both / desktop-only / mobile-only) alongside the existing size toggle.

Fixed

  • Expanding cards (HW): an empty or unconfigured card loop no longer renders a stray placeholder card on the live storefront (the editor still shows scaffolding).

  • Expanding cards (HW): empty header/footer no longer leave a blank gap above or below the card track.

  • Expanding cards (HW): fixed a white flash when clicking rapidly between cards; desktop cards now always fill the track and progress restarts cleanly.

  • Expanding cards (HW): on full-width sections, header and footer copy now sit within the page gutters instead of flush to the viewport edge while the card track keeps bleeding edge-to-edge.

v2.0.0

3 added4 changed4 fixed3 removed2 docs

Added

  • Colour palette: a single store-wide set of colours in *Theme settings → Colors* replaces the old colour schemes. Any section or block can override its own colours, and those colours cascade only to that subtree.

    Whatever the merchant picks is applied verbatim — nothing is derived and no contrast is computed, so a dark background needs an explicit text colour set alongside it.

    Docs: .agent/docs/architecture/COLOR_PALETTE.md, .agent/docs/theme-settings/color-palette/.

  • Buttons: twelve palette-backed presets — six Fill and six Outline — each with its own base and hover colours plus border width, corner radius, and mobile/desktop min-height. Every button-bearing block now picks a preset instead of carrying its own colour fields.

  • Carousel & linked-card arrows: own base, hover, and disabled colours (background / icon / border) plus a border width, instead of borrowing from a button preset. Colours now hold on hover.

Changed

  • Colour palette migration: every section, block, and shared snippet moved off colour-scheme classes onto palette colours. Sections expose a background/text pair (plus a divider pair where relevant); blocks inherit by leaving their pickers blank.

    Breaking: saved color_scheme values no longer resolve — a section previously set to a non-default (e.g. dark) scheme falls back to inheriting the page palette until its colours are re-set. It renders light, not an error.

    Migration: all 15 shipped JSON templates were converted with equivalent colours. Client stores holding their own saved data need a pass through the editor after syncing.

  • Buttons: the tertiary family is now presented as Outline 2; its underlying class is unchanged so existing markup and saved data are unaffected.

  • Marquee, Compare, and Hero slideshow (which landed from main mid-migration) were brought onto the palette alongside everything else.

  • Colour-scheme vocabulary retired from merchant-facing copy, setting labels, code comments, and docs (this changelog's historical entries excepted).

Fixed

  • Colour palette: an inverted section no longer renders dark-on-dark muted copy — the muted foreground is re-derived from the section's own text colour.

  • Media hotspots: the popup background no longer paints across the whole gallery image; it lands on the popup itself.

  • Carousel & linked-card arrows: colours hold through hover instead of being taken over by the old button preset.

  • Hero + templates: an orphaned colour key on saved text/heading blocks was renamed, clearing the invalid-preset error it raised.

Removed

  • Legacy colour-scheme system: the color_schemes config, the scheme snippet and its class emitters, and the old scheme docs are gone, replaced by the palette equivalents.

  • Colour-only wrapper elements, inert colour-class guards, the dead hw-text colour wrapper, and other now-unused scheme-era scaffolding.

  • Locale keys and preset/template data orphaned by the migration.

Docs

  • New: COLOR_PALETTE.md (the model, the full color-style parameter table, the token reference, and a troubleshooting section) plus the color-palette theme-settings guides (overview, editor, developer).

  • Rewritten for the palette: the colour docs, the architecture cheat sheet and section-building template, the cart and product family guides, and the merchant-facing editor guides.

v1.3.0

1 added

Added

  • Hero slideshow (HW) (sections/hw-hero-slideshow.liquid, blocks/_hw-hero-slide.liquid): Multi-slide full-bleed banner sibling to static Hero (HW). Per-slide image/video (optional mobile media), overlay, and nested HW content (Group / Text / Button). Section-level minimum frame height (vh or px, content can grow taller), autoplay with pause control, and hero-native arrows/pagination with icon style/shape, colour schemes (shaped arrows use primary button colours), inset, and pagination position/dot size/gap. Transparent-header compatible via hw-hero-wrapper. Prefer over bare Horizon slideshow for new HW work. Docs: .agent/docs/sections/hw-hero-slideshow/.

v1.2.0

5 added2 changed4 fixed

Added

  • hw-product-card-gallery: optional image_metafield setting to source the card image from a product metafield (falls back to featured image).

  • Carousel navigation & hw-product-linked-cards: arrows can be sized via explicit nav-button-width/nav-button-height (falls back to legacy padding sizing when unset).

  • hw-star-rating: underline_text option to underline the rating text.

  • Marquee (HW) section (sections/hw-marquee.liquid, snippets/hw-marquee.liquid, assets/hw-marquee-section.js): Standalone scrolling strip with trimmed settings (gap, optional leading label, direction left/right, slow/medium/fast speed, pause on hover/focus, pause control, optional edge fade). Dual-track CSS loop with prefers-reduced-motion support; script registered once via hw-scripts.liquid. Presets: scrolling text + logo strip. Core templates (index, collection, product, page.general) migrated off nested hw-section-starter + block wrappers. Docs: .agent/docs/sections/hw-marquee/.

  • New comparison-table section family — no prior HW equivalent. N-column, horizontal scroll with a sticky feature-label column, optional per-column product binding, column-level background/typography/icon overrides, custom typography support, and stacked or side-by-side header layouts. Blocks: hw-compare-header, hw-compare-footer, hw-compare-column-list → hw-compare-column → hw-compare-cell, hw-compare-row-list → hw-compare-row. Two presets: "Us vs. them" and "Pricing tiers".

Changed

  • hw-faq-split: 'Show top border' now governs both outer borders (relabelled 'Show outer borders (top & bottom)').

  • Legacy marquee block deprecated for new use: blocks/hw-marquee.liquid preset removed; Marquee preset removed from hw-section-starter. Block file kept for sync safety until client instances migrate.

Fixed

  • media: native play/pause now broadcasts so only one deferred video plays at a time (base Horizon file).

  • slideshow: next arrow no longer stays disabled after swiping back to the first slide (base Horizon file).

  • hw-search: 'No results for X' now updates on each keystroke instead of freezing on the first term.

  • hw-text: text alignment now applies when a block renders a bare text node (e.g. truncated text) at full width.

v1.1.0

2 added1 changed5 fixed

Added

  • Design-to-Template Playbook (.agent/docs/architecture/DESIGN_TO_TEMPLATE_PLAYBOOK.md): New process guide for turning a Figma design into a composed template/section/block tree from existing HW components. Covers decompose → match components → colours → typography → spacing → assemble → validate → flag-gaps, with a delivery checklist. Hardened after two real process failures: (1) a build that hand-wrote new markup instead of composing from existing components, and (2) a build that conflated a block's type_preset (visual size only) with its semantic HTML tag. Step 4 now states explicitly that the semantic tag and type_preset are independent decisions; Step 7 and the delivery checklist now require writing down real Figma numbers as a spec table and diffing live getComputedStyle/getBoundingClientRect values against it, on every breakpoint, before calling composition work done.

  • New alwaysApply: true Cursor rule (scoped to templates/*.json, sections/*.liquid, sections/*-group.json) that forces a read of the playbook, COMPOSITION_RULES.md, and CAPABILITY_MATRIX.md before any design-to-template work, and summarizes the two failure modes above so they're caught even on a rushed read.

Changed

  • `.agent/catalog/catalog.json` guidance: Removed all playbook references that pointed at the full ~58k-line component catalog for single-component lookups. The playbook now directs readers to the component's own .liquid file directly; the catalog is reserved for genuine cross-component surveys only.

Fixed

  • `.size-style` height inheritance: Scoped the height rule in assets/base.css to .group-block.size-style so nested hw-button/hw-text elements no longer inherit an ancestor Group (HW)'s custom height via the shared CSS class (was causing buttons to balloon to the full height of their parent card).

  • Template JSON — no Liquid: Replaced {{ article/blog/collection/page.* }} in templates/*.json with static placeholders so CI (check-template-json-no-liquid.sh) passes and storefronts do not print literal tokens.

  • PDP/client price fallback: product-price.js honors data-show-sale-price-first when morphing from embedded variant JSON misses the live data-block-id.

  • Variant ATC labels: hw-modal-variant-picker updates every product-form-component label in the section/dialog (main + sticky). Product cards distinguish sold-out vs unavailable label copy.

  • PDP enrichment: hw-pdp-variant-picker section fetch uses async/await. hw-add-to-cart-button translates locale-key add_to_cart_text values when unavailable.

v1.0.0

62 added59 changed68 fixed1 removed17 docs

Added

  • Hero Shape (HW) (blocks/hw-hero-shape.liquid, snippets/hw-hero-shape.liquid): New decorative shape block for use inside HW Hero. Absolutely positioned relative to the section frame (not the page-width wrapper — section-content-wrapper position reset in hw-hero.css). Settings: image picker; corner Position (top-right / top-left / bottom-right / bottom-left) with Override on desktop; Width (mobile + optional desktop override, free-text accepting px or rem); Offset X / Y (mobile + optional desktop override, free-text accepting px/rem/negative values, applied via translate before rotation); Rotation (−180–180°, range); Show on (both / mobile only / desktop only); Layer (behind / above) with Override on desktop; Opacity (10–100%). CSS custom properties drive all values; desktop vars fall back to mobile vars when no override is set. Locale keys added to en.default.schema.json.

  • HW Hero — editorial layout mode: layout_mode setting on HW Hero (overlay / editorial, default overlay). In editorial mode the ratio-box sizes to content (no padding-top/min-height frame), .hw-hero__content reverts to static positioning, and background media / overlay / section link settings are hidden. Enables 50/50 side-by-side hero layouts via a row-direction Group (HW). Editorial preset added to the section schema. CSS: .hw-hero__ratio-box--editorial, .hw-hero__content--editorial, .hw-hero__content-inner--editorial in hw-hero.css.

  • Group Hover (HW) (blocks/hw-group-hover.liquid, `snippets/hw-group-hover.liquid`): Full Group (HW) layout, appearance, sticky, link, and padding surface plus optional Hover Effect (background/border/text colour, scale 100–110%, lift, shadow sm–xl, transition duration/easing). Per-instance hover CSS targets `data-hw-group-hover="{{ block.id }}"`. Allowed children: @theme, @app, nested group variants, `hw-inset`, `hw-button`, carousel controls, `hw-flex-spacer`, countdown blocks, etc. Docs: `.agent/docs/blocks/hw-group-hover/EDITOR_GUIDE.md`, `DEVELOPER_GUIDE.md`.

  • Text (HW, align) (blocks/hw-align-text.liquid): Thin wrapper around `snippets/hw-text.liquid` with `force_alignment: true` — alignment applies at fit-content width, not only when width is Fill. Separate Alignment (desktop) and Alignment (mobile); mobile uses `--text-align-mobile` + **hw-text--align-mobile-* (`assets/hw-text.css`). Full Text (HW) typography, truncation, background, and padding schema. Locale: t:names.hw_align_text**.

  • Collection Count (HW) (blocks/hw-collection-count.liquid): Picks a collection and replaces `$$$` merge tokens in richtext with `collection.products_count` (falls back to 0 when unset). Renders via `hw-text` + `text_override`. Default copy: $$$ items. Docs: `.agent/docs/blocks/hw-collection-count/EDITOR_GUIDE.md`, `DEVELOPER_GUIDE.md`.

  • Price typography presets (HW): Theme settings → Prices → Price typography presets defines three shared size scales (Price 1, Price 2, Price 3) with separate mobile/desktop ranges for the main price, compare-at price, and savings label, plus global Price color and Compare price color. Price (HW) (blocks/hw-price.liquid) adds Price typography preset (price_preset: None / 1 / 2 / 3, default 1); snippets/hw-price.liquid emits price-preset-container / price-preset--{n} and accepts price_preset. Sizes resolve via CSS custom properties in `snippets/theme-styles-variables.liquid` (--price-preset-{n}-price-size, -compare-size, -label-size) and preset class rules on .price, .compare-at-price, .price__savings-label, and .hw-price__from-amount. Layout flex wrapper in `assets/hw-price.css`. Docs: `.agent/docs/blocks/price/EDITOR_GUIDE.md`, `DEVELOPER_GUIDE.md`; `.cursor/references/hw-price-settings-patterns.md`.

  • Search panel (HW) (sections/hw-search-panel.liquid, `assets/hw-search.js`, `assets/hw-search.css`): Header-group section (groups: ["header"], limit 1) for a fixed search overlay aligned below the nav. Custom element `hw-search-panel` drives open/close, optional backdrop (sibling node, not a child), scroll lock, `hw-header--search-open` on the header, Escape / ⌘K shortcuts, and focus return to the trigger. States: popular (pill tabs from `pill_collections` with optional label overrides filter static product cards), results (debounced AJAX via ?section_id=hw-search-panel&q=...), and no-results. Static `hw-product-card` block for card layout. Section settings cover placeholder/clear colour, close icon (image or SVG), colour scheme, section width (page / full / custom text), backdrop opacity, popular/results labels and count typography (preset + custom), pill styling (button presets, heights, padding, inactive opacity), sidebar width, divider, panel padding, logo area width on the desktop search row, and mobile breakpoint. Storefront copy in `locales/en.default.json` (hw_search.*). `sections/header-group.json` preset adds the section and wires the Glowwa header. Docs: `.agent/docs/sections/hw-search-panel/EDITOR_GUIDE.md`, `DEVELOPER_GUIDE.md`; `.cursor/references/hw-search-panel.md`.

  • Collection (HW): New hw-collection section. Loops collection.products and renders each via the hw-product-card block using product_context, delegating layout to hw-loop-carousel-grid. No filters — clean grid/carousel with standard HW layout controls. Drop onto any collection template.

  • Loop – Collection Tiles: New hw-loop-collection-tiles section and _hw-loop-item__collection-tile block. Loops a collection_list setting (supports dynamic source connection — e.g. collection.metafields.custom.filter_collections on a collection template) and renders each collection as a composable tile via child blocks. Active state applied automatically when the tile's collection handle matches the current page collection. Falls back gracefully when no source is configured. CSS registered in hw-stylesheets.liquid.

  • Cart drawer — compact line items (`hw-cart-line-item`): `item_layout: compact` card rows (thumbnail + title/qty pill, meta line, price + text remove) in `snippets/hw-cart-product-compact-row.liquid` / `assets/hw-cart-products.css`. `drawer_layout: compact` (section) and `item_layout` are independent — set both for the Glowwa-style drawer. Compact-only controls: mobile `thumbnail_size` (default 76px) + optional desktop override at 750px+ viewport (thumbnail_size_desktop, default 82px); `compact_content_gap` (title → details) and `compact_meta_price_gap` (details → price); `compact_vertical_align` (top or space price to thumb bottom). Subscription UI slot: `show_subscription_slot` renders `snippets/hw-cart-line-item-subscription.liquid` in the same <tr> (grid area subscription) — empty in HW, override in brand themes. `sections/cart-group.json` preset wires compact drawer + line items. Docs: `.agent/docs/cart/cart-drawer/DEVELOPER_GUIDE.md` §11, `.agent/docs/cart/cart-drawer/EDITOR_GUIDE.md`.

  • Sticky buy CTA (HW) (blocks/hw-sticky-buy-cta.liquid, `assets/hw-sticky-buy-cta.js`): Glowwa-style PDP bottom bar that scrolls to the buy area (not add-to-cart). Shows after scrolling past buy buttons; optional hide near footer; variant morph on `ThemeEvents.variantUpdate`. Mobile: full-width CTA strip. Desktop: centered floating card (Figma 731px max width, configurable side inset), optional product summary (image, title, variant badge, 2-line description, Price (HW)), separate bar / CTA / badge color schemes and button presets (primary, secondary, tertiary). Desktop CTA ideal width targets button column width when summary is visible (flex: 0 1 ideal, shrinks when tight). `color-{{ scheme }}` on painted controls only (not wrappers) to avoid global **body,.color-* page background on pills. Rendered at Product information (HW) section level; script in hw-scripts.liquid on product template. Turn off enable_sticky_add_to_cart on hw-product-information to avoid two bars. Preset hw_sticky_buy_cta_glowwa in templates/product.json. Docs: .agent/docs/blocks/hw-sticky-buy-cta/EDITOR_GUIDE.md, DEVELOPER_GUIDE.md; .cursor/references/hw-sticky-buy-cta.md**.

  • Product linked cards (HW) (blocks/hw-product-linked-cards.liquid): PDP upsell strip (Style 2 cart-upsell cards) inside `_hw-product-details`. Template `product_list` or current-product metafield list; independent mobile/desktop slider / stack; mobile card-track page-gutter bleed (nav/title inset); card title/description typography presets; description from product, per-linked-product metafield, or block text with dynamic source; optional View button padding/min-height overrides. Reuses `hw-cart-upsells.css` / `hw-cart-upsells.js` (cart drawer gate). Docs: `.agent/docs/blocks/hw-product-linked-cards/EDITOR_GUIDE.md`, `DEVELOPER_GUIDE.md`; `.cursor/references/hw-product-linked-cards.md`.

  • Button modal (HW) (blocks/hw-button-modal.liquid): Dedicated block for a CTA that opens a modal via Horizon `dialog-component`. Includes static nested `hw-button` trigger (modal_trigger), panel settings (viewport inset, position, max width as px/rem text, overlay, padding, close control), optional desktop overrides (gap, radius, padding, close size, max height) with step 1 ranges. Nested content: `hw-text`, `hw-image`, `hw-group`, etc. Mobile bottom position squares off bottom panel corners (bottom sheet). Docs: `.agent/docs/blocks/hw-button-modal/EDITOR_GUIDE.md`, `DEVELOPER_GUIDE.md`; README blocks table updated.

  • PDP layout and gallery (HW buy area):

    `sections/hw-product-information.liquid` — When Equal columns is off, Override desktop column widths exposes Desktop media max width and Desktop details max width text fields (px/rem) plus optional desktop gap override between gallery and product details.

    Theme settings → Page layout — Optional text overrides for narrow, normal, and wide content widths; independent mobile and desktop page gutter overrides (emit scoped `--page-margin` from `snippets/theme-styles-variables.liquid` when enabled).

    `blocks/_hw-product-media-gallery.liquid` — Extend media to screen edge on mobile toggles full-bleed vs inset gallery on page-width sections; when inset, bottom thumbnail rows can still bleed into the page gutter via `assets/hw-product-media-gallery.css`. Loop carousel checkbox disables slideshow infinite and enables end-state arrow disabling. Icons = Custom adds full Gallery arrow customization (carousel-nav controls, horizontal arrow inset with optional desktop override, arrow/chevron SVG stroke width 0.5–4px step 0.5). Sidebar Gap uses step 1 ranges (0–48px mobile, 0–64px desktop override).

    `blocks/hw-product-media-overlay.liquid` — Gallery media overlay (HW) child block: corner stack on the main slider surface (nested text, groups, price, etc.); horizontal/vertical inset with optional desktop override (0–100px, step 1); slider host respects custom arrow horizontal inset. Reparented to `__slider-overlays` by `assets/hw-product-media-gallery.js`.

    Docs — `.agent/docs/blocks/product-media-gallery/EDITOR_GUIDE.md`, `DEVELOPER_GUIDE.md`; index link in `.agent/docs/README.md`.

  • HW Group — width “Content” (intrinsic): New `width` / `width_mobile` value `content` on `hw-group`, `hw-group-basic`, `hw-group-card`, `hw-group-link` (`t:options.hw_group_width_content`). `snippets/hw-group-size-style.liquid` (`max-content`) + `assets/hw-group-layout.css` (`max-width: 100%`); `base.css` `group-block--width-content` uses `flex: none`. Homepage carousel stockist inner group `templates/index.json` uses `width: content`. Paragraph `t:info.hw_group_width_content` shows when Content is selected.

  • Presence indicator (HW) (blocks/hw-presence-indicator.liquid, `snippets/hw-presence-indicator.liquid`, `assets/hw-presence-indicator.css`): Online / live status dot with optional animated pulse; dot and pulse ring colours, size, pulse on/off, duration, and optional screen reader label (defaults to `accessibility.hw_presence_indicator` / “Online” in `locales/en.default.json`). Styles registered in `snippets/hw-stylesheets.liquid`. Allowed on `hw-group`, `hw-group-basic`, `hw-group-card`, `hw-group-link`, and `sections/hw-hero`. Docs: `.agent/docs/blocks/hw-presence-indicator/DEVELOPER_GUIDE.md`, `.agent/docs/blocks/hw-presence-indicator/EDITOR_GUIDE.md`.

  • HW Carousel Navigation — button appearance: `blocks/_hw-carousel-navigation.liquid` adds `arrow-button-appearance` (Icon / colour scheme vs Primary / Secondary / Tertiary) and `arrow-button-preset` (1–3, theme typography). Inherited colour scheme settings show only when Icon. `snippets/slideshow-arrow.liquid` accepts optional `arrow_button_style` / `arrow_button_preset`; `snippets/hw-carousel-navigation.liquid` wires theme variants, `hw-carousel-navigation__arrows--theme-button`, and scoped CSS. Locale keys added under `t:settings`, `t:info`, `t:options`. `visible_if` for hyphen ids uses `block.settings['arrow-button-appearance']` for theme-check.

  • HW Carousel Progress Bar — corner radius: `blocks/_hw-carousel-progress-bar.liquid` range `progress-bar-corner-radius` (0–24px, default 4); appearance paragraph localized as `t:info.hw_carousel_progress_bar_colors`. `snippets/hw-carousel-progress-bar.liquid` sets `--progress-bar-radius` / `--progress-bar-height` and clipped visual radius via Liquid. Colour-scoped track BG uses **.hw-carousel-progress-bar[class*='color-scheme'] so any color-{{ scheme }}** token is covered without listing schemes 1–6 only.

  • Ingredient loop section + item block: Added dedicated `hw-loop-ingredient` section (sections/hw-loop-ingredient.liquid) and `_hw-loop-item__ingredient` block (blocks/_hw-loop-item__ingredient.liquid) following the same loop-section pattern as UGC. Data source is a selected ingredient group metaobject entry plus a list-field key that resolves individual ingredient item entries per slide via closest.metaobject.ingredient. Item block includes optional min_height / enable_min_height settings for controlling card height per slide.

  • UGC loop section + item block: Added dedicated `hw-loop-ugc` section (sections/hw-loop-ugc.liquid) and `_hw-loop-item__ugc` block (blocks/_hw-loop-item__ugc.liquid) using the proven loop-section pattern (header/footer static blocks + slide capture + hw-loop-carousel-grid render). Data source is a selected `ugc_video_group` entry plus a list-field key that resolves `ugc_video` item entries per slide via closest.metaobject.ugc_video.

  • Global loop carousel wiring asset: Added `assets/hw-loop-carousel-wire.js` and registered it in `snippets/hw-scripts.liquid` to wire loop-section navigation controls to slideshow instances in a single shared place.

  • Text (HW) — Basic variant: New `hw-text-basic` block (blocks/hw-text-basic.liquid) reuses `snippets/hw-text.liquid` for RTE-based copy with a streamlined schema beside full `hw-text`. `hw-text` expands settings/rendering for the broader text family and removes `disable_fluid_scaling` — fluid type follows theme typography. `hw-text` / `hw-text-basic` are registered everywhere the theme whitelists text block types (`hw-featured-strip`, `hw-loop-collections`, `hw-blog-card`, `hw-product-card`, `hw-marquee-overlay`, **hw-mobile-drawer-* rich branches, hw-footer / hw-footer-utilities, hw-hero, etc.). hw-featured-strip / hw-loop-collections support optional paired title blocks (hw-text + hw-text-basic) via static ids. locales/en.default.schema.json** block names refreshed for HW text variants.

  • HW FAQ Split — Data-driven list block: `hw-faq-split-data` (blocks/hw-faq-split-data.liquid) and `_hw-faq-split-data-item` (blocks/_hw-faq-split-data-item.liquid) render FAQs from a FAQ Group entry’s `faqs` list (`faq_group`) or a product list-of-FAQ-reference metafield. Accordion UX (drawer vs accordion, caret vs plus, dividers, borders, heading preset) is configured on the item block; one `hw-accordion` wrapper spans all rows via `snippets/hw-accordion` `accordion_fragment` `open` (first loop iteration) / `close` (last), with loop flags passed on `content_for` (**hw_faq_split_*, analogous to form_obj on accelerated checkout). HW FAQ Split (`sections/hw-faq-split.liquid`, v4.3.0) and hw-faq-split-content accept hw-faq-split-data alongside manual hw-accordion trees; templates/index.json / locale strings updated as wired. Supporting CSS assets/hw-faq-split.css; docs .agent/docs/blocks/hw-faq-split/DEVELOPER_GUIDE.md and .agent/docs/blocks/accordion/DEVELOPER_GUIDE.md**.

  • HW Featured Blog — default preset and homepage wiring: Section preset aligns with the storefront HW Featured Blog homepage layout (templates/index.json): mobile/tablet carousel and desktop grid with slide peeks; Carousel Control hw-group (column alignment, padded top) holding Button (HW) (“All Blog Posts”), Carousel Progress Bar, and Carousel Navigation; static-header hw-group + Text (HW) eyebrow with full truncation/case defaults; `_hw-featured-blog-content` nest `static-blog-card` (featured image, meta row with category pill + Article date (HW), title uppercase, excerpt sentence-limited via Text (HW)). Content block schema declares `hw-blog-card`; `hw-group` schema lists `hw-button` so nested buttons validate with theme-check. Section and content block titles use `t:names.hw_featured_blog` / `hw_featured_blog_content`. Preset prefers valid color_scheme enums (no empty strings); carousel blocks omit preset `disabled` (theme-check caveat) — visibility hides progress/nav similarly when needed.

  • Article date (HW) (blocks/hw-article-date.liquid): Block for `published_at` vs `updated_at` with Liquid date filter format (date-format). Typography pattern matches Text (HW). Optional child on `hw-blog-card` (blocks/hw-blog-card.liquid).

  • Text (HW) — truncation and preset capitalization override: Setting Limit displayed length with Limit by: characters (strip HTML → plain truncation), lines (richtext retained, CSS line clamp), sentences (period + space split after strip). Override preset case when typography preset is not Custom. Implemented in `snippets/hw-text.liquid`; `hw-text` block schema additions; `hw-text-typography-settings` skill note.

  • Savings label suffix + gradient background: Theme settings → Prices → Savings label now includes a Suffix input (prices_savings_label_suffix) for text after the savings value (e.g. "20% off"). The Label background setting was changed from color to color_background to support gradient fills; CSS uses background instead of background-color for price__savings-label. (config/settings_schema.json, locales/en.default.schema.json, snippets/hw-price.liquid, assets/hw-price.css, assets/base.css)

  • “From” line typography (separate from main price): Theme settings → Prices and Price (HW) / featured Product price (HW) use the same Preset list as the price block (from_label_type_preset / price_from_label_type_preset), plus Use theme default and Match main price. Custom on the prefix uses typography-style when the block’s main preset is Custom. snippets/hw-price.liquid splits prefix + amount (.hw-price__from-line); prefix text uses content.price_from_prefix. Ad-hoc style options (subdued/body, etc.) were removed in favour of Horizon preset classes.

  • “From” price on product cards (HW): Theme settings → Prices includes "Show "From" price on product cards" (default off). Price (HW) blocks add "From" price (variant prices differ) (use theme default / on / off). snippets/hw-price.liquid uses content.price_from with the lowest variant price when prices differ, including initial render on swatch cards; after swatch selection, the card price updates to the selected variant. sections/section-rendering-product-card.liquid renders hw-price for Section Rendering API cards. Follow-up fix: avoid immediate JS overwrite of initial "From ..." during size-picker auto-init by passing userInitiated through hw:card-option-changed and skipping init-only replacement in product-card.js. (config/settings_schema.json, locales/en.default.schema.json, blocks/hw-price.liquid, blocks/_hw-featured-product-price.liquid, sections/hw-product-information.liquid, assets/hw-card-variant-picker.js, assets/product-card.js)

  • HW Featured Collections - tab controls + preset refresh: hw-collection-tabs now supports comma-separated custom tab labels and responsive row behavior controls (overflow scroll or wrap on mobile, with optional desktop override). Added a new section preset "HW Featured Collections (Current Index)" to mirror the currently saved homepage configuration.

    Files: blocks/hw-collection-tabs.liquid, assets/hw-featured-collections.css, sections/hw-featured-collections.liquid.

    Editor simplification: Removed hw-collection-tabs border radius controls (unused) and kept capsule radius static in CSS.

  • HW Grid - desktop row gap override (blocks/hw-grid.liquid, assets/hw-grid.css, locales/en.default.schema.json): When Override row gap is on, merchants can optionally enable Override row gap on desktop (990px+) and set Row gap (desktop) separately from Row gap (below 990px). Tablet row gap continues to match the below-990px value. CSS variables --hw-grid-row-gap-mobile, --hw-grid-row-gap-tablet, and --hw-grid-row-gap-desktop drive row-gap in assets/hw-grid.css.

  • HW Carousel / Grid - Comparison carousel preset (sections/hw-carousel-grid.liquid, locales/en.default.schema.json): New section preset Comparison carousel (Storytelling) that recreates the before/after + testimonial carousel layout: carousel-controls group (progress bar + navigation), static-header (eyebrow, title, description), and static-carousel-grid-content with three HW Grid slides (two Group (HW) image cells plus a full-width HW Grid item for name and quote). All hw-image picks are empty so the theme shows image placeholders; copy uses t:html_defaults.hw_comparison_carousel_* keys in html_defaults. Section colour scheme in the preset defaults to scheme-1 for portability across stores.

  • Flex spacer (HW) & Group (HW) height (desktop): Composable "spring" flex item and optional per-breakpoint group height.

    `blocks/hw-flex-spacer.liquid` - Version 1.0.0 (file comment). Invisible div.hw-flex-spacer with flex: 1 1 0 in row/column; `display_breakpoint` (all / mobile only / desktop only) via hw-block-display classes. Schema uses plain English for block name, preset, and help paragraph to avoid ValidSchemaTranslations issues when en.default.schema.json contains duplicate keys; optional t: keys remain in locale file for later use.

    `assets/hw-flex-spacer.css` - Mobile vs desktop rules for layout-panel-flex, HW group data-hw-desktop-layout overrides at 750px, and Hero .hw-hero__content-inner > .hw-flex-spacer. Registered in snippets/hw-stylesheets.liquid.

    `snippets/hw-group-size-style.liquid` - HW-only duplicate of size-style width logic; when override_height_desktop is on, sets --size-style-height-mobile and --size-style-height for mobile vs 750px+ desktop. snippets/hw-group.liquid uses this instead of size-style and adds hw-group--override-height-desktop + hw-group--desktop-h-{fit,fill,custom} when the checkbox is on.

    `blocks/hw-group.liquid` - Settings: override_height_desktop (default off), height_desktop, custom_height_desktop; child block type `hw-flex-spacer`. `assets/hw-group-layout.css` - Desktop height/flex overrides for data-hw-desktop-layout='column' when override classes are present.

    Sections: hw-hero block list includes hw-flex-spacer (and group already allows the block).

    Locales: names / info / settings keys for hw_flex_spacer and hw_group_override_height_desktop in locales/en.default.schema.json.

    Docs: .agent/docs/blocks/hw-flex-spacer/DEVELOPER_GUIDE.md, EDITOR_GUIDE.md; index in .agent/docs/README.md.

  • Tabbed content (HW) (blocks/hw-tabbed-content.liquid, blocks/_hw-tab.liquid, snippets/hw-tabbed-content.liquid, snippets/hw-tab.liquid, assets/hw-tabbed-content.css, assets/hw-tabbed-content.js): New composable tabs feature.

    Core: Tabs with Tab (HW) children (<hw-tab>) and optional `hw-image` blocks. Parent uses {% content_for 'blocks' %} only; hw-tabs JS builds role="tablist", reparents tab triggers, toggles panels/ARIA, and handles keyboard support (Arrow / Home / End / Enter / Space).

    Image hoisting: JS hoists direct-child .hw-image into .hw-tabbed-content__media after the tab row (Shopify does not allow dynamic id/type on {% content_for 'block' %}).

    Tab row: Responsive layouts at 990px - flex wrap, grid (column counts + optional desktop column override), or horizontal slider with symmetric page-margin bleed and hidden scrollbar.

    Typography: Button preset (primary/secondary/tertiary + preset + scoped --blockId) or heading/paragraph presets via data-hw-tabs-type-class and JS.

    Shell: content_vertical_position (top / center / bottom) with .hw-tabbed-content__shell for alignment inside tall columns.

    Colour scheme: Inherit colour scheme (default) and optional Colour scheme on the shell (color-{{ scheme }}, same idea as Group HW).

    Tab (HW): Label, richtext body, open-by-default; optional Panel button under the body (link URL, label, style, preset, new tab); Panel button width - fit content vs full width, with optional desktop override at 990px.

    Locales & assets: accessibility.hw_tabbed_section_tabs added to locales/en.default.json; hw-image gains display_breakpoint select (desktop only / mobile only / both). Assets wired through snippets/hw-stylesheets.liquid and snippets/hw-scripts.liquid.

    Docs: .agent/docs/blocks/hw-tabbed-content/DEVELOPER_GUIDE.md, EDITOR_GUIDE.md; index link in .agent/docs/README.md.

  • HW Card block (`hw-card`) for HW Carousel / Grid: Horizon Works replacement for the base _card, scoped under .hw-card__* so the base _card stylesheet (with aspect-ratio: var(--card-ratio, 1) default) cannot leak in. Outer wrapper keeps the .card class so the carousel's slideshow-slides > .card sizing rules still match.

    Responsive aspect ratio - auto or custom (0-200%, step 2%) expressed as height-as-a-percentage-of-width via a padding-top ::before pseudo (100% = square, 56% ≈ 16:9, 200% = 2:1 portrait). Separate desktop value with a Different ratio on desktop toggle; single @media (min-width: 990px) rule swaps --card-ratio-padding-desktop in. All four combinations render correctly (mobile-only, desktop-only, same, different).

    Per-card heights - .hw-card uses align-self: start (no height: 100% on card or content) so different cards in the same carousel/grid can render at different heights without being stretched to the tallest sibling.

    Gradient-capable overlay - color_background-typed overlay_color setting renders via hw-overlay (same pattern as hw-stat-split).

    Click-to-play background video - optional poster + custom play icon mode for background videos, driven by a new <hw-card-bg-video> custom element (assets/hw-card-bg-video.js). Delegated click pauses/plays the swapped-in <video> and stopPropagation prevents the block link from firing. .hw-card--bg-video-click stack flip: link drops to --layer-flat, media wrapper rises to --layer-raised with pointer-events: none + interactive-children opt-in.

    Block padding moved to inner - spacing-style is applied to .hw-card__inner (not .hw-card__content) so the padding-top ratio base is the full card width, not the card minus merchant padding. When --has-ratio is on, .hw-card__inner uses position: absolute; inset: 0 so flex content sits inside the ratio box (flex + in-flow ratio pseudo cannot co-exist).

    Content above link - .hw-card__content paints at --layer-raised (above the block link) with pointer-events: none cascade + a, button, input, textarea, select { pointer-events: auto }. Interactive children inside the card receive clicks; non-interactive areas fall through to the block link. Addresses Gemini high-priority review comment on earlier revision.

    Registered in _hw-carousel-grid-content and sections/hw-carousel-grid.liquid (both schema and default preset). Presets: HW Card, HW Card with video (150% ratio + hw-video child, corner play + overlay), HW Card with image (120% ratio + overlay, bottom-aligned title/description).

    Docs: .agent/docs/blocks/hw-card/DEVELOPER_GUIDE.md, EDITOR_GUIDE.md.

  • HW Video block (`hw-video`) and shared video snippet: Fork of base video block / video.liquid adding custom play icon, flexible positioning, and gradient overlay wiring. Renders a <deferred-media> for uploaded Shopify videos or a YouTube/Vimeo <iframe> for external URLs.

    Custom play icon - image_picker fallback, play_icon_size range 24-120 px, 9-position grid (play_icon_vertical × play_icon_horizontal). Corner positions inset max(12px, var(--padding-sm, 0.75rem)); centre positions use true-centre transforms. Icon size driven by --hw-play-icon-size custom property.

    Gradient overlay - toggle_overlay + color_background overlay_color, layered between poster image and play icon (poster mode), above the video element (autoplay mode), or above the placeholder (no video yet). All three stacks defined in assets/hw-video.css (registered via snippets/hw-stylesheets.liquid).

    Shared with hw-card - hw-card background video routes through snippets/hw-video.liquid with video_class: 'hw-card__bg-video ...'. CSS overrides make the deferred-media element, poster button, poster image, and <video>/<iframe> all fill the card via position: absolute; inset: 0; object-fit: cover.

    External URL query params - autoplay/mute/loop/controls params are inlined into YouTube (&mute=1&loop=1&playlist={id}&controls=0&enablejsapi=1) and Vimeo (&muted=1&loop=1&controls=0&api=1) src URLs as appropriate.

    Presets: HW Video (defaults), HW Video (corner play) (bottom-right + overlay on).

    Docs: .agent/docs/blocks/hw-video/DEVELOPER_GUIDE.md, EDITOR_GUIDE.md.

  • Custom font support (Typography): Merchants can now use fonts outside the Shopify font library.

    New Use custom font checkbox in Theme settings → Typography.

    When enabled: four text inputs for custom font-family names (body, subheading, heading, accent); Shopify font pickers remain in schema but are hidden.

    New metaobject_list setting (custom_fonts, type custom_font) - pick one or more custom_font metaobject entries. Each entry defines font_family_name, source_files (list of file references - woff2, woff, ttf, otf), font_weight, font_style, font_display.

    New snippets/hw-custom-fonts.liquid: iterates the metaobject list, auto-detects format from file extension, outputs one @font-face block per entry with all source_files combined into a single src: line.

    snippets/fonts.liquid: Shopify font preload <link> tags are suppressed per slot when a custom font name is active.

    snippets/theme-styles-variables.liquid: --font-body--family, --font-subheading--family, --font-heading--family, --font-accent--family use the custom font name when set, falling back to type_*_font.fallback_families (no hardcoded sans-serif).

    Setup guide in .agent/docs/theme-settings/typography/EDITOR_GUIDE.md.

  • Typography preset enhancements: All preset blocks (Label, Paragraph, Paragraph 2-6, H1-H6, Link) now have a full and consistent set of controls.

    Added type_font_*, type_weight_*, type_case_* settings to Label, Paragraph, and Paragraph 2-6 (previously only headings had these).

    Font select: Body / Subheading / Heading / Accent (4 options on all presets).

    Font weight select: Default / 100-900.

    Text case select: Default / Uppercase / Capitalize / Lowercase (expanded from Uppercase-only on headings and Link).

    Unified setting order across every preset: Font → Size → Size (desktop) → Line height → Letter spacing → Weight → Case.

    theme-styles-variables.liquid preset loop unified - single branch handles all preset types; default: 'body' fallback for backward compatibility.

  • Text (HW) - color scheme override: Colors group with Inherit color scheme (default on), same pattern as Group (HW). When inherit is off, Color scheme sets which palette supplies --color-foreground / heading / link for the block (including the text / heading / link tone control). Markup wraps in color-{{ scheme }} via snippets/hw-text.liquid; assets/hw-text.css uses display: contents on .hw-text-block__color-scheme so flex/grid sizing matches an unwrapped block.

  • HW Styleguide section (`hw-styleguide`) and `page.styleguide` template: Visual audit page for typography presets, buttons, colour schemes, spacing, and related tokens. Section uses `@theme` blocks only ({% content_for 'blocks' %}) so the theme editor owns all variants without editing the section Liquid.

    `sections/hw-styleguide.liquid` - Width, page width preset, colour scheme, optional section padding override, top/bottom dividers, and mobile/desktop gap between blocks (CSS variables --hw-styleguide-gap-*). Preset HW Styleguide (Custom category).

    `assets/hw-styleguide.css` - Column layout and responsive gap between top-level block wrappers; registered in snippets/hw-stylesheets.liquid.

    `templates/page.styleguide.json` - Assign to a Shopify page to render main-page (title) plus the styleguide section with a large default block tree; per-block padding/corner noise trimmed so the JSON stays maintainable.

  • Desktop mega menu block-composable panel system (hw-mega-menu-panel)

  • Five child block types: link column, product cards, collection cards, promo card, image card

  • Per-dropdown customisation - each top-level nav item can have its own panel layout

  • Locale-safe panel targeting via link.handle (not translated title)

  • Bottom bar with CTA button and trust text support

  • assets/hw-mega-menu.css for all mega menu styles

  • Fallback to standard mega-menu-list rendering for nav items without a custom panel

  • HW Quick-Add Modal (`hw-quick-add-modal`): Zero-fetch quick-add modal for the hw-product-card block. Modal content is server-rendered into a <template data-hw-quick-add-template> inside each product card; hw-quick-add.js clones the fragment into the dialog on click - no network request on open.

    Variant picker (`hw-modal-variant-picker`): Custom element extending Horizon's VariantPicker; overrides fetchUpdatedSection so no network request is made on option change. All variant data is embedded at render time via <script data-hw-qam-variants>. On selection change: updates per-combination option availability (CSS fade for OOS), keeps swatch legend labels in sync, resolves the matching variant from embedded JSON, builds a minimal fake Document for product-price.js to morph the price display from, dispatches VariantUpdateEvent so product-form-component updates the hidden variant ID and enables/disables the ATC button, and updates button text (Add to cart / Sold out / Unavailable) directly via data-attribute-driven translation strings.

    OOS styling: No strikethrough SVG - OOS size tiles fade via opacity / background; OOS color swatches fade via opacity: 0.7 on .swatch. Cart icon is hidden via CSS button:disabled .add-to-cart-icon { display: none } when the selected variant is unavailable.

    Layout: .buy-buttons-block is a direct flex child of .product-details (not nested inside .group-block-content), matching the sticky-button layout the native quick-add-modal.liquid CSS expects - flex: 0 0 auto; margin-top: auto; position: sticky; bottom: 0. Explicit padding-inline: var(--padding-3xl) compensates for the missing :where(.group-block-content > *) padding.

    Files: assets/hw-quick-add.js, assets/hw-modal-variant-picker.js, assets/hw-variant-picker.css, snippets/hw-quick-add.liquid, snippets/hw-quick-add-modal-content.liquid, snippets/hw-quick-add-variant-picker.liquid, snippets/hw-quick-add-buy-buttons.liquid, snippets/hw-quick-add-wrapper.liquid.

  • Docs: Mobile menu drawer (HW) - block tree, split mode, rest-links markup, custom accordion/drawer SVG wiring, Liquid pitfalls (render param shadowing). Linked from header-navigation README and DEVELOPER_GUIDE.

  • Cart upsells card styles (`hw-cart-upsells`): Two visual styles for cart upsell product cards.

    Style 1 (default): Vertical card layout with image above title/price.

    Style 2: Horizontal layout with configurable columns, custom background options (color scheme inheritance or per-product metafield background colors), and enhanced typography controls.

    Card background options: Inherit block color scheme or specify custom color scheme; optional metafield-driven background colors per product (e.g., custom.card_background_color).

    Button customization: Custom ATC button labels, icon toggle, and Quick Add behavior (inherit/theme/show/hide).

    Layout improvements: Gap controls, configurable card width (slider mode), column overrides (grid mode), and responsive padding.

    Files: Updated blocks/hw-cart-upsells.liquid, snippets/hw-cart-upsells.liquid, assets/hw-cart-upsells.css, assets/hw-cart-upsells.js; documentation in .agent/docs/blocks/hw-cart-upsells/ (DEVELOPER_GUIDE.md, EDITOR_GUIDE.md).

  • Product media area (HW): Composable blocks for the HW product media gallery and related PDP layout.

    `hw-media-hotspots` (blocks/hw-media-hotspots.liquid, blocks/_hw-media-hotspot.liquid) - Template-level hotspots on the first visible gallery image; child blocks for position and popup copy/link. Pin: mobile size, optional desktop size override, color, pulse. Popup: color scheme (content only; overlay stays transparent), corner radius, shadow blur (px), typography presets, padding with optional desktop override. Vanilla JS (hw-media-hotspot-component); not shown in zoom (positioning unreliable).

    `hw-media-usps` - Loop-style USPs from a product metafield (list of metaobject references), rendered via `_hw-media-usp-item` children and shared group/layout patterns.

    `hw-product-info-items` - Replaces `hw-model-stats`: product metafield (list of `product_info_item` metaobject references), `_hw-product-info-item` children, parent-level card styling (scheme, padding, margin, radius). Placeable from the HW gallery or HW product details.

    `_hw-product-media-gallery` - Sidebar rail opens for USPs and model info (not hotspot-only). New settings: sidebar position (left/right), max width, gap between sidebar and main image (optional desktop gap override), vertical alignment (top/center). Help text clarifies hotspots do not consume sidebar width. Watch video: optional overlay badge (theme Badges typography/position/padding + block color scheme + optional custom label); click uses scrollIntoView, slideshow.select when available, then poster button click so deferred video actually plays; video detection treats slideshow-slide.product-media-container--video as the media root. `__gallery-media` uses `overflow: hidden` to stop hotspot pins bleeding into the sidebar; hotspots JS prefers `aria-hidden="false"` slide for overlay alignment.

    Docs - .agent/docs/blocks/hw-product-media-gallery/HOTSPOTS_TEMPLATE_PLAN.md. Theme editor settings guidance updated in .cursor/skills/theme-editor-settings-planning/SKILL.md (loop parent + child pattern).

  • Product card block system (`hw-product-card`): Composable, block-based product card (blocks/hw-product-card.liquid) that accepts child blocks for full merchant control of card layout. Renders via snippets/hw-product-card.liquid. Each preset ships with a curated default block stack; merchants can add, remove, and reorder blocks freely in the theme editor.

    Child blocks supported - hw-text, text, image, hw-price, hw-product-title, hw-swatches, hw-size-picker, hw-add-to-cart, hw-view-product, hw-card-popup, hw-icon-text, hw-card-divider, hw-inventory-alert, hw-price-calculation, hw-badge, custom-liquid, @app, and grouped via _product-card-group.

    Gallery block (`hw-product-card-gallery`) - image carousel/slider with per-block child blocks (badges, overlaid content), secondary image hover, aspect ratio, border radius, and click-through to PDP via snippets/hw-page-url-variant.liquid.

    Quick add - snippets/hw-quick-add-wrapper.liquid wraps the base quick-add with HW visibility (always/hover/never), style (icon-label/icon-only/label-only), and size overrides per gallery block setting. snippets/hw-quick-add-size-picker.liquid adds size-picker variant pre-selection before adding to cart. snippets/hw-quick-add-popup.liquid renders an inline <details>/<summary> info popup for gallery overlays. Styles in assets/hw-quick-add.css.

    Files - blocks/hw-product-card.liquid, blocks/hw-product-card-gallery.liquid, snippets/hw-product-card.liquid, snippets/hw-quick-add-wrapper.liquid, snippets/hw-quick-add-size-picker.liquid, snippets/hw-quick-add-popup.liquid, snippets/hw-page-url-variant.liquid, assets/hw-product-card.css, assets/hw-quick-add.css.

  • Product list section (`hw-product-list`): sections/hw-product-list.liquid - a dedicated section for rendering a list of products using the hw-product-card block system. Supports section-level layout settings (width, color scheme, spacing, gap) with header blocks via _product-list-content.

  • Size picker block (`hw-size-picker`): blocks/hw-size-picker.liquid - renders clickable size tiles for the variant option whose name matches the theme setting hw_size_option_names. Coordinates with hw-swatches for multi-option variant selection. Tile click selects the variant (updates price/image) without adding to cart or changing the URL. Configurable padding per block. Renders via snippets/hw-size-picker.liquid; JS in assets/hw-size-picker.js; styles in assets/hw-size-picker-block.css.

  • View product block (`hw-view-product`): blocks/hw-view-product.liquid - renders a "View product" link or button on the product card. Configurable label, button style, icon (theme/custom image/custom SVG), icon position, button height (mobile + optional desktop override), and full-width toggle. Renders via snippets/hw-view-product.liquid; styles in assets/hw-view-product.css.

  • Card divider block (`hw-card-divider`): blocks/hw-card-divider.liquid - horizontal rule between card blocks. Style (solid/dashed/dotted), color scheme, top/bottom spacing, full-width toggle, and line thickness. Renders via snippets/hw-card-divider.liquid; styles in assets/hw-card-divider.css.

  • Icon-text block (`hw-icon-text`): blocks/hw-icon-text.liquid - icon paired with text label on product cards. Icon source (theme icon / custom image / custom SVG), icon width (mobile + optional desktop override), stroke width, text content, typography preset, icon position (left/right/above), and alignment. Renders via snippets/hw-icon-text.liquid + snippets/hw-icon-text-icon.liquid; styles in assets/hw-icon-text.css.

  • Inventory alert block (`hw-inventory-alert`): blocks/hw-inventory-alert.liquid - shows a stock-scarcity alert when inventory is at or below a configurable threshold (1-50 units). Optional progress bar with a per-block color scheme override. Renders via snippets/hw-inventory-alert.liquid; styles in assets/hw-inventory-alert.css.

  • Price calculation block (`hw-price-calculation`): blocks/hw-price-calculation.liquid - displays a derived price or unit-cost calculation sourced from a product metafield (namespace.key). Optional label, show/hide toggle. Renders via snippets/hw-price-calculation.liquid; styles in assets/hw-price-calculation.css.

  • Card popup block (`hw-card-popup`): blocks/hw-card-popup.liquid - per-block info popup for product cards. Trigger styles: button, plain text link, text with arrow, pill with icon. Popup content: title, image (metafield-aware), richtext body, color scheme. Data stored inert in a <template> element; shared <dialog id="hw-card-popup-dialog"> rendered once in layout/theme.liquid via snippets/hw-card-popup-modal.liquid and populated by assets/hw-card-popup.js. Trigger rendered via snippets/hw-card-popup-trigger.liquid (includes aria-haspopup="dialog"). Styles in assets/hw-card-popup.css. Renamed from the original hw-popup block name to avoid collision with the returning visitor hw-popup section.

  • Add to cart block (`hw-add-to-cart`): Block-level enhancements - icon toggle (show/hide), custom icon option, and custom label override for available products on product cards. Styles in assets/hw-add-to-cart.css. Renders via snippets/hw-add-to-cart-button.liquid.

  • Returning visitor popup (`hw-popup`): Section group (sections/popup-group.json, type: custom.popup) that renders a compact dialog for returning visitors. Only outputs HTML when cart.item_count > 0 and the current template is in the enabled pages list - fully Liquid-first. Trigger logic (time delay, scroll depth), session suppression (sessionStorage), and min-gap enforcement (localStorage) are handled client-side in assets/hw-popup.js.

    Cart line items - image (links to PDP), title (anchor to PDP), variant, price, optional product metafield, configurable typography presets per field. Scrollable list with configurable max height. Checkout button and View cart link.

    Recently viewed (empty cart) - when the cart is empty and the setting is on, fetches recently viewed products via Section Rendering API (predictive-search section) and injects them. Hides the modal entirely if no recently viewed products exist and "hide when no content" is on.

    Settings - page targeting (home, product, collection, blog, article, search, page), time delay trigger (on/off + seconds), scroll depth trigger (on/off + %), min-gap between shows, popup width (mobile + optional desktop override), max height (mobile + optional desktop override), position (mobile: top/center/bottom; desktop: top/center/bottom/left/right), corner radius, inner gap, padding (mobile + optional desktop override), backdrop toggle, color scheme, editor preview mode (always-show for theme editor editing).

    Files - sections/hw-popup.liquid, sections/popup-group.json, snippets/hw-popup.liquid, snippets/hw-popup-cart.liquid, snippets/hw-popup-recently-viewed.liquid, assets/hw-popup.css, assets/hw-popup.js. CSS and JS loaded via layout/theme.liquid conditionally.

Changed

  • v1.0.0 flagship release: Theme version set to 1.0.0. Default templates and group JSON cleaned for a blank-store install (store-specific product handles cleared from header showcase and cart gift slots; demo templates/groups rebuilt as the Horizon Works preset).

  • Tab (HW) — panel action blocks: _hw-tab no longer uses inline panel CTA settings (link, label, style, width). Add Button (HW) or Button modal (HW) as a child block; output renders in .hw-tab__cta below the tab body. New Tab preset includes a Read more modal. Docs: `.agent/docs/blocks/hw-tabbed-content/`.

  • Inset (HW) — margin: `blocks/hw-inset.liquid` adds mobile/desktop Margin sliders (−50–50 px per side) and Override on desktop (override_margin_desktop). `snippets/hw-inset-margin-style.liquid` emits **--margin-* / --margin-*-desktop; assets/hw-inset.css applies logical margins with a 750px desktop override (same pattern as padding via hw-spacing-style). Locale: t:content.margin_desktop, t:settings.override_margin_desktop_info. Docs: .agent/docs/blocks/hw-inset/EDITOR_GUIDE.md, DEVELOPER_GUIDE.md**.

  • Text (HW) snippet — alignment + override hooks: `snippets/hw-text.liquid` accepts `text_override` (server-side richtext preprocessing) and `force_alignment` (used by Text (HW, align)). Mobile alignment classes and `--text-align-mobile` CSS var when `alignment_mobile` is set.

  • Badges — position: Theme settings → Badges → Badge position adds Bottom right (bottom-right) alongside existing corners.

  • HW Header 2 — search style: `blocks/_hw-header-actions-2.liquid` adds `search_style` (Standard vs Horizon Works, default Standard). `snippets/hw-header-utilities.liquid` keeps `#search-modal/showDialog` for Standard or switches the search utility to `data-hw-search-open` / `aria-controls="hw-search-panel"` when Horizon Works is selected. Non-breaking opt-in — merchants need `search_style: horizon_works` on header actions and `hw-search-panel` in the header group for the new UX.

  • Cart Upsells — button & heading typography: button_preset setting renamed to button_type_preset; now supports custom typography preset with inline style bridge. Heading preset extended with custom typography option. Button class composition moved to Liquid (cu_atc_btn_class) and passed as a single pre-composed string to JS via data-cu-button-class. data-cu-button-style now carries an inline style string for custom typography (previously carried the button style class — semantic change). Color scheme override added per-block.

  • Cart Upsells — checkout button controls: New settings for accelerated checkout toggle, button label override, icon (lock / custom SVG), style class, color scheme, and typography preset with custom option.

  • Cart Upsells — free shipping bar: Amount size and label margin bottom are now configurable. Per-element custom typography supported on the amount span.

  • Cart Drawer — overflow: Replaced overflow-x: clip with overflow-x: visible on upsells scroll containers to allow slider horizontal bleed. Compact layout footer top-rule implemented via ::before bleed instead of a border on .cart-drawer__summary.

  • Card Icon Text — text preset options: Extended from paragraph / paragraph_2 / label to include all heading levels (h1–h6), additional paragraph scales, and a custom typography option. typography_settings param added. Version bumped to 1.1.0.

  • Button (HW) — presets: Removed nine theme-editor presets (Button (Primary / 1–3), Secondary, Tertiary variants). Add block now offers only Button (HW) and Button with icon — style and preset are chosen in block settings instead.

  • Button (HW) vs modal: Removed Opens modal action and nested modal child from `hw-button`. Modals use the dedicated `hw-button-modal` block (see Added). `hw-button` keeps URL / cart / checkout only. Hidden `modal_trigger` setting remains for the static trigger child inside `hw-button-modal`.

  • PDP buy area — measurement controls: Gallery spacing (grid Gap, sidebar Gap, overlay corner inset, block padding) uses range sliders with step 1 and expanded max values where needed (for example overlay inset 0–100px). Theme Page layout custom width overrides and HW Product information desktop column caps stay text inputs resolved via `hw-resolve-css-length.liquid`. See `.cursor/rules/hw-dimension-inputs.mdc` and `.cursor/references/hw-layout-settings-patterns.md`.

  • HW Group — desktop horizontal row wrapping (opt-in): From 750px, `flex-flow` reads `--hw-group-flex-wrap-desktop` (snippets/hw-layout-panel-style.liquid, `assets/hw-group-layout.css`) instead of assuming `nowrap` only implicitly. Default remains non-destructive: `nowrap` (same as historic CSS). Merchant opt-in `wrap-horizontal-row-large-screens` (checkbox on `hw-group` / `hw-group-basic` / `hw-group-card` / `hw-group-link`, `content_direction_desktop == 'row'`) sets `flex-wrap: wrap` so siblings can flow to the next line. Locale `t:settings.wrap-horizontal-row-large-screens`, `t:info.wrap-horizontal-row-large-screens`.

  • HW Marquee: Removed `sections/hw-marquee.liquid`; marquee is block-only (blocks/hw-marquee.liquid). Homepage `templates/index.json` now uses `hw-section-starter` wrapping an `hw-marquee` block (same nested content and dividers on the starter where applicable). `assets/hw-marquee.css`, `assets/hw-marquee.js`, `blocks/hw-marquee-overlay.liquid`, and schema locale keys for the block/overlay stay in use.

  • HW Carousel progress bar + nav (row layout): In `assets/hw-carousel-progress-bar.css`, the progress bar Shopify block wrapper uses `flex: 1 1 0%` and `min-width: 0` (not `width: 100%` + `flex: 1 1 auto`), and the carousel navigation wrapper’s block uses `flex: 0 0 auto`, so a control row stays on one line under Keep horizontal row on one line without clipping arrows off-screen.

  • HW Cart Upsells — slider arrow icons: `nav_icon_size` is replaced by `nav-icon-width` and `nav-icon-height` (`blocks/hw-cart-upsells.liquid`) with the same 8–64px ranges and carousel-nav locale labels (`t:settings.hw_carousel_nav_arrow_icon_width` / `_height`). `snippets/hw-cart-upsells-nav.liquid` sizes the hit target from both dimensions; `sections/cart-group.json` updated. Saved JSON that still has `nav_icon_size` is read as a fallback until re-saved.

  • Cart drawer header — cart title typography: `blocks/hw-cart-drawer-header.liquid` adds Cart title size (paragraph–h6, label, custom) via `hw-type-preset-classes` / `hw-type-preset-style`. Empty-cart header mirrors the same controls on `sections/hw-cart-drawer.liquid` (empty_header_heading_*). Styles in `assets/hw-cart-drawer.css` on `cart-drawer-component` (drawer is outside .header). Item count bubble size/font remain separate Cart bubble sliders.

  • Cart drawer footer — row typography: `snippets/hw-cart-summary.liquid` applies `row_heading_type_preset` to shipping/subtotal labels and `row_value_type_preset` to calculated-at-checkout copy and money values. `assets/hw-cart-summary.css` adds leaf-preset and custom-typography rules so all four nodes respect the two presets (not legacy cart font-size fallbacks).

  • Loop sections JS deduplication: Removed duplicated inline {% javascript %} carousel wiring from `sections/hw-loop.liquid`, `sections/hw-loop-review.liquid`, and `sections/hw-loop-ugc.liquid`. All now rely on the shared theme-level script (hw-loop-carousel-wire.js).

  • UGC data-source editor clarity: Updated `hw-loop-ugc` source field copy to clarify that Source list field expects the field key on the selected group entry (e.g. entries) and is not the metaobject type name (ugc_video); default updated to entries.

  • HW Loop — FAQ (sections/hw-loop-faq.liquid): Metaobject mode targets a `faq_group` entry and always reads list handle `faqs` (HW convention). Removed `source_list_field` and the old `faq` picker in favour of a FAQ Group entry picker; empty-state messaging updated (`locales/en.default.schema.json` copy).

  • `snippets/hw-accordion.liquid`: `accordion_fragment` — `full` (default), `open`, or `close` for split shell markup; `accordion_block_settings` only (never a render param named `settings`, which would shadow global theme `settings`).

  • `snippets/spacing-style.liquid`: Optional `fallback_settings` plus kebab/underscore padding keys so merged accordion settings emit valid **--padding-*** variables.

  • `snippets/border-override.liquid`: Final defaults when width, style, opacity, or radius stay blank after merge (avoids broken `--border-width: px` / empty style output).

  • `hw-marquee` (later block-only) + `blocks/hw-marquee-overlay.liquid` + `assets/hw-marquee.css`: Added section divider support and split-overlay divider controls for HW Marquee (section file later removed in favour of `blocks/hw-marquee.liquid` inside `hw-section-starter`).

    Section-level: divider_top, divider_bottom, and divider_color_scheme (matching HW Carousel / Grid pattern). When enabled, top/bottom lines render from the selected scheme's Borders token (--color-border) while marquee content keeps the main section scheme.

    Overlay-level (on hw-marquee-overlay): optional vertical divider between static overlay and scrolling track with independent overlay_divider_color_scheme and overlay_divider_margin_inline_end controls. Split layout renders the .marquee__static-overlay wrapper from the block so editor settings apply reliably; vertical divider uses the selected scheme's Borders token.

  • `blocks/_hw-carousel-grid-content.liquid`: Allowed `@theme` in the nested blocks array so merchants can add theme blocks (e.g. Tabbed content (HW)) inside HW Carousel / Grid content. `hw-card` remains in the allowed blocks array alongside `_card` so HW Card can nest without invalidating section presets.

  • `sections/hw-carousel-grid.liquid`: Registered hw-card at section level. Default preset's four demo cards switched from _card (aspect_ratio: adapt) to hw-card (aspect_ratio: auto).

  • `assets/base.css` (base file, flagged for upstream): Narrowed the deferred-media[class] :is(.deferred-media__poster-button img, .deferred-media__poster-button ~ video) rule to target .deferred-media__poster-button .deferred-media__poster-image instead of any <img> inside the poster button. Previously this rule forced object-fit: cover; height: 100%; aspect-ratio: var(--size-style-aspect-ratio, auto) onto custom play-icon <img> tags rendered inside .deferred-media__poster-button by hw-video, stretching them. Poster images still get the cover treatment; custom icons now size freely via --hw-play-icon-size.

  • `snippets/media.liquid` (base file, flagged for upstream): Same scoped tweak as base.css - .media-block__media deferred-media rule targets .deferred-media__poster-image only, not any <img> descendant of the poster button.

  • `locales/en.default.schema.json`: Added settings.video_mute ("Mute") and info.video_mute (explains mute applies after play in click-to-play cards; browsers may still restrict audio).

  • Group (HW) (blocks/hw-group.liquid): Layout (small screens) vs Layout (desktop); desktop Direction is only Vertical or Horizontal (removed "Same as mobile"). Removed Vertical on mobile; legacy vertical_on_mobile / content_direction_desktop: match_mobile still interpreted in hw-group-effective-directions.liquid until JSON is cleaned. Presets updated (e.g. HW Reviews Card, Materials slider).

  • Text (HW) (blocks/hw-text.liquid, snippets/hw-text.liquid): Width controls use fill / fit / custom px and snippets/hw-size-style.liquid; removed max_width setting; layout CSS moved to hw-text.css where noted above.

  • Button (HW) (blocks/hw-button.liquid, snippets/hw-button.liquid): Custom width uses custom_width_px / custom_width_mobile_px; Full width applies only when both widths are Fill and is hidden in the editor otherwise; hw-button--full-width no longer overrides Fit content. Locale copy in locales/en.default.schema.json.

  • `snippets/size-style.liquid`: Emit --size-style-height only when settings.height is not blank (avoids invalid empty custom properties).

  • `assets/base.css` (`.size-style`): Fallbacks fit-content / auto when width or height variables are unset so blocks without height still layout correctly.

  • `templates/index.json`: hw-group blocks - removed vertical_on_mobile, replaced match_mobile desktop direction with explicit column/row where migrated.

  • Mega menu (HW) content: Removed page overlay settings and related CSS vars (overlay was not working reliably). Added a soft bottom box-shadow on desktop mega .menu-list__submenu[data-mega-target] > .menu-list__submenu-inner so the panel separates slightly from same-colored page content. (blocks/hw-mega-menu-content.liquid, assets/hw-mega-menu.css, locales/en.default.schema.json)

  • `sections/hw-loop-review.liquid`: Refactored source mode from metaobject_entry_list to metaobject_list.

    Old: merchants picked a single reviews metaobject entry, then provided a list field handle (e.g. reviews) that contained references to individual review entries.

    New: merchants use a metaobject_list setting scoped to metaobject_type: "review" and pick the review entries directly in the theme editor. This removes the intermediate "container" metaobject and the need for editors to know field handles.

    Presets updated: Reviews Default Loop and Trustpilot reviews now default to metaobject_list mode with simplified settings (redundant defaults removed). Trustpilot reviews preset rebuilt with a full nested block tree: header heading, review card with rating image + body + author portrait + product title, and footer with carousel progress bar + navigation.

    New locale keys in locales/en.default.schema.json: hw_loop_review_block_* names for preset blocks (author, author details, review text, footer row, review card, product, rating image, top content), plus preset names (hw_loop_review_preset_default, hw_loop_review_preset_compact, hw_loop_review_preset_trustpilot).

  • `assets/hw-carousel-progress-bar.css` + `blocks/_hw-carousel-progress-bar.liquid`: Progress bar wrapper now uses flex display (was block) with cross-axis centering (align-self: center, flex-direction: column, justify-content: center). Fixes misalignment when the progress bar sits in a row with carousel navigation arrows.

  • Loop sections - removed hardcoded progress bar overrides: sections/hw-loop.liquid, sections/hw-loop-faq.liquid, sections/hw-loop-ingredient.liquid, sections/hw-loop-material.liquid, and sections/hw-loop-review.liquid all dropped their local #shopify-section-{{ section.id }} .hw-carousel-progress-bar-wrapper overrides (which previously forced display: block; min-height: 12px). Progress bar sizing and visibility are now fully governed by the shared assets/hw-carousel-progress-bar.css asset and the block-level CSS custom properties.

  • Docs: Updated .agent/docs/blocks/loop/DEVELOPER_GUIDE.md, EDITOR_GUIDE.md, and FINDINGS.md to document the metaobject_list source mode and current preset structures.

  • Template JSON: Removed Liquid dynamic sources from templates/*.json (metafields, closest.*, etc.). Replaced with static placeholders so shopify theme check and theme push validate; merchants reconnect dynamic sources in the editor. Added scripts/check-template-json-no-liquid.sh, npm run check:templates, and GitHub Action Template JSON (no Liquid) on main / PRs.

  • Loop (HW): Consolidated generic section to a single preset Loop (HW); removed type-specific presets (Ingredients, Materials, Reviews, FAQs) from hw-loop.liquid. Type-specific sections (hw-loop-ingredient, hw-loop-material, hw-loop-faq, hw-loop-review) now have schema parity with the generic section and renamed presets: Ingredients Default Loop, FAQs Default Loop, Reviews Default Loop; Materials slider (matched to product.json); Trustpilot reviews (matched to index.json). Presets that mirror templates use a full copy of settings and blocks. Updated .agent/docs/blocks/loop/FINDINGS.md with current section list, preset names, and merchant quick reference.

  • Variant picker: All variant-picker OOS and HW-specific styling moved from base.css into assets/hw-variant-picker.css (scoped to .hw-variant-picker). Base variant-main-picker.liquid no longer references removed "Show Unavailable" setting.

  • Theme settings: Removed "Show Unavailable" label checkbox; removed from schema and settings_data.json per schema/merge safety.

  • HW product siblings: Removed overflow-list / "more" behaviour; list now renders all siblings up to block max_items in a single list.

  • Button (HW): Consolidated icon rendering into a single reusable block; added scoped styles for icon sizing, desktop width override, and SVG effects (opacity, grayscale). SVG stroke/fill styles inherit currentColor via color scheme.

  • Base CSS: SVG stroke-width and vector-effect rules for icon defaults.

  • Badges: Renamed "metafield badge" to "custom badge" in settings and UI.

  • Badges: Typography preset and case now apply to Sale, Sold out, and custom badges (not just custom).

  • Badges: Extracted badge and card-gallery styles to static CSS assets (per project rules) instead of Liquid snippets.

  • Pricing (HW): Added HW duplicate pricing chain (hw-format-price, hw-price, _hw-featured-product-price) with scoped currency-code controls and trailing-zero formatting, while keeping base Horizon blocks/snippets aligned with upstream.

  • Pricing (HW): Added configurable savings label behavior (enable, prefix, value type, radius) and moved savings badge colors into global color scheme settings for HW price rendering.

  • Typography: Font sizes changed from select to range sliders (0-100px with element-specific min/max).

  • Typography: Fluid scaling fix when desktop size < mobile size (clamp bounds corrected).

  • Config: settings_data.json migrated for range/select schema compatibility.

  • Docs: Reorganised .agent/docs/ for clearer structure: theme-level docs under theme-settings/ (color-schemes, typography, buttons, cursor) with dev + editor guides together; section/block docs under cart/ (cart-drawer, empty-cart) and blocks/ (accordion, price, loop, popup, social-links); architecture and process under architecture/; dev-only under dev-tools/; design-branch-sync under workflows/; planning docs in _archive/. README is the index; internal links and references (CONTRIBUTING, root README, CHANGELOG, PR template, workflow) updated to new paths.

Fixed

  • Price typography presets — global price colour on “From” prefix: `snippets/theme-styles-variables.liquid` no longer sets color on bare .price (which matched .hw-price__from-line). Targets .price:not(.hw-price__from-line) and .hw-price__from-amount instead so From label typography stays independent. Colour rules always emit with | default: 'var(--color-foreground)' when theme pickers are blank. Preset font-size selector updated the same way.

  • Cart free shipping & gifts — custom message typography: Missing inline style on the locked FSB label when `{amount}` is split; component CSS in `assets/hw-cart-free-shipping-gifts.css` for banner, pill, and amount nodes so custom font sizes apply.

  • Cart drawer header — title and bubble layout: `text-block` on `.cart-drawer__heading` stacked the item count under Cart; `assets/hw-cart-drawer.css` restores `inline-flex` row layout (and empty-cart header in `assets/hw-empty-cart.css`).

  • Cart upsells — ATC min-height: Removed extra `padding-block` on `.hw-cart-upsells__atc-btn` / options links so the button min-height override controls height.

  • Sticky buy CTA (HW) — visibility performance: `assets/hw-sticky-buy-cta.js` drops `window` scroll/resize listeners and per-tick `getBoundingClientRect()` on the buy target. Buy-area and footer state are cached from `IntersectionObserver` callbacks; `#syncVisibility` reads only that state (footer `rootMargin` uses `FOOTER_HIDE_VH`).

  • Cart prices — hide trailing zeros with currency: snippets/hw-cart-item-price.liquid no longer uses remove: '.00' / remove: ',00' on the full formatted string (which could turn $5,000.00 into $50). Strips only decimal .00 / ,00 before the currency code.

  • Cart drawer compact line items: Divider borders now target `.cart-items--dividers` on the table (not the wrapper). Compact row padding-bottom no longer inherits `header-actions` gap-xl padding. Thumbnail size respects theme editor on desktop (mobile/desktop CSS vars, not a hardcoded inline --hw-cart-item-thumb-size). `item_layout` is no longer forced to compact when section `drawer_layout` is compact.

  • Sticky buy CTA (HW) — z-index under carousel/grid: Block moved to Product information (HW) section level (sections/hw-product-information.liquid + `templates/product.json`); removed from `_hw-product-details` children. Fixes carousel/grid overlap without JS `document.body` portal.

  • Sticky buy CTA (HW) — Badge height: Badge height range (20–80px, default 28) under Badge appearance.

  • Sticky buy CTA (HW) — CTA labels: Separate CTA label (subscription) and CTA label (standard product); Liquid picks by product.selling_plan_groups (same signal as subscription pricing).

  • Sticky buy CTA (HW) — “From” price: hw-price only rendered “From” on non-product templates (is_product_card). PDP sticky bar now passes `price_from_variant_prices`, `from_label_type_preset`, and optional **from_label_* custom typography; hw-price.liquid shows “From” on product pages when the effective price range differs (variants and/or selling plan allocations for subscriptions). Replaces the old show_price_from checkbox with the same control model as Price (HW)**.

  • HW Product media gallery — media corner radius: `media_radius` now clips carousel and grid media via `assets/hw-product-media-gallery-content.css` on `--media-radius` (base `product-media` stylesheet alone did not apply radius on the PDP gallery).

  • HW Group — desktop row wrap + intrinsic-width children: Three related issues, fixed together.

    Wrap setting ignored. Checkbox `wrap-horizontal-row-large-screens` is a hyphenated schema id — Liquid must read `settings['wrap-horizontal-row-large-screens']` (fallback `settings.wrap_horizontal_row_large_screens` for legacy JSON). `snippets/hw-layout-panel-style.liquid` + `snippets/hw-group.liquid` updated; the panel emits `--hw-group-flex-wrap-desktop: wrap` and `data-hw-desktop-flex-wrap="wrap"` / `data-hw-mobile-flex-wrap="wrap"` when the merchant enables wrap (content_direction(_desktop) is row).

    Content / Fit row children crushed. `base.css` used `flex: 0` on `.layout-panel-flex--row > .group-block--width-{fit,content,fit-content}` — invalid shorthand → browsers fell back to `flex: 0 1 auto` and flex items collapsed under **> * { min-width: 0 }. base.css now uses flex: none (matching real HW class names); assets/hw-group-layout.css also forces flex: none + min-width: auto on > .group-block--width-content / -fit-content row children. width: content emits max-content (not min(100%, max-content), which leaves descendants' % in min(100%, Npx) with an indefinite base) with max-width: 100% capped on .hw-group.group-block--width-content**.

    Wrap + fill children never wrapped. `base.css` gives `.layout-panel-flex--row > .group-block--width-fill` `flex: 1`; with `min-width: 0` from row children, fill items absorbed all spare space and prevented siblings from wrapping. When the panel has `data-hw-{desktop,mobile}-flex-wrap='wrap'`, fill children get `flex: none` + `max-width: 100%` + `min-width: auto` so the flex line breaks naturally on overflow.

  • HW Button — custom widths collapse inside intrinsic-width groups (desktop): `snippets/hw-size-style.liquid` custom width emits `width: min(100%, Npx)`. Inside an `hw-group` with `width: content` / `fit-content` (parent `max-content` / `fit-content`), the `100%` is indefinite during intrinsic sizing and falls back to `auto` (≈ label width). Buttons rendered well below their configured `Npx` (e.g. 260 → ~140), the inner group's `max-content` was undercounted, the outer row never saw an overflow → wrap did not trigger → CTAs overlapped sibling text. `hw-size-style.liquid` now also emits `--size-style-width-raw: Npx` / `--size-style-width-mobile-raw` for custom widths; `assets/hw-group-layout.css` uses the raw value on desktop `row` `> .hw-button-block-root` (with `flex-shrink: 0` + `min-width: auto`), so buttons hold their set widths. `.hw-button-block-root { max-width: 100% }` still caps in definite parents. Overflow in narrow rows is handled by `wrap-horizontal-row-large-screens` on the relevant `hw-group` panels — including nested CTA clusters when their own `max-content` still exceeds the available space (homepage stockist preset enables inner wrap too).

  • HW Carousel progress bar (track vs thumb rounding): Rounding now applies at `.hw-carousel-progress-bar` ( `border-radius` + `overflow: hidden` + `isolation: isolate` ), with `inherit` on `.hw-carousel-progress-bar__track` and `__thumb`, plus an inline `border-radius` from Liquid capped to half bar height — track and indicator stay visually aligned.

  • `sections/hw-featured-blog.liquid` + `assets/hw-featured-blog.css`: Decorative background — clip horizontal overflow while keeping vertical bleed; `pointer-events: none` on the image wrapper; elevate all direct content children above the bg layer so stacking does not depend on divider wrappers; placement classes default to `center`; `image_tag` adds empty `alt` and `sizes`; opacity control uses `t:settings.opacity`.

  • `snippets/hw-accordion-row.liquid`: Drawer close `aria-label` uses `accessibility.close_dialog` (existing locale key) instead of `accessibility.close` (missing string).

  • Text (HW) - "Fit" width in a group row: hw-text--fill-width was applied when either desktop or mobile width was fill, but assets/hw-text.css used it for desktop flex: 1 in row layouts. Blocks with desktop fit + mobile fill incorrectly grew on large viewports. Split into hw-text--fill-width (desktop fill) and hw-text--fill-width-mobile (small-screen fill, including inherited fill). (snippets/hw-text.liquid, assets/hw-text.css)

  • Theme editor / `theme watch` preset validation: Removed obsolete keys from presets - max_width on nested hw-text, vertical_on_mobile on hw-group, custom_width / custom_width_mobile on hw-button (Collections loop HW), and aligned Materials slider / Reviews / Hero / Product card (HW) preset JSON in Liquid templates with current schemas.

  • Menu (HW) - mega submenu gating: Top-level items only get a dropdown when there is fallback nav children or a populated HW mega panel (matching hw-mega-menu-panel with child blocks). (blocks/_hw-header-menu.liquid)

  • Mega menu link columns: Removed per-column menu picker and CTA; columns use the header menu linklist. (blocks/hw-mega-menu-link-column.liquid, snippets/hw-mega-menu-link-column.liquid, sections/header-group.json)

  • Mega menu (HW) content - typography: Heading/link presets use theme --font-size--* tokens (paragraph / paragraph 2-6); custom preset uses size token selects. Variables scoped to .hw-mega-menu-color-wrap. (blocks/hw-mega-menu-content.liquid)

  • `assets/hw-mega-menu.css`: Replaced invalid --font-size--small / --font-size--xsmall with --font-size--sm / --font-size--xs.

  • `blocks/_hw-header-menu.liquid`: elsif typo fix (featured collections vs featured products aspect ratio).

  • Liquid: Removed invalid menu_linklist: argument on {% content_for 'blocks' %} (Theme Check / Shopify parser).

  • Mobile drawer - Mobile menu row (HW): With Parent link handle set on hw-mobile-menu-drawer-content, 3+ level menus no longer use hw-header-drawer-accordion-mode only (that path did not render rich_branch_rows_markup). Rich rows + rest links use the main navigation branches. (snippets/hw-header-drawer-navigation.liquid)

  • `sections/header-group.json`: block_order for static blocks under mobile-menu-drawer-content.

  • Button minimum-height overrides — scoped pills/tabs/nav (`snippets/hw-scoped-button-min-height-style.liquid`, `blocks/hw-collection-tabs.liquid`, `blocks/hw-faq-split-filter.liquid`, `snippets/hw-tabbed-content.liquid`, `snippets/hw-carousel-navigation.liquid`): Assign scope_id before {% render %} where the id is composed with append, so Liquid does not truncate it (fixes styles not applying to tab/pill/arrow targets). Overrides set both min-height and --button-min-height-mobile / --button-min-height-desktop so theme .button tokens do not win. With Override minimum height on, slider 0 applies 0px at each breakpoint (both @media blocks always emit).

  • Button (HW) — minimum height sliders (`snippets/hw-button.liquid`): When override is enabled, mobile/desktop values apply as explicit px for all slider values including 0 (no longer falls back to theme var(--primary-*-button-min-height-*) when a slider was 0).

  • Product card ATC / quick-add vs global button min-heights (`assets/hw-add-to-cart.css`, `assets/hw-quick-add.css`): Targets `.button.hw-add-to-cart` (and full-width variant on the same node) instead of descendant .hw-add-to-cart .button, which never matched; sets token + min-height from **--hw-add-to-cart-height-*. hw-quick-add-wrapper .quick-add__button.button** resets theme --button-min-height-* so fixed height: var(--button-size-md) and compact/large gallery sizes are not blown up by global button preset floors.

  • Mobile menu drawer (HW): Rest-links split mode no longer depends on resolving the static `hw-mobile-drawer-menu-links` block in `block.blocks` when `block_order` is empty. Rest links inject inside the rich-branch panel for both accordion and drawer submenu styles (removed accordion-only gate); removed duplicate post-loop injection and invalid nested-list markup. Gap and rest-list margins live on `hw-mobile-drawer-menu-links`; drawer link typography on `hw-mobile-menu-drawer-content`. Custom accordion + drawer SVGs: avoid assign x = x for render / content_for params; `hw-mobile-drawer-menu` passes icon strings and submenu settings from resolved `hw-mobile-menu-drawer-content`. Files: blocks/hw-mobile-menu-drawer-content.liquid, blocks/hw-mobile-drawer-menu-links.liquid, blocks/hw-mobile-drawer-menu.liquid, snippets/hw-header-drawer-navigation.liquid, sections/header-group.json, locales/en.default.schema.json, assets/hw-header.css, .cursor/rules/blocks.mdc.

  • hw-popup: visible_if added to time_delay_seconds (gated on trigger_time_enabled) and scroll_percent (gated on trigger_scroll_enabled) so sub-settings hide when their parent trigger is off.

  • hw-popup: Removed dead data-hw-popup-dialog attribute from dialog element (never queried by JS).

  • hw-popup: Cleared test metafield value (custom.test_product) from popup-group.json.

  • hw-popup: Added missing storefront locale keys - content.hw_popup_cart_empty, content.hw_popup_recently_viewed_loading, content.hw_popup_recently_viewed_editor_preview.

  • hw-popup: Cart line item title class output no longer duplicates heading preset class (e.g. h3 h3).

  • Empty cart state (HW): Configurable empty cart state for both the cart page (hw-main-cart) and cart drawer (hw-cart-drawer). Section settings: custom heading, body copy, CTA button (label + URL), and optional featured product recommendations grid (collection picker + count). Snippet-first: snippets/hw-empty-cart.liquid renders the full UI in both contexts; static styles in assets/hw-empty-cart.css (loaded via hw-stylesheets). Cart page uses CSS show/hide on .cart-page--empty ancestor; drawer replaces hardcoded heading + continue-shopping button. aria-labelledby accessibility preserved in drawer. (Design spec TBC - clean token-based defaults applied.)

  • Estimated delivery (HW): hw-estimated-delivery block displays a calculated delivery date or date range. Configurable min/max days, optional weekday-only calculation (JS), and optional dispatch cutoff message (e.g. "Order before 2pm for same-day dispatch"). Renders via snippets/hw-estimated-delivery.liquid; base date calculation in Liquid (server-side UTC); weekend exclusion handled client-side in assets/hw-estimated-delivery.js using local timezone. CSS in assets/hw-estimated-delivery.css, both registered in hw-stylesheets / hw-scripts. Placeable in cart drawer, cart page, or product page sections.

  • Theme settings (Dev Indicator): Adds show_dev_indicator toggle (default: on). When enabled, renders a fixed bar at the bottom of the page showing the theme name - only when theme.role != 'main' (i.e. not the published live theme). Implemented via snippets/hw-dev-indicator.liquid + assets/hw-dev-indicator.css, rendered in layout/theme.liquid.

  • Theme settings (Scripts): Three custom injection points for merchant-defined HTML - custom_head_content (before </head>, for analytics tags, GA4, Meta Pixel, Hotjar), custom_body_start_content (after <body>, for GTM noscript), and custom_body_end_content (before </body>, for deferred third-party tools). Rendered via new hw-custom-scripts snippet registered at all three positions in layout/theme.liquid.

  • Accordion (HW): hw-accordion container block extending Horizon's base accordion. Adds a display_style toggle (accordion | drawer). _hw-accordion-row block extends _accordion-row with: toggle icon position (left/right), icon size with optional desktop override, per-row vertical padding with optional desktop override, and drawer mode - clicking a row in drawer mode opens a right-side <dialog> panel using the theme's drawer_color_scheme. Drawer content is moved from the inline details-content into the panel on first open. <dialog> handles backdrop click and native Escape key; slide-in animation respects prefers-reduced-motion.

  • HW variant picker: Snippet-first hw-variant-main-picker and hw-strikethrough-variant used only by hw-variant-picker block. OOS styling (fade background only, full-opacity labels, visible strikethrough), pill 100% on selected for resize, corner-to-corner strikethrough in grid; screen reader gets "(Unavailable)" in aria-label. Static CSS in assets/hw-variant-picker.css, loaded via hw-stylesheets.

  • Rules: Schema and merge safety (.cursor/rules/horizon-works-layer.mdc) for removing/adding settings and avoiding merge issues with settings_data.json.

  • Theme settings (Swatches): Shape (square, circle, pill), size preset (small, medium, large), show option label, and spacing between swatches. CSS variables --hw-swatch-shape, --hw-swatch-size, --hw-swatch-spacing for use by HW swatches and product grid.

  • HW Swatches: hw-swatches block available on product cards (alongside base swatches). Uses theme swatch settings with optional per-block overrides (shape, size, show label). Renders via hw-swatch snippet with BEM styling in hw-swatches.css. Base blocks unchanged per Clone & Own rule.

  • Icon (HW): hw-icon block with three source types-theme icon, custom image upload, and custom SVG code input. Includes configurable width (mobile + optional desktop override), optional link with new-tab support, and SVG effects (opacity, grayscale). Rendering logic in hw-icon-or-image snippet with SVG sanitization.

  • Button (HW): Custom SVG code icon type, configurable icon width (mobile + optional desktop override), and SVG effects (opacity, grayscale).

  • Theme settings (Icons): Per-color-scheme icon_svg_fill color setting, exposed as --color-icon-svg-fill CSS variable.

  • Product card badges (HW): Metafield-powered custom badges as sub-block of Product image (HW). Add Badge (HW) block and connect to a product metafield via dynamic source for per-product badges.

  • Badge (HW): hw-badge block with color scheme picker, typography preset override, and inherit-from-theme option.

  • Theme settings (Badges): Layout (stack/row), gap between badges, max badges per card, hide Sale when custom badge present.

  • Theme settings (Badges): Typography preset, text case override (badge case overrides type preset case).

  • Theme settings (Badges): Badge order-Sale and Sold out first, or Custom badges first. With max 1 and custom first, products with a custom badge show only that; others show Sale/Sold out.

  • Theme settings (Badges): Padding (vertical, horizontal) for all badges.

  • Assets: hw-product-badges.css and hw-card-gallery.css loaded once per page via hw-stylesheets snippet.

  • Add to cart (HW): Icon toggle (show/hide), custom icon option (image picker), and custom label override for available products.

  • Blocks: hw-add-to-cart, hw-buy-buttons, _hw-product-details with full HW duplicate chain for product page.

  • Sections: hw-product-information using HW add-to-cart blocks (product template opt-in).

  • Button (HW): hw-button block with icon support-icon type (default/custom), icon picker, icon position (left/right), content alignment (left, right, space-between), fill width option, and divider toggle.

  • Buy buttons: Inherit color scheme and color scheme override (hw-buy-buttons).

  • Typography: Label preset (smallest text, 10-14px) in Theme settings.

  • Typography: Line height and letter spacing as range sliders (%).

  • Typography: Font weight override per heading (h1-h6).

  • Typography: Logical font-size ranges per element (Label 10-14px, Paragraph 12-24px, H1 24-120px, etc.).

  • Typography: Six paragraph presets (Paragraph, Paragraph 2-6) in Theme settings-each with size (mobile/desktop), line height, letter spacing. Available in hw-text, hw-price, hw-product-title and RTE.

  • Typography: Button typography-line height, letter spacing, font weight, text case (primary/secondary).

  • Blocks: hw-text, hw-price, hw-product-title with typography presets and fluid scaling toggle.

  • Theme settings docs: .agent/docs/theme-settings/ with README and dev/editor guides for Typography and Buttons. Typography: link typography and underline, paragraph/label/headings, pointer to button typography in Buttons tab. Buttons: typography presets 1-3, Primary/Secondary/Tertiary (border, radius), base vs HW block, file map and CSS vars. Other theme setting tabs to be documented later.

Removed

  • `hw-model-stats` block - removed in favour of `hw-product-info-items` (update templates/product.json and any custom templates still referencing the old type).

Docs

  • `.agent/docs/sections/hw-carousel-grid/DEVELOPER_GUIDE.md` + `EDITOR_GUIDE.md`: Static vs dynamic blocks (content_for ids), presets, Comparison carousel. Expanded with Carousel Navigation (`arrow-button-appearance`, presets, `slideshow:select` for JS sync), Carousel Progress Bar (`progress-bar-corner-radius`, nowrap row layout in `hw-carousel-progress-bar.css`).

  • `.cursor/references/hw-layout-settings-patterns.md`: Carousel nav/disabled/progress/sync notes; nowrap row `flex` rules for progress vs nav Shopify block wrappers.

  • `.agent/docs/blocks/hw-grid/EDITOR_GUIDE.md`: New. Column gaps, row gap overrides (including desktop-only row gap at 990px+), and HW Grid item as a child block.

  • `.agent/docs/blocks/hw-flex-spacer/DEVELOPER_GUIDE.md` + `EDITOR_GUIDE.md`: New. Flex spacer behaviour vs theme Spacer / space-between, hero + Group (HW) Fill workflow, display_breakpoint, hw-group-size-style and desktop height override, file map, CSS notes, theme-check / locale note, pre-push checklist.

  • `.agent/docs/blocks/hw-card/DEVELOPER_GUIDE.md` + `EDITOR_GUIDE.md`: New. Class-scoping rationale, stacking-context tables (default vs .hw-card--bg-video-click), padding-top ratio technique, mobile/desktop cascade matrix, <hw-card-bg-video> delegation, media branching (hw-background-media vs hw-video), schema map, presets, troubleshooting.

  • `.agent/docs/blocks/hw-video/DEVELOPER_GUIDE.md` + `EDITOR_GUIDE.md`: New. Shared snippet parameters, deferred-media modes (autoplay / poster click / no-poster autoplay), 9-way play icon grid CSS, overlay layering rules per mode, hw-card background-video reuse, schema map, base file edit notes (assets/base.css + snippets/media.liquid scoping).

  • `.agent/docs/theme-settings/typography/DEVELOPER_GUIDE.md`: Rewritten to cover custom font system (file map, metaobject definition table, unified preset loop, fallback handling).

  • `.agent/docs/theme-settings/typography/EDITOR_GUIDE.md`: Rewritten with step-by-step metaobject setup, font file upload, entry creation, theme setting configuration, and a worked example.

  • Breakpoint-specific settings (HW): Separate desktop padding and device visibility for hw-button, hw-text, hw-group blocks and hw-hero section.

    Desktop padding: padding-*-desktop range settings (0-100 px) override mobile padding at 750 px+. Uses new snippets/hw-spacing-style.liquid + assets/hw-spacing-style.css (mirrors base spacing-style but scopes desktop custom properties per-node so values never inherit from ancestors).

    Device visibility: display_breakpoint select (Both / Desktop / Mobile) on each block; outputs hw-desktop-only or hw-mobile-only class via assets/hw-block-display.css (hide with display: none !important at the relevant viewport).

    Hero (HW): Switched from spacing-style / spacing-style snippet to hw-spacing-style / hw-spacing-style snippet; adds desktop padding settings (defaults: 40 px block, 0 px inline).

    Button (HW): Wraps output in .hw-button-block-root div carrying hw-spacing-style, visibility class, and block.shopify_attributes; preserves fit-content sizing and expands to full-width when child button is full-width.

    Locale: Added display_visibility, display_breakpoint, display_breakpoint_desktop / _mobile / _both, padding_desktop to locales/en.default.schema.json.

  • Hero (HW): sections/hw-hero.liquid with composable blocks; assets/hw-hero.css for frame, background/overlay/content stacking, and pointer-events so overlays stay clickable where intended. Section-building notes in .agent/docs/architecture/SECTION_BUILDING.md.

  • Group (HW) layout stack: snippets/hw-group.liquid, snippets/hw-group-effective-directions.liquid, snippets/hw-layout-panel-style.liquid, and assets/hw-group-layout.css (registered via snippets/hw-stylesheets.liquid) for small-screen vs 750px+ flex direction and alignment variables.

  • Text (HW) width cascade: assets/hw-text.css - .text-block.hw-text-block rules so HW width / max-inline-size variables win over snippets/text.liquid and row flex rules in assets/base.css; hw-text-block class on snippets/hw-text.liquid.

  • Shared HW width variables: snippets/hw-size-style.liquid - --size-style-width / mobile vars for fit, fill, and custom pixel caps (min(100%, Npx)), used with HW text and button sizing.

  • `.cursor/skills/hw-text-typography-settings/SKILL.md`: References assets/hw-text.css and the .hw-text-block contract.

  • `.cursor/skills/mobile-row-wrap-debugging/SKILL.md`: Documents hw-group small-screen vs desktop direction (no vertical_on_mobile on HW group) vs base group / mobile-column.

  • `.agent/docs/header-navigation/` - merchant editor docs split by topic: mega-menu/EDITOR_GUIDE.md, mobile-nav/EDITOR_GUIDE.md; index EDITOR_GUIDE.md. Developer index DEVELOPER_GUIDE.md, mega mega-menu/DEVELOPER_GUIDE.md.

  • `.agent/docs/header-navigation/MOBILE_MENU_DRAWER.md` - 3+ levels + accordion vs Mobile menu row (HW).

v1.0.0-beta.1

4 added1 changed

Added

  • Core: Initial "Flagship" designation.

  • Docs: Added **Developer Guide** and **Maintainer Guide**.

  • Docs: Added Strategic Architecture, Action Plan, and Lifecycle Walkthrough.

  • Repo: Established horizon-works package name and versioning structure.

Changed

  • Config: Updated package.json to reflect the new framework name and version.