Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
41c6914
Fix incorrect sale badge showing on products without compare-at price…
hta218 Feb 26, 2026
d0458d8
Add bypass for Weaverse theme layout in Hydrogen dev tools
hta218 Feb 26, 2026
363591f
Add minimal HeaderCountrySelector component for header usage
hta218 Feb 26, 2026
265d292
Add showHeaderCountrySelector theme setting in Header group
hta218 Feb 26, 2026
a839c3a
Wire up HeaderCountrySelector in header, gated by theme setting
hta218 Feb 26, 2026
554cab3
Implement ScrollArea for improved country list display in CountrySele…
hta218 Feb 26, 2026
130d862
Show HeaderCountrySelector based on theme setting
hta218 Feb 26, 2026
2c69cbb
Add .plans directory to .gitignore
hta218 Feb 26, 2026
21572fe
Replace framer-motion with CSS keyframes and IntersectionObserver in …
hta218 Feb 26, 2026
c414b89
Replace data-motion attributes with animate-* CSS classes in shared c…
hta218 Feb 26, 2026
f92825b
Replace data-motion attributes with animate-* CSS classes in all sect…
hta218 Feb 26, 2026
e2e4cab
Remove framer-motion dependency
hta218 Feb 26, 2026
4e8fa70
Add ScrollReveal component with shared singleton observer, remove use…
hta218 Feb 27, 2026
e556f90
Wrap shared components with ScrollReveal internally (button, heading,…
hta218 Feb 27, 2026
1ef8741
Replace animate-* classes with ScrollReveal wrapper in all sections
hta218 Feb 27, 2026
5308979
Remove useAnimation hook from slideshow and hero-video sections
hta218 Feb 27, 2026
2f9af6f
ScrollReveal always renders a tag, add ref forwarding and as prop sup…
hta218 Feb 27, 2026
b829c58
Eliminate double wrapper in shared components by using ScrollReveal a…
hta218 Feb 27, 2026
76a482b
Merge inner element props into ScrollReveal to eliminate double wrapp…
hta218 Feb 27, 2026
7ec3d54
Add blur reveal effect to sections and ScrollReveal to Link component
hta218 Feb 27, 2026
f857790
Merge branch 'main' of github.com:Weaverse/pilot into dev
hta218 Feb 28, 2026
0607b31
Update @shopify/hydrogen to 2026.1.1
hta218 Feb 28, 2026
d7c517e
Add spec-driven development conventions
hta218 Mar 3, 2026
586cd49
Add spec for removing framer-motion and replacing with CSS animations
hta218 Mar 3, 2026
6605952
Update .gitignore and remove spec-driven development conventions file
hta218 Mar 3, 2026
df3bb76
Wrap slideshow section with ScrollReveal for scroll-triggered animation
hta218 Mar 3, 2026
f671f5f
Remove outdated setup instructions and streamline development guideli…
hta218 Mar 3, 2026
7bfb89e
Add spec-driven development guidelines and folder structure to AGENTS.md
hta218 Mar 3, 2026
68a8b6d
Consolidate .hydrogen and guides into .hydrogen/upgrades and .hydroge…
hta218 Mar 3, 2026
de4e48a
Tweak header layout: reorder country selector, adjust cart drawer and…
hta218 Mar 3, 2026
9bcf618
Remove useMemo/useCallback and refactor animation variants to cva
hta218 Mar 3, 2026
eb77f8b
Clarify convention exceptions for arrow exports and default exports
hta218 Mar 3, 2026
74e6c78
Downgrade vite to ^6.4.1 and update dependencies for v8.1.0
hta218 Mar 3, 2026
ec093fc
Remove will-change and blur from scroll reveal to reduce scroll jank
hta218 Mar 3, 2026
ee0d40f
Wrap hero-video with ScrollReveal and fix hotspots aspect ratio
hta218 Mar 3, 2026
0dee68d
Add variant-based media grouping for product pages
hta218 Mar 3, 2026
40b77b3
Fix variant media grouping to use all variants instead of adjacentVar…
hta218 Mar 3, 2026
cc5311f
Rewrite variant media grouping algorithm
hta218 Mar 3, 2026
3c74468
Add debug logs to variant media grouping
hta218 Mar 3, 2026
8af027b
Switch to filename-based media grouping
hta218 Mar 3, 2026
75744db
Remove debug console.log statements from variant media grouping
hta218 Mar 3, 2026
75757a6
Add upgrade guides for Hydrogen versions 2025.5.0 to 2025.7.0 and 202…
hta218 Mar 3, 2026
8a09baf
Add cookbook for variant media grouping feature
hta218 Mar 3, 2026
9dc4773
Remove unused variants(first: 100) from product query
hta218 Mar 4, 2026
24596d3
Update cookbook to remove stale query reference
hta218 Mar 4, 2026
f5bc965
Simplify spec folder naming to date-first format
hta218 Mar 4, 2026
6c051a5
Export getVariantGroupedMedia function and related types for external…
hta218 Mar 4, 2026
c87b436
Merge pull request #347 from Weaverse/feat/variant-media-grouping
hta218 Mar 4, 2026
0820f71
Remove Framer Motion dependency and replace animations with CSS trans…
hta218 Mar 4, 2026
caca531
Rename cookbooks
hta218 Mar 4, 2026
db3c113
Update AGENTS.md to enhance Spec-Driven Development guidelines and fo…
hta218 Mar 4, 2026
cac37ac
Update dependencies: tailwindcss 4.2, biome 2.4, swiper 12.1, zustand…
hta218 Mar 4, 2026
dbbb27c
Migrate biome config for v2.4: graduate useConsistentArrowReturn from…
hta218 Mar 4, 2026
e3d91b9
Reorder imports in scroll-reveal and variants components for improved…
hta218 Mar 4, 2026
5f91c52
Add animate prop to Section component to allow disabling scroll-reveal
hta218 Mar 4, 2026
5124129
Clean up variant-media: remove unused normalizeImageUrl and extra bla…
hta218 Mar 4, 2026
78987ba
Expand variant-media filename matching to cover start-of-filename pat…
hta218 Mar 4, 2026
fbf3a12
Apply Biome formatting and lint fixes across components and sections
hta218 Mar 4, 2026
ea00108
Enhance grid layout logic in ProductMedia for better responsiveness
hta218 Mar 4, 2026
093acfb
Add show more/less toggle for product media grid with configurable in…
hta218 Mar 4, 2026
a01593b
Refine ProductMedia component styles and button interactions for impr…
hta218 Mar 4, 2026
d04a63e
Split ProductMedia into separate modules for better maintainability
hta218 Mar 5, 2026
47f5419
Refactor main-product into mp--media and mp--info child architecture
hta218 Mar 5, 2026
8e40cfb
Move state ownership to child components
hta218 Mar 5, 2026
9357713
Show all media in zoom modal for cross-variant browsing
hta218 Mar 5, 2026
e85bfd1
Skip show-more media limiting on mobile
hta218 Mar 5, 2026
9ecc0db
Add media grouping options by variant to ProductMediaComponent schema
hta218 Mar 5, 2026
9cd4c09
Show 2 slides per view on wide screens (1700px+)
hta218 Mar 5, 2026
ee1e1a9
Track active thumbnail via swiper index instead of CSS classes
hta218 Mar 5, 2026
fa2bcde
Offset product badges to avoid overlapping slider thumbnails
hta218 Mar 5, 2026
b8bfba3
Reset slider to first slide on media change and add thumb click navig…
hta218 Mar 5, 2026
b14c351
Update lockfile for @shopify/cli 3.91.1 and node-releases 2.0.36
hta218 Mar 5, 2026
b581d78
Use container-based breakpoints for product media slider
hta218 Mar 5, 2026
2e4fb0c
Use container queries for responsive thumbnail width
hta218 Mar 6, 2026
7b2b4d5
Update @weaverse/hydrogen to 5.9.3
hta218 Mar 6, 2026
b1c6a01
Move specs from .specs/ to .weaverse/specs/ for public template
hta218 Mar 6, 2026
b330584
Merge branch 'main' of github.com:Weaverse/pilot into dev
hta218 Mar 9, 2026
b824216
Fix media slider to navigate to variant image when grouping is disabled
hta218 Mar 9, 2026
1fa19fb
Fix media slider navigation when grouping is enabled but product has …
hta218 Mar 9, 2026
1dd910f
Refactor product media utilities for better colocation
hta218 Mar 9, 2026
a0b67ef
Update variant media grouping documentation after refactoring
hta218 Mar 9, 2026
14dff24
Add support for multi-word option values in variant media grouping
hta218 Mar 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,5 @@ pnpm-lock.yaml
.react-router
.claude/
.github/copilot-instructions.md
memory/
scripts/
specs/
templates/
.rules/
.plans/
File renamed without changes.
61 changes: 61 additions & 0 deletions .guides/cookbooks/variant-media-grouping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Variant Media Grouping

Groups product media by variant option (e.g., Color). When a visitor selects "Black", only images with "black" in the filename are shown, plus shared/ungrouped images. Falls back to all media if no matches.

## How It Works

1. Get the selected option value from `selectedVariant.selectedOptions` (e.g., "Black")
2. Get all known option values from `product.options` for the grouping option (e.g., ["Black", "Cream"])
3. For each media item, extract the option value from the image filename URL
4. **Filename matching** supports:
- Single-word values: `_black`, `-black`, `black_`, `black-`, `black` (end)
- Multi-word values: space is replaced with `_`, `-`, or removed
- "Slate Brown" matches `slate-brown`, `slate_brown`, `slatebrown`
5. Group media into a `Map<string, MediaFragment[]>` keyed by option value. Media not matching any option goes to `ungrouped[]`
6. Return `{ media: [...matched, ...ungrouped], isGrouped: true }`. If no matches, return `{ media: allMedia, isGrouped: false }` as fallback

## Files

| File | Role |
|------|------|
| `app/components/product/product-media/variant-media-group.ts` | Core logic: `getVariantGroupedMedia()` and `extractOptionValueFromUrl()` |
| `app/components/product/product-media/index.tsx` | Calls the utility when `groupMediaByVariant` is enabled. Uses `displayMedia` instead of `media` for rendering |
| `app/components/product/product-media/media-slider.tsx` | Handles slide navigation based on `isGrouped` flag |
| `app/sections/main-product/index.tsx` | Weaverse schema settings + passes props to `ProductMedia` |
| `app/sections/single-product/index.tsx` | Same as above |

## Weaverse Settings (in both sections)

- **Group media by variant** — switch, default `false`
- **Group by option name** — text, default `"Color"`, conditional on switch being `true`

## Props Added to ProductMedia

```ts
groupMediaByVariant?: boolean;
groupByOption?: string;
product?: NonNullable<ProductQuery["product"]>;
```

## Return Type

```ts
interface VariantGroupedMediaResult {
media: MediaFragment[];
isGrouped: boolean; // true if media was actually grouped by variant
}
```

## Navigation Behavior

When the selected variant changes:

- **If `isGrouped` is true**: Slider resets to the first slide (showing grouped images)
- **If `isGrouped` is false**: Slider navigates to the selected variant's image (better UX when all media is shown)

## To Remove

1. Delete `app/components/product/product-media/variant-media-group.ts`
2. In `app/components/product/product-media/index.tsx`: remove the import, the 3 props above, the `displayMedia` logic block, and revert `displayMedia` references back to `media`
3. In both `app/sections/main-product/index.tsx` and `app/sections/single-product/index.tsx`: remove the `groupMediaByVariant` and `groupByOption` schema settings, interface fields, destructured props, and the 3 extra props on `<ProductMedia>`
4. Run `npm run codegen`
11 changes: 11 additions & 0 deletions .weaverse/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# .weaverse

This folder contains **Weaverse org-internal** resources used for development and planning of the Pilot theme.

## Contents

- `specs/` — Feature specifications following the [Spec-Driven Development](../AGENTS.md#spec-driven-development-sdd) conventions. Each feature has its own subfolder with a README, implementation plan, and optional work logs.

## For template users

This folder is safe to delete. It contains internal planning documents specific to the Weaverse team and is not required for your storefront to function.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Feature: Remove Framer Motion, Replace with CSS Animations

| Field | Value |
| ------------ | ---------------------------------- |
| **Status** | `in-progress` |
| **Owner** | @hta218 |
| **Created** | 2026-02-27 |
| **Last Updated** | 2026-02-27 |

## Original Prompt

> Remove the `framer-motion` dependency and replace all scroll-triggered animations with pure CSS keyframes + IntersectionObserver. Use Tailwind CSS custom `@keyframes` (no external animation library). Replace `data-motion` attributes with CSS classes. Use CSS custom properties for stagger delays.

## Summary

Removes the `framer-motion` library from the project and replaces all scroll-triggered animations with a lightweight, zero-dependency approach. Phase 1 (completed) replaced framer-motion with a `useAnimation` hook using IntersectionObserver + CSS `@keyframes`. Phase 2 (in progress) refactors that into a shared `<ScrollReveal>` wrapper component backed by a singleton IntersectionObserver and CSS transitions for simpler, per-element animation control.
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
# Remove Framer Motion, Replace with CSS Animations

## Phase 1 (COMPLETED): Remove framer-motion, use CSS + IntersectionObserver hook

Replaced `framer-motion` with `useAnimation` hook using IntersectionObserver + CSS `@keyframes`. Used `animate-*` CSS classes on elements within a hook-managed scope. ✅ Done and committed.

---

## Phase 2 (CURRENT): Refactor to shared `<ScrollReveal>` wrapper component

### Motivation

The Phase 1 approach has coupling issues:
- `useAnimation` hook creates a scope and queries children for `animate-*` classes — parent controls animation of unaware children
- `animate-*` classes are baked into component classNames — mixed concerns (styling vs animation intent)
- Adding/removing animation requires editing className strings in each component
- No per-element control over duration or delay

### New Approach

Replace the hook + class system with a `<ScrollReveal>` wrapper component that:
1. Uses a **shared singleton IntersectionObserver** (1 observer for all elements, not 1 per component)
2. Takes `animation`, `duration`, `delay`, and `className` as props
3. Reads `revealElementsOnScroll` from theme settings — if disabled, renders children directly (no wrapper overhead)
4. Uses **CSS transitions** (not `@keyframes`) for simpler state management — just toggle classes on intersection

---

## Requirements

### Functional Requirements
- [x] FR1: ~~Elements animate in when scrolled into view~~ (Phase 1)
- [x] FR2: ~~Three animation types: fade-up, zoom-in, slide-in~~ (Phase 1)
- [x] FR7: ~~framer-motion removed~~ (Phase 1)
- [ ] FR3: `<ScrollReveal>` component wraps any element and animates it on scroll
- [ ] FR4: Props: `animation` (type), `duration` (seconds), `delay` (ms), `className`, `as` (element type)
- [ ] FR5: Shared singleton IntersectionObserver — one observer for entire app
- [ ] FR6: `revealElementsOnScroll` theme setting toggles animation — when off, renders children with no wrapper
- [ ] FR7: Remove `useAnimation` hook, `animated-scope` CSS system, and all `animate-*` classes from components

### Non-Functional Requirements
- [ ] NFR1: Zero additional dependencies
- [ ] NFR2: Single observer instance reduces memory usage vs N observers
- [ ] NFR3: No flash of invisible content when animations disabled

### Out of Scope
- Staggered sibling animations (no longer needed — each `<ScrollReveal>` controls its own delay)
- The `animated-scope` parent class pattern (replaced by per-element control)

---

## Technical Approach

### ScrollReveal Component API

```tsx
<ScrollReveal animation="fade-up" duration={0.5} delay={100}>
<Heading>Title</Heading>
</ScrollReveal>

// With custom element type
<ScrollReveal as="section" animation="slide-in" className="my-class">
<Content />
</ScrollReveal>

// When revealElementsOnScroll is false → renders children directly, no wrapper
```

### Shared Observer Pattern

```typescript
// Singleton — created once, shared across all ScrollReveal instances
let sharedObserver: IntersectionObserver | null = null;
let callbacks = new Map<Element, (isIntersecting: boolean) => void>();

function getSharedObserver(): IntersectionObserver { ... }
function observe(element: Element, callback): () => void { ... }
```

### How It Works

```
Mount: <ScrollReveal animation="fade-up">
→ Element starts with: opacity-0 translate-y-5 (via CSS transition classes)
→ Registers with shared observer

Intersect: Observer fires callback
→ State: isVisible = true
→ Element transitions to: opacity-100 translate-y-0
→ Unregisters from observer (one-shot)

Disabled: revealElementsOnScroll = false
→ Renders children directly, no wrapper div, no observer
```

### Animation Styles (CSS Transitions, not @keyframes)

Each animation type maps to before/after Tailwind classes:

| Animation | Before (hidden) | After (visible) |
|-----------|-----------------|-----------------|
| `fade-up` | `opacity-0 translate-y-5` | `opacity-100 translate-y-0` |
| `zoom-in` | `opacity-0 scale-80 translate-y-5` | `opacity-100 scale-100 translate-y-0` |
| `slide-in` | `opacity-0 translate-x-5` | `opacity-100 translate-x-0` |

Applied via `transition-all` + `will-change-transform` with `transitionDuration` and `transitionDelay` as inline styles.

---

## Implementation Structure

### Files to Create

| File | Purpose |
|------|---------|
| `app/components/scroll-reveal.tsx` | `<ScrollReveal>` wrapper component with shared observer |

### Files to Modify

| # | File | Changes |
|---|------|---------|
| 1 | `app/styles/app.css` | Remove all `@keyframes` (fade-up, zoom-in, slide-in) and `.animated-scope` / `.in-view` CSS rules added in Phase 1 |
| 2 | `app/components/button.tsx` | Remove `animate-fade-up` class logic, wrap with `<ScrollReveal>` at usage sites (or keep `animate` prop that wraps internally) |
| 3 | `app/components/heading.tsx` | Remove `animate-fade-up` from className merge, wrap with `<ScrollReveal>` at usage sites (or keep `animate` prop that wraps internally) |
| 4 | `app/components/subheading.tsx` | Remove `animate-fade-up` from className, wrap with `<ScrollReveal>` at usage sites (or keep `animate` prop that wraps internally) |
| 5 | `app/components/paragraph.tsx` | Remove `animate-fade-up` from className, wrap with `<ScrollReveal>` at usage sites (or keep `animate` prop that wraps internally) |
| 6 | `app/components/product/quantity.tsx` | Remove `animate-fade-up` class, wrap with `<ScrollReveal>` |
| 7 | `app/sections/main-product/variants.tsx` | Remove `animate-fade-up` class, wrap with `<ScrollReveal>` |
| 8 | `app/sections/countdown/timer.tsx` | Remove `animate-fade-up` class, wrap with `<ScrollReveal>` |
| 9 | `app/sections/newsletter/newsletter-form.tsx` | Remove `animate-fade-up` (2 elements), wrap with `<ScrollReveal>` |
| 10 | `app/sections/featured-collections/collection-items.tsx` | Remove `useAnimation` hook, remove `animate-slide-in` class, wrap items with `<ScrollReveal>` |
| 11 | `app/sections/image-with-text/image.tsx` | Remove `animate-slide-in` class, wrap with `<ScrollReveal>` |
| 12 | `app/sections/image-gallery/image.tsx` | Remove `animate-slide-in` class, wrap with `<ScrollReveal>` |
| 13 | `app/sections/promotion-grid/item.tsx` | Remove `animate-slide-in` class, wrap with `<ScrollReveal>` |
| 14 | `app/sections/testimonials/item.tsx` | Remove `animate-slide-in` class, wrap with `<ScrollReveal>` |
| 15 | `app/sections/columns-with-images/column.tsx` | Remove `animate-slide-in` class, wrap with `<ScrollReveal>` |
| 16 | `app/sections/hotspots/index.tsx` | Remove `animate-zoom-in` class, wrap with `<ScrollReveal>` |
| 17 | `app/sections/single-product/index.tsx` | Remove `useAnimation` hook, remove `animate-*` classes, wrap elements with `<ScrollReveal>` |
| 18 | `app/sections/slideshow/slide.tsx` | Remove `useAnimation` hook usage, wrap animated children with `<ScrollReveal>` |
| 19 | `app/sections/hero-video.tsx` | Remove `useAnimation` hook usage, wrap animated children with `<ScrollReveal>` |
| 20 | `app/sections/ali-reviews/review-list.tsx` | Remove `animate-slide-in` (2 elements), wrap with `<ScrollReveal>` |

### Files to Delete

| File | Reason |
|------|--------|
| `app/hooks/use-animation.ts` | Replaced by `<ScrollReveal>` component — no longer needed |

---

## Step-by-Step Instructions

### Step 1: Create `app/components/scroll-reveal.tsx`

Implement the `<ScrollReveal>` component with:
- Shared singleton `IntersectionObserver` (threshold: 0.1)
- `observe()` / cleanup utility functions
- Props: `as`, `animation`, `duration` (default 0.5), `delay` (default 0), `className`, `children`
- Reads `revealElementsOnScroll` from `useThemeSettings()`
- When disabled: render `children` directly without a wrapper

Animation class mappings:
- `fade-up`: hidden `opacity-0 translate-y-5` → visible `opacity-100 translate-y-0`
- `zoom-in`: hidden `opacity-0 scale-80 translate-y-5` → visible `opacity-100 scale-100 translate-y-0`
- `slide-in`: hidden `opacity-0 translate-x-5` → visible `opacity-100 translate-x-0`

### Step 2: Clean up `app/styles/app.css`

Remove all Phase 1 CSS added at the bottom:
- `@keyframes fade-up`, `zoom-in`, `slide-in`
- `.animated-scope` hiding rules
- `.in-view` trigger rules

### Step 3: Delete `app/hooks/use-animation.ts`

No longer needed — `<ScrollReveal>` handles everything.

### Step 4: Update shared components (button, heading, subheading, paragraph)

These components currently have animation built into their className logic. Two options:

**Option A (Recommended)**: Keep `animate` prop, but wrap with `<ScrollReveal>` internally
```tsx
// heading.tsx
if (animate) {
return (
<ScrollReveal animation="fade-up">
<Tag ref={ref} {...rest} className={cn(...)}>
{content}
</Tag>
</ScrollReveal>
);
}
return <Tag ref={ref} {...rest} className={cn(...)}>{content}</Tag>;
```

**Option B**: Remove `animate` prop entirely, let parent sections wrap with `<ScrollReveal>`
- Cleaner separation but requires more changes at usage sites

### Step 5: Update section components

For each section, remove `animate-*` classes and wrap with `<ScrollReveal>`:

```tsx
// Before
<div className="animate-slide-in group relative">

// After
<ScrollReveal animation="slide-in">
<div className="group relative">
</ScrollReveal>
```

For sections that used `useAnimation` hook (collection-items, slideshow, hero-video, single-product):
- Remove `useAnimation` import and `const [scope] = useAnimation(ref)`
- Remove the `ref={scope}` from the parent wrapper
- Wrap individual animated children with `<ScrollReveal>`

### Step 6: Verify

1. Run `npm run typecheck` — no type errors
2. Run `npm run biome:fix` — no lint errors
3. Run `npm run build` — production build succeeds
4. Verify no remaining references: `grep -r "animate-fade-up\|animate-slide-in\|animate-zoom-in\|useAnimation\|animated-scope" app/`

---

## Execution Order

| Phase | Files | Parallel? |
|-------|-------|-----------|
| 1. Create component | `app/components/scroll-reveal.tsx` | - |
| 2. Clean CSS | `app/styles/app.css` | - |
| 3. Delete hook | `app/hooks/use-animation.ts` | - |
| 4. Update shared components | button, heading, subheading, paragraph, quantity | Yes |
| 5a. Update sections (fade-up) | variants, timer, newsletter-form | Yes |
| 5b. Update sections (slide-in) | collection-items, image-with-text, image-gallery, promotion-grid, testimonials, columns-with-images, ali-reviews, single-product | Yes |
| 5c. Update sections (zoom-in) | hotspots | Yes |
| 5d. Update hook-dependent sections | slideshow, hero-video | Yes |
| 6. Verify | typecheck, biome, build, grep | Sequential |

**Total files created**: 1
**Total files modified**: 20
**Total files deleted**: 1
**Estimated effort**: Medium — component creation + mechanical wrapping across 20 files
16 changes: 16 additions & 0 deletions .weaverse/specs/2026-03-03--variant-media-grouping/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Feature: Variant Media Grouping

| Field | Value |
| ------------ | ---------------------------------- |
| **Status** | `completed` |
| **Owner** | @hta218 |
| **Created** | 2026-03-03 |
| **Last Updated** | 2026-03-09 |

## Original Prompt

> I have an idea for the product media images inside product page, we now will implement a feature that group images with variant, for e.g when visitor select a color black/cream only images that match will display instead of showing all like currently

## Summary

Filter product media on the product page based on the selected variant's option value (e.g., Color). When a visitor selects a variant option like "Black", only images assigned to variants with that option value are displayed, plus any ungrouped/shared media. The feature is opt-in via a Weaverse section toggle and configurable for which option name drives grouping. Applies to both `main-product` and `single-product` sections.
Loading