# IM-Press v4.7.0 — Implementation Plan

**Codename:** "Depth"
**Source of scope:** [bb-uabb-parity-audit.md](./bb-uabb-parity-audit.md)
**Goal:** close BB+UABB capability-depth gaps so IM-Press blocks are as deep as their BB counterparts on the dimensions BB users actually configure in production.
**Product north star:** *Build a tool that helps AI build beautiful WP websites and lets users tweak the data — SEO optimized, user-friendly, agent-friendly.*

---

## Discovery: Track A infrastructure is already built

`includes/class-block-animations.php` (1,312 lines) already does this for every `imp/*` block via `register_block_type_args` + `render_block` filters:

- **18 animation attrs auto-added** to every IM-Press block: `animation`, `animationDelay`, `animationDuration`, `animationOffset`, `animationOnce`, `hoverEffect`, `hoverDuration`, `hoverCurve`, `parallaxType`, `parallaxSpeed`, `scrollTransform`, `scrollStart`, `scrollEnd`, `scrollValues`, `textAnimation`, `stagger`, `staggerDelay`, `staggerAnimation`
- **24 entrance animations** ready (fadeIn/fadeInUp/slideInLeft/zoomIn/bounceIn/flipInX/rotateIn/lightSpeedIn/rollIn/jackInTheBox/headShake/etc.)
- **12 hover effects** ready (grow/shrink/pulse/float/bob/shadow/shadowGrow/brighten/darken/grayscale toggles/blur)
- **5 text animations** ready (typewriter/splitReveal/gradient/glow/underlineSlide)
- **CSS keyframes** auto-injected into `wp_footer` only when used
- **JS runtime** uses IntersectionObserver for scroll triggering

**What's missing:** the InspectorControls UI to surface these attrs in the editor. Right now the attrs exist on every block but the editor sidebar doesn't render pickers for them, so AI agents *can* set them via REST/block markup but human users *can't* set them in Gutenberg unless they edit block markup directly.

That changes the work. Track A becomes:
- **A1 (largest):** Universal InspectorControls panel — one shared React component injected into every `imp/*` block's editor sidebar, exposing animation/hover/parallax/scroll pickers.
- **A2:** New trait `Block_Link_Controls` — `linkTarget`/`linkRel` attrs auto-added to blocks that have a URL field. Same `register_block_type_args` pattern.
- **A3:** New trait `Block_Hover_State` — `hoverColor`/`hoverBackground`/`hoverOpacity` attrs auto-added to visual blocks. Render via inline CSS.
- **A4:** New trait `Block_Responsive_Alignment` — `alignmentMobile` attr auto-added to text-bearing blocks. Render via responsive CSS rule.

---

## Track A — Cross-cutting traits (~1 sprint)

### A1: Universal block animation/effects InspectorControls panel

**Goal:** every `imp/*` block sidebar gets a collapsible "Effects & Animation" panel with the same controls.

**Approach:**
1. New file `src/shared/EffectsPanel.js` — React component that uses `@wordpress/components` (PanelBody, SelectControl, RangeControl, ToggleControl)
2. New file `src/shared/extendInspector.js` — uses `addFilter('editor.BlockEdit', ...)` + `addFilter('blocks.registerBlockType', ...)` to inject the EffectsPanel into every block whose name starts with `imp/`. WordPress core pattern, no per-block edit.
3. Webpack entry `effects-panel.js` → built file at `assets/js/effects-panel.min.js`
4. Enqueue in editor only via `enqueue_block_editor_assets` from a new `class-effects-panel.php` glue class.

**Sections in the panel:**
- Entrance Animation: dropdown (24 options, grouped: Fade / Slide / Zoom / Bounce / Flip / Rotate / Special / None) + delay slider (0–3000ms) + duration slider (200–2000ms) + "Trigger once" toggle
- Hover Effect: dropdown (12 options) + duration slider + easing select (linear/ease/ease-in/ease-out/ease-in-out)
- Advanced — collapsed by default — Parallax (type + speed), Scroll Transform (type + start/end %), Text Animation, Stagger (for blocks with children: features-grid, gallery, team)

**Defaults:** all `none` / `0` / `false`. Zero behavior change for existing pages.

### A2: Block_Link_Controls trait

**File:** `includes/class-block-link-controls.php`
**Pattern:** mirror Block_Animations.

```php
class Block_Link_Controls {
    private const BLOCKS_WITH_LINKS = [
        'imp/heading', 'imp/button', 'imp/icon', 'imp/photo',
        'imp/info-box', 'imp/callout', 'imp/marketing-button',
        'imp/dual-button', 'imp/creative-link', 'imp/image-text',
    ];
    public static function init() { /* hooks */ }
    public function add_attrs($args, $block_type) {
        if (!in_array($block_type, self::BLOCKS_WITH_LINKS)) return $args;
        $args['attributes']['linkTarget'] = ['type' => 'string', 'default' => '_self'];
        $args['attributes']['linkRel']    = ['type' => 'string', 'default' => ''];
        return $args;
    }
    public function apply_at_render($content, $block) { /* DOM-edit anchor tags */ }
}
```

**InspectorControls integration:** add a "Link" sub-section to the EffectsPanel (or sibling panel) for blocks in BLOCKS_WITH_LINKS — target dropdown (`_self`/`_blank`/`_parent`/`_top`) + rel checkboxes (nofollow / sponsored / ugc / noopener — auto-set noopener on `_blank`).

### A3: Block_Hover_State trait

**File:** `includes/class-block-hover-state.php`
**Applies to:** visual blocks only — `imp/photo`, `imp/info-box`, `imp/callout`, `imp/flip-box`, `imp/icon`, `imp/button`, `imp/team`, `imp/logo-grid`.

**Attrs added:**
- `hoverColor` — text color on hover
- `hoverBackground` — background color on hover
- `hoverOpacity` — number 0–100, opacity on hover
- `hoverBorderColor` — border color on hover
- `hoverScale` — number 0.8–1.3, transform scale on hover
- `hoverTransitionDuration` — number ms (default 300)

**Render:** at `render_block` time, inject scoped CSS using a unique block class + `:hover` selector. No JS required.

### A4: Block_Responsive_Alignment trait

**File:** `includes/class-block-responsive-alignment.php`
**Applies to:** text-bearing blocks — `imp/heading`, `imp/rich-text`, `imp/button`, `imp/icon`, `imp/cta-section`, `imp/callout`, `imp/info-box`, `imp/dual-heading`, `imp/dual-button`, `imp/fancy-text`.

**Attrs added:**
- `alignmentMobile` — string `inherit` (default) | `left` | `center` | `right`

**Render:** inject `@media (max-width: 767px) { .imp-block-{id} { text-align: {value}; } }` via the existing Responsive CSS pipeline (`class-responsive.php` already has a per-page custom CSS hook).

### A5: Fluid typography baseline

**Decision:** fluid-only, no per-breakpoint override. Single `fontSize` per block, scaled with `clamp()`.

**File:** patch existing typography rendering wherever `fontSize` is consumed in render_callbacks.

**Pattern:**
```php
// Old
$fs = $attrs['fontSize'] ?? '1rem';
$style .= "font-size: {$fs};";

// New — small helper in class-blocks-helpers.php
$fs = \IMPress\Blocks_Helpers::fluid_font_size($attrs['fontSize'] ?? '1rem');
$style .= "font-size: {$fs};";
```

`fluid_font_size()` takes a CSS size value and returns a `clamp(min, vw-scaled-preferred, max)` expression with a sane scale factor. Handles `px`, `rem`, `em`. Bypasses if value is already a `clamp()`/`calc()`/`var()` expression.

Apply in: `imp/heading`, `imp/rich-text` (lineHeight stays static), `imp/cta-section`, `imp/callout`, `imp/dual-heading`, `imp/info-box`, `imp/numbers` (number_size), and any block with explicit fontSize.

---

## Track B — High-frequency block depth (~2 sprints)

Blocks deepened in ranked order. Each block gets a focused PR with the missing attrs added to `register_block_type` schema, render_callback updated, and editor `edit.js` updated to surface the new controls.

### B1: `imp/info-box` — image/icon toggle, photo position, separator

Current attrs: `backgroundColor, borderColor, description, icon, iconColor, title` (6).

**Adding:**
- `mediaType` — `'icon'` | `'image'` (default `'icon'`) — switches between icon picker and image upload
- `image` — string (URL), `imageId` — number (attachment ID)
- `mediaPosition` — `'above'` | `'left'` | `'right'` | `'below'` (default `'above'`)
- `mediaSize` — number px or `auto` (default `64`)
- `enableSeparator` — boolean (default `false`)
- `separatorColor` — string
- `separatorWidth` — number px (default `40`)
- `separatorPosition` — `'below_title'` | `'below_description'` (default `'below_title'`)
- `titleTag` — `'h2'` | `'h3'` | `'h4'` | `'h5'` | `'h6'` (default `'h3'`)
- `verticalAlign` — `'top'` | `'middle'` | `'bottom'`
- `linkUrl` — string (use Block_Link_Controls trait for target/rel)
- `linkScope` — `'icon'` | `'title'` | `'whole_box'` (default `'whole_box'`)

### B2: `imp/callout` — title_tag, photo_position, separator, vertical_align

Current attrs: `backgroundColor, borderRadius, buttonText, buttonUrl, heading, hoverEffect, icon, image, imageSize, layout, text` (11).

**Adding:**
- `titleTag` — h1–h6 (default `'h2'`)
- `mediaType` — `'icon'` | `'image'` | `'none'` (default `'image'`)
- `mediaPosition` — `'above'` | `'left'` | `'right'` | `'below'` (extends existing `layout`)
- `iconSize` — number px
- `verticalAlign` — top/middle/bottom
- `enableSeparator` — boolean
- `separatorColor`, `separatorWidth`, `separatorStyle` (`solid`/`dashed`/`dotted`)
- `ctaType` — `'button'` | `'link'` | `'none'` (default `'button'`)

### B3: `imp/heading` — link, target, nofollow, subheading

Current attrs: `color, fontSize, fontWeight, letterSpacing, level, text, textAlign, textTransform` (8).

**Adding:**
- `linkUrl` — string (Block_Link_Controls trait covers target/rel)
- `subheading` — string (text rendered below heading in smaller weight)
- `subheadingTag` — `'div'` | `'h6'` | `'p'` (default `'div'`)
- `subheadingColor`, `subheadingSize`, `subheadingPosition` (`above`/`below`)
- `enableSeparator` — boolean (visual divider between heading and subheading)
- `separatorColor`, `separatorWidth`, `separatorStyle`, `separatorPosition` (`above_heading`/`between`/`below_subheading`)

### B4: `imp/button` — click_action, custom_width, icon_animation

Current attrs: `backgroundColor, borderRadius, fullWidth, icon, iconPosition, size, style, target, text, textColor, url` (11).

**Adding:**
- `clickAction` — `'link'` (default) | `'lightbox_video'` | `'lightbox_iframe'` | `'lightbox_inline'` | `'email'` | `'phone'` | `'sms'` | `'scroll_to_anchor'` | `'copy_to_clipboard'`
- `clickActionPayload` — string (URL for lightbox, anchor ID for scroll, email address for mailto, etc.)
- `customWidth` — number px or `''` for auto
- `customHeight` — number px or `''`
- `iconAnimation` — `'none'` | `'slide_in'` | `'fade_in'` | `'pulse'` | `'shake'`
- (linkRel comes from Block_Link_Controls trait)
- `transitionEffect` — `'fade'` | `'slide'` | `'scale'` | `'none'`

Lightbox click actions reuse existing `imp/modal` rendering via JS dispatch.

### B5: `imp/numbers` — number_type variants, animation_speed, position

Current attrs: `animationType, circleBar, circleBarColor, color, duration, fontSize, format, label, labelColor, labelFontSize, layout, number, prefix, suffix` (14).

**Adding:**
- `numberType` — `'digits'` (default, current) | `'circle_progress'` | `'horizontal_bar'` | `'pie'`
- `numberPosition` — `'above_label'` | `'below_label'` | `'beside_label'`
- `circleSize` — number px (when numberType=circle_progress)
- `circleStrokeWidth` — number px (default 8)
- `circleBackgroundColor`
- `barHeight` — number px (default 16)
- `barBackgroundColor`
- `targetValue` — number (the "denominator" for circle/bar — e.g. 75 of 100)
- `animationSpeed` — number ms (default 1500)
- `animationDelay` — already covered by Block_Animations trait
- `triggerOnView` — boolean (use IntersectionObserver, default `true`)

### B6: `imp/photo` — photo_source, hover_opacity, grayscale

Current attrs: `alt, borderRadius, caption, captionPosition, hoverEffect, lightbox, link, linkTarget, maxWidth, shadow, shape, url` (12).

**Adding:**
- `photoSource` — `'media_library'` (default) | `'external_url'` | `'unsplash'`
- `externalUrl` — string (when photoSource=external_url)
- `unsplashId` — string (when photoSource=unsplash; reuses `class-image-fetcher.php`)
- `opacity` — number 0–100 (default 100)
- `grayscale` — number 0–100 (default 0)
- `hoverOpacity` — number 0–100 (covered by Block_Hover_State trait — but expose explicitly in panel)
- `hoverGrayscale` — number 0–100
- `titleHover` — string (overlay caption shown on hover)
- `titleHoverStyle` — `'fade_in'` | `'slide_up'` | `'slide_down'` | `'zoom'`

### B7: `imp/modal` — content_type variants, video controls

Current attrs: `buttonText, content, title, triggerId, triggerText` (5).

**Adding:**
- `contentType` — `'inline'` (default, current) | `'video'` | `'iframe'` | `'image'` | `'shortcode'` | `'page_id'`
- `videoUrl` — YouTube/Vimeo URL (auto-detect provider)
- `videoAutoplay` — boolean
- `videoMuted` — boolean (default true if autoplay)
- `videoLoop` — boolean
- `youtubeShowControls` — boolean (default true)
- `youtubeRelatedVideos` — boolean (default false)
- `iframeUrl` — string
- `iframeHeight` — string (e.g. `500px`)
- `pageId` — number (load a WP page's content into the modal)
- `triggerType` — `'click'` (default) | `'page_load'` | `'time_on_page'` | `'scroll_depth'` | `'exit_intent'`
- `triggerDelay` — number ms (when triggerType=time_on_page)
- `triggerScrollPercent` — number 0–100
- `previewMode` — boolean (editor-only, force-open in Gutenberg preview)

### B8: `imp/divider` — align, height, custom_width, opacity

Current attrs: `color, spacing, style, thickness, width` (5).

**Adding:**
- `alignment` — `'left'` | `'center'` (default) | `'right'`
- `customHeight` — number px (default current behavior)
- `customWidth` — string `'100%'` (default) or `'200px'` etc.
- `opacity` — number 0–100 (default 100)
- `glyph` — string (optional centered icon/text — for "fancy divider" effect)
- `glyphSize` — number px

---

## Track C — Net-new blocks (~0.5 sprint)

### C1: `imp/image-hover`

**Replaces UABB ihover.** CSS-driven photo overlay block.

**File:** new `imp/image-hover` block in `class-blocks-extra.php` plus optional `src/blocks/image-hover/` for v1-style React edit.

**Attributes:**
- `imageUrl`, `imageAlt`, `imageId`
- `effect` — `'fade'` | `'slide_up'` | `'slide_down'` | `'slide_left'` | `'slide_right'` | `'zoom_in'` | `'zoom_out'` | `'rotate'` | `'curtain'` | `'flip'`
- `overlayColor` — rgba (default `rgba(0,0,0,0.6)`)
- `overlayOpacity` — 0–100
- `title` — string shown on hover
- `titleColor`, `titleSize`
- `description` — string shown on hover
- `descriptionColor`, `descriptionSize`
- `linkUrl` — string (whole image becomes clickable)
- `transitionDuration` — number ms (default 400)
- `aspectRatio` — `'1:1'` | `'4:3'` | `'16:9'` | `'auto'`

**Render:** wrapper div with `position: relative; overflow: hidden`, image as `<img>`, overlay as absolute-positioned div with text. CSS transitions on the overlay's `opacity` + `transform` based on `:hover` state. Zero JS.

### C2: `imp/audio`

**HTML5 audio block with cover art.**

**File:** new `imp/audio` block in `class-blocks-extra.php`.

**Attributes:**
- `audioUrl` — string
- `audioId` — number (media library attachment) — use Image_Fetcher pattern for source picker
- `provider` — auto-detected from URL: `'self_hosted'` | `'soundcloud'` | `'spotify'` (existing `imp/embed` already covers SoundCloud/Spotify, so this is mostly self-hosted MP3/OGG/WAV + cover)
- `coverImage` — string (URL)
- `coverImageId` — number
- `title` — string
- `artist` — string
- `controls` — boolean (default true)
- `autoplay` — boolean (default false; warn in editor about UX)
- `loop` — boolean (default false)
- `muted` — boolean (default false)
- `preload` — `'none'` | `'metadata'` | `'auto'`
- `theme` — `'minimal'` | `'card'` | `'banner'` (visual treatments)

**Render:** native `<audio controls>` with optional surrounding cover-art card.

---

## Work order

**Sprint 1 (Track A):**
1. A1 — Universal EffectsPanel React component + extendInspector filter + webpack entry + glue class. Test with one block (`imp/heading`) end-to-end.
2. A2 — Block_Link_Controls trait + InspectorControls panel section. Test by setting target/rel on `imp/button` and `imp/heading` and verifying frontend rel/target.
3. A3 — Block_Hover_State trait. Visual smoke test on `imp/photo`.
4. A4 — Block_Responsive_Alignment trait. Mobile alignment override test.
5. A5 — Fluid typography helper. Apply to top 5 blocks. Visual regression on existing pages.

**Sprint 2 (Track B 1–4):**
6. B1 info-box, B2 callout, B3 heading, B4 button — in that order. Each its own PR with smoke-test page.

**Sprint 3 (Track B 5–8 + Track C):**
7. B5 numbers, B6 photo, B7 modal, B8 divider, C1 image-hover, C2 audio.

**Sprint 4 (release):**
8. Bump `IMPRESS_VERSION` to `4.7.0` in `im-press.php`
9. Update `CHANGELOG.md` with full entry following existing format
10. Update `README.md`'s feature list (add product north star line at top)
11. Update `docs/research/v470-plan.md` with "Status: shipped" footer
12. Run smoke test (`~/im-press-smoke-test.sh`) — target 10 sites × 27+ endpoints all passing
13. Build canonical ZIP, upload to airobotbuild.com/wp-content/uploads/im-press.zip
14. Deploy via `~/deploy-impress-update.sh`
15. Tag `v4.7.0` on GitHub `hitechclawz/im-press`
16. Update Aims' SITE-STATE.md with new version

---

## Anti-patterns to avoid (carry-forward from prior sessions)

- **Never** `register_rest_route()` outside a `rest_api_init` callback — premature `rest_get_server()` flushes other modules' route registrations (Session 54)
- **Never** `__CLASS__` at file scope — resolves to `""` outside a class definition, fatal error
- **Never** `{$self::$prop}` inside double-quoted strings — `$self` is a variable, not a keyword. Use `" . self::$prop . "` concatenation
- **Never** mix `init()` patterns in bootstrap — Block_Animations is `::get_instance()->init_hooks()`, Blocks_V2 is `::instance()->init()`, Blocks_WP7 is `::init()`. Pick the right call per class.
- **Never** `wp_update_post()` on pages with inline `<style>` blocks — use `$wpdb->update($wpdb->posts, ...)` to bypass `wp_kses_post`
- **Never** arrow functions in JS injected into WP post_content — `=>` is HTML-encoded to `=&gt;`
- **Always** verify with smoke test before declaring deploy done
- **Always** clear OPcache via HTTP curl to a temp PHP file after deploys (CLI/touch don't clear web-server OPcache on Hostgator)

---

## Out of scope (tracked for v4.8.0+)

- **Site Theme Mode** — global header/footer with nav bar living in a Header template, per-page override (v4.8.0)
- **BB Theme Builder-style conditional display rules** — show this header on posts only, that one on pages, etc. (v4.9.0+, only if Site Theme Mode toggle alone proves insufficient)
- **Sidebar / widget-area block** — deferred indefinitely, sidebars are a dying pattern
- **Per-breakpoint typography overrides** — fluid-only by deliberate choice
- **Saved rows / global modules** — pattern can already be approximated with reusable block patterns; revisit if there's demand
