# Happy Addons Free & Pro — Elementor V4 (Atomic) Integration: Scoped Plan (3 widgets + 2 features)

## Context

The earlier plan covered Atomic versions of all widgets, extensions and GSAP. The scope is now **5 components only**:

- Widgets: **Image Cycle, Info Box, Card**
- Features: **Appearing Image Animation (AIA), Text Animation (HTA)**

Everything else stays V3-only. Nothing in V3 is modified.

All findings below were read from source (Free `atomic-v4-wf` @ `293c150f`, Pro, Elementor 4.3.5). The only existing Atomic work is commit `936a052c` on `origin/atomic-elements-support`. It is not on this branch, forked from an older base, and covers 11 widgets, 4 extensions and a dashboard. We reuse its *patterns* but do not cherry-pick it wholesale.

### Facts that shape the plan

- **Ownership: all 5 components are Free-only.** Pro has no Card/Info Box/Image Cycle/AIA/HTA code. Pro only *attaches* to V3 Card and Info Box:
  - Image Masking (`extensions/image-masking.php`, hooks `elementor/element/ha-card/...` and `ha-infobox/...`).
  - Presets (`extensions/designs-manager.php:155,182`).
  - Neither attaches to Atomic elements, so Pro needs **no changes** this phase. Pro integration is limited to the dashboard seam (`happyaddons_get_widgets_map` etc.), which stays untouched.
- **Source folder is `assets/dev/sass/`, not `scss/`.** The gulp glob (`gulpfile.mjs:68-70`) is `assets/dev/sass/**/*.scss`. The Atomic SCSS goes in `assets/dev/sass/atomic/` (see decision D1).
- **Widget registration is map-driven; there is no directory scanning** (`widgets-manager.php:1676`). A `widgets/atomic/` folder needs an explicit map entry and register call. The autoloader already resolves `Happy_Addons\Elementor\Widget\Atomic\X` → `widgets/atomic/x/widget.php` and `Extensions\Atomic\X` → `extensions/atomic/x.php`, so **no autoloader change is needed**.
- **Defaults:** Image Cycle on, Info Box on, **Card off** (V3 `is_active=false`, `widgets-manager.php:696`), AIA on, HTA on.
- **Atomic is experimental in Elementor:**
  - It is gated by the hidden `e_atomic_elements` experiment, and `936a052c` never checked it.
  - We gate on `experiments->is_feature_active('e_atomic_elements')` in addition to Elementor >= 4.0 and class existence.

---

## 1. Verified Atomic API (used; nothing invented)

All paths are under `elementor/modules/atomic-widgets/`.

| Need | Verified mechanism |
|---|---|
| Register leaf widget | `add_filter('elementor/widgets/register', fn($wm) => $wm->register(new X))`; X extends `Elements\Base\Atomic_Widget_Base` + `Has_Template` |
| Register container | `elementor/elements/elements_registered` → `register_element_type()`; X extends `Atomic_Element_Base` + `Has_Element_Template`, Twig contains `<!-- elementor-children-placeholder -->` (pattern: `elements/div-block/`) |
| Required methods | `get_element_type()`, `define_props_schema()`, `define_atomic_controls()`, `get_templates()`; optional `define_base_styles()`, `define_atomic_pseudo_states()`, `get_style_depends()`, `get_script_depends()` |
| Props | `String/Number/Boolean_Prop_Type`, `Escaped_Html_Prop_Type`, `Html_V3_Prop_Type`, `Image_Prop_Type`, `Link_Prop_Type`, `Classes_Prop_Type`, `Attributes_Prop_Type`, `Size/Color_Prop_Type`. Conditions via `set_dependencies(Dependency_Manager…)`. |
| Controls | `Text/Textarea/Inline_Editing/Image/Link/Select/Toggle/Switch/Number/Size/Html_Tag/Repeatable_Control`, `Section` |
| **Style tab** | **Not defined in PHP.** It is generated by the editor JS from `Style_Schema` (typography, spacing, border, background, shadow, layout, size, responsive per breakpoint, states). Every atomic element gets it for free. |
| Defaults for styles | `define_base_styles()` → `Style_Definition` / `Style_Variant` (breakpoint + state aware) |
| Dynamic tags | Auto-mapped through prop `meta`; per-prop support must be confirmed in Phase 1 |
| Add controls to *existing* atomic elements | Filters `elementor/atomic-widgets/props-schema`, `elementor/atomic-widgets/controls` (receives `$element`, so scope by `get_element_type()`). Elementor Pro's own `attributes` and `display-conditions` modules do this. |
| Custom prop transformer | action `elementor/atomic-widgets/settings/transformers/register` |
| Frontend JS | Atomic does **not** fire `frontend/element_ready/*`. Core uses `@elementor/frontend-handlers`. `elementor/frontend/before_render` still fires. Targets are found via `data-interaction-id` / `data-id` (Twig macro `m.render_data_attributes`). |
| Interactions module (`e-interactions`, `motion-js`) | Same experiment gate. **Not extensible for our animations**, so we do not use it. |
| Save strictness | `get_data_for_save()` is final and validates every prop; an invalid default throws. |

**Unsupported, internal or experimental (documented, not used):**
- `elementor/editor/v2/packages`.
- `get_initial_config` keys.
- The frontend-handlers package as public API. Dev-only gates `e_list`, `e_accordion`, etc.
- Settings props are **not per-breakpoint** (only style props are). This drives the limitations in §3.

---

## 2. Component Compatibility Matrix

| Component | Type | Owner | V3 reference | V4 default | Atomic feasibility |
|---|---|---|---|---|---|
| Image Cycle | Widget | Free | `widgets/image-cycle/widget.php`; JS `haImageCycleHandler` in `assets/dev/js/happy-addons.js:1957-2591`; `sass/image-cycle.scss` | **On** | **Feasible (leaf widget).** Needs a ported GSAP engine; style controls map to the native style tab. |
| Info Box | Widget | Free | `widgets/infobox/widget.php`; `sass/infobox.scss` + `btn.scss`; no widget JS | **On** | **Feasible, partial.** Container composition. Lord Icon type not natively representable (L1). |
| Card | Widget | Free | `widgets/card/widget.php`; `sass/card.scss` + `badge.scss` + `btn.scss`; no JS | **Off** (V3 `is_active=false`) | **Feasible.** Container composition. Image offset/CSS-filter hover mappings are partial (L2). |
| Appearing Image Animation | Feature | Free | `extensions/appearing-image-animation.php`; `assets/dev/js/appearing-image-animation.js`; `sass/appearing-image-animation.scss` | **On** (GSAP) | **Feasible via filters** on `e-image`. Controls are not responsive per breakpoint (L3). |
| Text Animation | Feature | Free | `extensions/heading-text-animation.php`; `assets/dev/js/heading-text-animation.js`; `sass/heading-text-animation.scss` | **On** (GSAP) | **Feasible via filters** on `e-heading`. Same L3. |

Pro impact: none (see Context).

---

## 3. Control Parity Matrix

Legend: **Native** = covered by Atomic's built-in Style tab (typography, color, spacing, border, shadow, background, size, responsive). **Prop** = a Happy prop plus Atomic control in the Content/General tab. **Lim** = limitation.

V3 defaults to preserve are listed in each row. All V3 selectors/classes (`ha-ic-*`, `ha-infobox-*`, `ha-card-*`) are kept where Atomic markup allows, so shared SCSS can be reused.

### 3.1 Image Cycle (`ha-a-image-cycle`, leaf widget, Twig)

| V3 control(s) → default | Atomic equivalent | Status |
|---|---|---|
| `ha_ic_animation_mode` `variation-1` (v1/v2/v3) | `String_Prop_Type` enum + `Select_Control` | Prop |
| `ha_ic_direction` `bottom` (bottom/left/top/right; only v1) | enum + `Toggle_Control`; dependency on mode=v1 | Prop |
| `ha_ic_images` repeater, 8 placeholder rows (`ha_ic_image`, `ha_ic_thumbnail` `large`, `ha_ic_link`) | `Array` of item props (`Image_Prop_Type`, `Link_Prop_Type`) + `Repeatable_Control`; 8 seeded defaults, max 10 rendered | Prop |
| Title/second/subtitle `Happy`/`Addons`/`2026` | `String_Prop_Type` ×3 + `Text_Control` | Prop |
| `ha_ic_title_tag` `h2`, `ha_ic_subtitle_tag` `h5` | enum + `Html_Tag_Control` or `Select_Control` | Prop |
| Animation settings: radius1 50 (resp.), radius 250px/180/180 (resp.), rotation dir `clockwise`, stagger 0.1, stagger_v3 0.15, entrance 0.5, heading dur 1, group dur 20, flip interval 2, initial scale 3, initial opacity 0.8, pause on hover, play on appear | `Number_Prop_Type` / `Boolean_Prop_Type` / enum with same defaults; dependencies mirror V3 conditions | Prop. **Lim L3**: radius/radius1 responsive → see below |
| Style: wrapper bg/height `100vh`/min-height `70vh`/padding/border/radius/shadow/overflow `hidden` | Native style tab on the root. `define_base_styles()` seeds height `100vh`, min-height `70vh`, overflow `hidden`. | Native |
| Style: headings box (bg, align `center`, padding, border, radius) | Native style on the headings element via a second class slot (see below) | See L4 |
| Style: title/subtitle typography + color + shadow + spacing | Native, same | See L4 |
| Style: image width `6em`/`5em`/`5em`, border, radius `5px`, shadow, fit `cover` | Native on card image class | See L4 |

**L4 (shared by all three widgets): sub-element styling.** A leaf atomic widget has **one** style target. V3 styles ~5 inner elements.
- Image Cycle: a leaf widget with an **Style prop section** for the inner parts (Prop: `Color_Prop_Type`/`Size_Prop_Type`, emitted as CSS variables and base-style variants), covering color, size and spacing. Full typography/border/shadow per inner part is **not** reproducible in a leaf widget.
- Info Box and Card avoid this by composing native children (§3.2, §3.3).
- Image Cycle is the only widget where L4 remains. This is documented as the main Image Cycle limitation. Its safest alternative is exposing the high-value parts (colors, sizes, spacing, typography via the variables) and the wrapper through the native style tab.

**L3: settings are not per-breakpoint.** `radius1` and `radius` (px/%/em/rem) are responsive in V3. Atomic: a single base value plus a *Style-tab breakpoint variant* where the value can live as a CSS custom property (e.g. `--ha-ic-radius`) set through a style variant, which **is** per-breakpoint. JS reads the computed variable. This is the safest supported route; it is verified in Phase 4 before committing.

### 3.2 Info Box (`ha-a-infobox`, container, children: media, title, description, button)

Root: `Atomic_Element_Base` with props for layout; children are core atomic elements (`e-image` or icon, `e-heading`, `e-paragraph`, `e-button`) created via `define_default_children()`, so they have **full native style tabs** (typography, colors, spacing, border, shadow, hover states). This replaces V3's ~50 style controls with the native mechanism and gives true parity of capability.

| V3 control(s) → default | Atomic equivalent | Status |
|---|---|---|
| `type` `icon` / image / lordicon | Child element choice: `e-image` (image), icon child (core icon element if available in 4.3.5, else SVG/`Icon_Prop_Type`) | Prop. **L1** below |
| Image + `thumbnail` `medium_large` | `e-image` `Image_Prop_Type` with `size` | Native/Prop |
| Icon `fas fa-smile-wink` | default icon child with the same glyph | Prop |
| `media_direction` `top` (responsive left/top) | Root enum prop → class (`ha-infobox-media-dir-left|top`). Per-breakpoint via root *style variants* (flex-direction), not a prop | Native style variant |
| `media_v_align` `top` | align-self on media child (native layout style) | Native |
| `title` / `description` / `title_tag` `h2` / `align` | `e-heading` / `e-paragraph` children; tag via `Html_Tag_Control`; align native | Native |
| Button text `Button Text`, link `#`, icon after, spacing | `e-button` child + link prop | Prop |
| Media offset X/Y/rotate (popover) | Native transform/position on media child | Native (verify) |
| Media/title/desc/button padding, margin, border, radius, shadow, colors, typography, hover colors | Native per child incl. hover state | Native |
| Dynamic tags on image/title/description/button | Core children carry dynamic support | Native |
| Lord Icon: method cdn/file, json, trigger, target, pulse, 4 colors, stroke, size | **No equivalent core child.** | **L1: unsupported** |

**L1 (Lord Icon):** needs a custom `<lord-icon>` element. Creating one means a fourth Atomic component, which is out of scope. Plan: Atomic Info Box ships with Image and Icon media only. The Lord Icon option is documented as unsupported, and V3 Info Box keeps it. Proposal for a later phase: a standalone Atomic Lord Icon widget.

### 3.3 Card (`ha-a-card`, container; children: image(+badge), title, description, button)

Same composition approach.

| V3 control(s) → default | Atomic equivalent | Status |
|---|---|---|
| `image` placeholder, `thumbnail` `large` | `e-image` child | Native |
| `image_position` `top` (left/top/right, responsive) | Root enum prop sets class `ha-card--{pos}`; per-breakpoint layout by style variants | Prop + native |
| `badge_text` `Badge Text`, `badge_position` `top-right` (9 options), offset X/Y | Badge as positioned child with a position enum prop; offsets via native position style | Prop + native |
| Badge color/bg/padding/border/radius/shadow/typography | Native on badge child | Native |
| Title `Happy Card Title`, tag `h2`, description | `e-heading` + `e-paragraph` | Native |
| Button text/link/fullwidth/align `center`/icon | `e-button` child; fullwidth/align as native layout | Native |
| Image width/height/padding/border/radius/shadow | Native on image child | Native |
| Image opacity, CSS filters, hover opacity/filters, transition duration | Native opacity/filter/transition with hover state | Native (**L2**: verify filter/`hover_animation` support; `hover_animation` classes unsupported → documented) |

### 3.4 Appearing Image Animation (filters on `e-image`)

Props/controls added only when `get_element_type() === 'e-image'`, in one "Appearing Animation" section. Defaults identical to V3.

| V3 control → default | Atomic prop |
|---|---|
| `ha_aia_switcher` off | `Boolean_Prop_Type` |
| `ha_aia_mode` `reveal` (reveal, tiles-reveal, corner-reveal, scale, stretch) | enum |
| `ha_aia_rs_direction` `left` | enum (mode=reveal) |
| `ha_aia_tiles_count` 5 / orientation `horizontal` / horizontal dir `bottom-to-top` / vertical dir `left-to-right` / stagger 0.08 | number / enums (mode=tiles-reveal) |
| `ha_aia_corner_direction` `top-right` | enum |
| `ha_aia_scale_from` 0.5 / `ha_aia_scale_to` 1 | numbers |
| `ha_aia_trigger_point` `top-center` (+10 options), `ha_aia_custom_trigger_start` `top 80%` | enum + string |
| `ha_aia_animation_duration` 1.2, `ha_aia_delay` 0, `ha_aia_easing_function` `power4.inOut` (21 options) | number / number / enum |
| `ha_aia_enable_on_mobile` `no` | boolean |

- **Settings delivery:** the filters add props. A `elementor/frontend/before_render` hook (as in `936a052c`'s Text_Stroke) reads `get_atomic_setting()` and registers the element in a **single footer JSON** (`window.HappyAtomic.features`), keyed by `data-id`. It prints nothing when no element uses the feature.
- **Limitations:**
  - **L3:** every AIA control is responsive in V3. Atomic settings are single-valued. Supported alternative: base values plus the `enable_on_mobile` switch. Per-breakpoint overrides are *not* provided, and this is documented as unsupported.
  - The V3 stretch mode hardcodes a 395px padding. The same value is kept; changes are out of scope.
  - V3 never honors `custom` trigger (`getConfig` only uses `trigger_point.replace('-',' ')`). **Decision D3:** the Atomic version honors the custom value. This is a deliberate fix, not drift.

### 3.5 Text Animation (filters on `e-heading`)

V3 attaches to `heading` and a broken `text-editor` hook (`section_title` does not exist there) and `e-heading`. **Only `e-heading` is relevant** here; `text-editor` is not ported.

Props (same IDs/defaults as V3):

| Group | Controls → default |
|---|---|
| Core | switcher off; `mode` `reveal` (slide, reveal, smoky_reveal, alt_reveal, popup_reveal, mixing_reveal, scale, text_flip, invert); `trigger_mode` `scroll` (scroll / playwithscroll); `trigger_point` `top-center`; `custom_trigger_start` `top 80%` |
| Timing | `delay` 0.15, `duration` 0.8, `stagger_delay` 0.05, `easing_function` `power2.out` (26 opts), `enable_on_mobile` off |
| slide | `chars_words_mode` `chars`, `cw_transform_x` 25, `cw_transform_y` 0 |
| text_flip | rotation dir `vertical`, value -80, origin `50% 100%`, custom origin `bottom center -50` |
| reveal | `orientation` `bottom` |
| smoky | y_offset 80, scale 2.5, blur 15 |
| alt | even -60, odd 60 |
| popup | y_offset 50, rotation 40 |
| mixing | blur 4 |
| shared | `stagger_from` `random` |
| scale | scale 1.5, `scale_text_break` `lines` |
| invert | `invert_opacity` 0.45 |
| highlight | `highlight_color` (applies to `{curly}` text) |

- **Limitations:**
  - The heading content prop is `Escaped_Html`/`Html_V3`, which affects how `{braces}` parse. A Phase-8 spike verifies that `{…}` survives the editor round-trip. If it does not, the highlight syntax is documented as unsupported, since V3 relies on raw HTML.
  - Same L3 (no responsive overrides).
  - V3 `||` fallbacks turn a value of 0 into the default (e.g. `cw_transform_y`). The Atomic port uses explicit null checks. Called out as a deliberate fix.

---

## 4. Asset Mapping

Build facts: `gulpfile.mjs` compiles `assets/dev/sass/**/*.scss` → `assets/css/widgets/<path>.min.css` **and concatenates all into `main(.min).css`**. `assets/dev/js/**/*.js` → `assets/js/<name>(.min).js`, one output per file, no concatenation. Outputs are gitignored.

| Component | V3 CSS | V3 JS | Atomic SCSS | Atomic JS | Shared deps | Compiled output | Loading |
|---|---|---|---|---|---|---|---|
| Image Cycle | `sass/image-cycle.scss` | `happy-addons.js` handler (inside IIFE, **not reusable**) | `sass/atomic/image-cycle.scss` (`@import`/`@use` the V3 partial's mixins; same `.ha-ic-*` classes so styles are **shared**, not duplicated) | engine port in `happy-addons-atomic.js` | `gsap`, `imagesloaded` (existing handles) | `assets/css/widgets/atomic/image-cycle.min.css` | `get_style_depends`/`get_script_depends` on the widget |
| Info Box | `infobox.scss`, `btn.scss` | none | `sass/atomic/infobox.scss`: only layout (media direction, body spacing). Children are native, so `btn.scss` is not needed | none | none | `.../atomic/infobox.min.css` | `get_style_depends` |
| Card | `card.scss`, `badge.scss`, `btn.scss` | none | `sass/atomic/card.scss`: layout + badge position only | none | none | `.../atomic/card.min.css` | `get_style_depends` |
| AIA | `appearing-image-animation.scss` | `appearing-image-animation.js` (Backbone-style handler on `image.default`, **not usable on Atomic**) | reuse V3 CSS by handle (the same `.ha-aia-*` classes). Atomic SCSS **only** if the `e-image` wrapper selectors differ (verified in Phase 7) | animation logic in `happy-addons-atomic.js`, not a new file | `gsap`, `scroll-trigger` | `assets/js/happy-addons-atomic(.min).js` | registered in `wp_enqueue_scripts`; enqueued only when ≥1 element on the page uses the feature |
| HTA | `heading-text-animation.scss` | `heading-text-animation.js` | same rule as AIA | same | `gsap`, `scroll-trigger`, `split-text` (existing handles) | same | same |

Decisions:
- **No duplicate handles.** Existing `gsap`, `scroll-trigger`, `split-text`, `imagesloaded`, and the V3 feature CSS handle `happy-appearing-image-animation` / `happy-heading-text-animation` are reused. The Atomic widgets' CSS handles are new (`happy-atomic-image-cycle`, …) because V3 widget CSS goes through the post-cache concat pipeline and has no standalone handle. V3 asset handles are unchanged.
- **One new JS handle** `happy-elementor-addons-atomic`, depends on `gsap`/`scroll-trigger`, registered once and **never loaded unless an enabled Atomic component renders**.
- **Small gulp change (D2):** exclude `sass/atomic/**` from the `main.css` concatenation so Atomic CSS does not ship in the global bundle. This is the only build-file edit and does not alter V3 output.
- **Honest cost:** the Image Cycle GSAP engine, AIA and HTA logic live in V3 files that cannot be imported (an IIFE and an `image.default`-bound handler). The Atomic versions must be **ported, not shared**. We are not allowed to refactor V3. The port keeps the algorithm identical, and the duplication is noted for a later shared-module refactor.
- **Lifecycle (`happy-elementor-addons-atomic.js`)**
  - A single `WeakMap<Element, Instance>` is the "already initialized" registry.
  - Init on `DOMContentLoaded`, and in the editor via a `MutationObserver` scoped to the preview frame, because Atomic elements are re-rendered by the React editor.
  - `destroy(el)` kills the ScrollTriggers/timelines, restores the original HTML (HTA) and removes listeners.
  - Resize/orientation listeners are namespaced per instance. V3 removed them for *all* instances.
  - `prefers-reduced-motion: reduce` → skip animation, show final state (new vs V3).
  - Scoped to the five components only; no generic runtime.

---

## 5. Dashboard & Settings Plan

### Catalog and settings (lightweight)

- Add **`classes/atomic-registry.php`** (new): a static array literal, with no widget class instantiation, `__()` called lazily per label. It holds the 3 widgets and 2 features. Each entry carries `title`, `icon`, `is_pro=false`, `is_gsap` (features), `is_active` (default), `css`/`js` handles. It is also the single source of truth for registration.
- **Storage (independent of V3, no sync):**
  - `happyaddons_atomic_integration_enabled` (`'off'` by default, the global switch).
  - `happyaddons_active_atomic_widgets` / `happyaddons_active_atomic_features`, an **active list**, not an inactive list.
  - If the option is absent, the registry's `is_active` flags give the defaults (Image Cycle/Info Box/AIA/HTA on, Card off). The option is written only when the user first saves. This is what meets "match V3 defaults without repeatedly syncing". An inactive-list model could not express Card-off by default.
  - V3 options (`happyaddons_inactive_*`) are never read or written by Atomic code.
- Save: `Dashboard::save_data()` already fires `happyaddons_save_dashboard_data`. Add handlers keyed on a hidden `atomic_scope` marker (the pattern from `936a052c`), so saving with the V4 panel not rendered never wipes settings, and a V3-only save never touches Atomic.

### UI

- Reuse `936a052c`'s ideas: a `.ha-version-switcher` (V3 | V4) in the **Widgets, Features, Extensions, GSAP** tabs, and `switchDashboardVersion()` in `dashboard.js`.
- **Widgets tab V4:** 3 cards. **Features tab V4:** 2 cards. **Extensions and GSAP tabs V4:** an empty-state notice, with no registrations or placeholders.
- The global Atomic switch card is shown once (Widgets tab). While it is off, the V4 panels render a short "Enable Atomic integration" notice instead of the lists.
- Filters (All/Free/Pro) and Enable/Disable All reuse the existing `.ha-action--btn` handler, scoped to the **visible panel** (`.ha-version-panel[data-version=…]`) and category, so a bulk action can't touch the other version. The Pro filter on V4 yields an empty list.
- Persist the chosen version in `localStorage` (try/catch). Default V3.
- No new JS framework. Edits stay in `assets/dev/admin/js/dashboard.js`.

### Performance (existing bottlenecks → minimal fixes)

| # | Bottleneck (verified) | Fix |
|---|---|---|
| 1 | Analytics tab rendered on **every** dashboard load; `get_raw_usage()` → `get_formatted_usage()` ×3, called twice (~6 Elementor usage computations, instantiating all widgets) (`dashboard.php:437-441`, `dashboard-analytics.php:11-12`) | Read `\Elementor\Modules\Usage\Module::OPTION_NAME` directly (as `936a052c` did) and render the analytics tab body only when its tab is active (lazy, via the hash route) |
| 2 | `get_local_widgets_map()` (~950-line literal, ~159 `__()`) rebuilt per call from ≥8 callers | `static` memo inside `Widgets_Manager::get_local_widgets_map()` and the features map. Behaviour is unchanged because only the first call evaluates `__()` |
| 3 | `get_widgets()/get_features()/get_gsap_*()` re-merged + `uksort` in 4 templates | Memoize in `Dashboard` (one property per getter) |
| 4 | `get_tabs()` computed ~3×/request | Memoize |
| 5 | GSAP items duplicated in DOM across 3 tabs | Left as is (V3 behaviour; not in scope), noted |
| 6 | Save → full `location.reload()` | Left as is (V3 behaviour; no change) |
| 7 | V4 additions | Static registry; **no class instantiation, no extra queries, no AJAX**. A single `get_option` per Atomic list (autoloaded) |

Dashboard loading is measured before and after (Phase 2, manual: query count and render time) to confirm there is no regression.

---

## 6. Registration Infrastructure (Atomic)

New files:
- `classes/atomic-registry.php`: catalog (§5).
- `classes/atomic-manager.php`: `is_supported()` = global switch on **and** Elementor ≥ 4.0 **and** `class_exists(Atomic_Widget_Base)` **and** `e_atomic_elements` active (new vs `936a052c`). Registers only enabled components, with `try/catch` and `class_exists` guards, and bails silently (admin notice only on the dashboard) if the environment is unsupported.
- Hooks, all in `base.php` next to existing ones:
  - `elementor/widgets/register` → `Atomic_Manager::register_widgets()` (leaf: Image Cycle).
  - `elementor/elements/elements_registered` → containers (Info Box, Card).
  - `elementor/elements/categories_registered` → category `happy_addons_atomic`. It is registered only when ≥1 widget is enabled.
- Element types: `ha-a-image-cycle`, `ha-a-infobox`, `ha-a-card`. The `ha-a-` prefix prevents collisions with V3 `ha-*`.
- Features: `Extensions\Atomic\Appearing_Image_Animation` and `Extensions\Atomic\Text_Animation`, each a singleton with `init()`, called by `Atomic_Manager::init_features()` only when enabled. They add the two filters, a `before_render` collector, and a footer JSON emitter.
- Duplicate-registration guard: a `static $registered` flag and a check of `widgets_manager->get_widget_types()` before registering.

---

## 7. File-Level Architecture

**New (Free)**
```
classes/atomic-registry.php
classes/atomic-manager.php
widgets/atomic/image-cycle/widget.php        (+ image-cycle.html.twig)
widgets/atomic/infobox/widget.php            (+ container twig)
widgets/atomic/card/widget.php               (+ container twig)
extensions/atomic/appearing-image-animation.php
extensions/atomic/text-animation.php
assets/dev/sass/atomic/image-cycle.scss
assets/dev/sass/atomic/infobox.scss
assets/dev/sass/atomic/card.scss
assets/dev/js/happy-addons-atomic.js
templates/admin/dashboard-atomic-settings.php   (global switch card)
```
(AIA/HTA SCSS only if Phase 7/8 find a selector mismatch.)

**Modified (Free)**, small and additive
- `base.php`: 3 hook registrations + `init`.
- `inc/functions-extensions.php`: `ha_is_atomic_integration_enabled()`, `ha_is_atomic_environment_supported()`.
- `classes/dashboard.php`: memoization, lazy analytics, atomic save handler, `get_atomic_*()` getters.
- `classes/widgets-manager.php` / `classes/extensions-manager.php`: static memo of the local maps only.
- `templates/admin/dashboard-{widgets,features,extensions,gsap,analytics}.php`: version switcher and panels, lazy analytics.
- `assets/dev/admin/js/dashboard.js` (+ matching dashboard sass): version switcher, scoped bulk actions.
- `classes/assets-manager.php`: register `happy-elementor-addons-atomic` and the three atomic CSS handles (register-only; enqueue happens via `get_*_depends` / feature enqueue).
- `gulpfile.mjs`: exclude `sass/atomic/**` from the `main.css` concat.

**Pro:** no file changes this phase. Verified in Phase 10 that the Pro dashboard filters (`happyaddons_get_*_map`, `happyaddons_dashboard_get_tabs`) don't break with the new panels.

**Not touched:** every V3 widget, extension and asset, and `widgets/<v3-slug>/`.

---

## 8. Phased Implementation

1. **Source analysis and API spike.** In a scratch Elementor 4.3.5 editor, confirm: icon child element availability; `Repeatable_Control` for the Image Cycle items; dynamic-tag support per prop; `{braces}` round-trip in `e-heading`; `e-image`/`e-heading` extension via the filters; per-breakpoint CSS-variable style variants (L3); CSS filter and hover state support (L2). The result updates the parity matrices before any build.
2. **Dashboard performance and settings.** Items 1-4 (§5), measured before/after. Add the atomic option model and save handler. Behind no visible change for V3.
3. **Registration infrastructure.** Registry, manager, hooks, global switch, version-switcher UI, empty states, scoped bulk actions.
4. **Image Cycle.** Leaf widget, props with V3 defaults, Twig reproducing `ha-ic-*` markup, base styles, SCSS (shared classes), engine port, lifecycle.
5. **Info Box.** Container with default children, root layout props, SCSS, Pro-scope check (no masking/presets).
6. **Card.** Same pattern; default **off**; badge position enum.
7. **Appearing Image Animation.** Filters, collector, footer config, handler port, reduced motion, cleanup, mobile switch.
8. **Text Animation.** Same; fix custom trigger and `||` fallbacks; verify the `{braces}` highlight.
9. **Asset build and conditional loading.** Gulp exclusion, compiled outputs, handle registration, load only when an enabled component renders.
10. **Free/Pro compatibility, security, performance review.** Sanitization, capability and nonce checks on the new save path, escape on output, Pro-active/inactive matrix, final dashboard timing.

No automated tests are written or run. Verification is manual (below).

---

## 9. Acceptance Criteria

- **Parity:** for all 5 components, every V3 control with a supported Atomic route (§3) exists with the same default; every limitation (L1-L4, `{braces}` if applicable) is listed in the release notes. None is claimed as complete.
- **Defaults:** the first dashboard load with no saved option yields Image Cycle/Info Box/AIA/HTA on, Card off, global Atomic switch off. No sync runs per request.
- **Rendering:** editor preview and frontend markup match for each widget; reduced-motion users see the final state without animation.
- **Independence:** V3 and V4 registrations are separate (`ha-*` vs `ha-a-*`), settings are separate options, and saving or bulk-acting in one version never changes the other.
- **Dashboard:** V4 panels show exactly 3 widgets / 2 features; Extensions/GSAP show empty states; filters and bulk actions work per panel. Dashboard time and query count are not worse than the pre-change baseline, and are better thanks to fixes 1-4.
- **Assets:** no duplicate handle registered, nothing enqueued twice, and no Atomic asset loads when the integration/component is off or the page has no Atomic component. V3 handles unchanged, and `main.css` is unchanged by Atomic SCSS.
- **No extras:** exactly 5 Atomic components are registered, with no placeholders.
- **Compatibility:** with the Atomic experiment off, Elementor < 4.0, or Pro absent/active, there are no PHP notices or fatals, V3 is unaffected, and the V4 section shows a clear notice.
- **Security/failure:** capability + nonce on save; all new inputs sanitized (enum whitelist, `absint`/`floatval` clamps, `sanitize_hex_color`); outputs escaped; footer JSON encoded with `wp_json_encode`; all registration wrapped to fail safely.

## 10. Verification (manual, end to end)

1. Atomic experiment on, Elementor 4.3.5: enable the global switch; add each widget in the editor, edit every control group, and compare against V3 on the same content.
2. Apply AIA to an `e-image` and HTA to an `e-heading` in each mode; scroll test; editor re-render (change a control and confirm there are no duplicate ScrollTriggers/HTML split); preview with reduced-motion enabled.
3. Dashboard: toggle V3/V4, Free/Pro, Enable All/Disable All per panel; save; reload; confirm the other version's settings are unchanged. Compare load time and DB query count with the pre-change baseline (e.g. Query Monitor).
4. Network/asset check: pages without Atomic components load no Atomic assets; no handle appears twice.
5. Disable the experiment, then Pro: confirm graceful behaviour.

## Decisions for your review

- **D1: SCSS path.** You wrote `assets/dev/scss/atomic/`; the real directory is `assets/dev/sass/`. The plan uses `assets/dev/sass/atomic/` to match the existing build. Say if you want it renamed.
- **D2: gulp edit.** Excluding `sass/atomic/**` from the `main.css` concat is the one build-file change.
- **D3: deliberate V3 bug fixes in the Atomic ports** (custom trigger honored, 0-value fallbacks, per-instance listeners, reduced motion). Alternatively we port V3 behaviour bug-for-bug; the plan recommends the fixes.
- **D4: Info Box / Card as container compositions** (native children) versus leaf widgets. Containers give real per-part style parity but depend on the Phase 1 spike; if it fails, the fallback is a leaf widget with Happy prop-based style controls (lower parity).
- **Not carried over from `936a052c`:** its hand-written `assets/atomic/*`, its other 8 widgets and 4 extensions, and its three-list (widgets/features/extensions) option model.
