commit c7b2c38eb95cb3177bffe7b130f018e4d35bb44e Author: Keith Solomon Date: Wed Aug 5 12:09:36 2026 -0500 Add design spec for Filtered Image block plugin diff --git a/docs/superpowers/specs/2026-08-05-image-filters-design.md b/docs/superpowers/specs/2026-08-05-image-filters-design.md new file mode 100644 index 0000000..3f4769e --- /dev/null +++ b/docs/superpowers/specs/2026-08-05-image-filters-design.md @@ -0,0 +1,210 @@ +# Filtered Image — WordPress Block Plugin + +**Date:** 2026-08-05 +**Status:** Approved (pending user review of this written spec) +**Author:** Brainstorming session + +## Purpose + +A WordPress plugin that lets users add Instagram-style visual filters to images in the block editor. Filters are applied purely in CSS at render time, so they are reversible, lightweight, and accessibility-safe. + +## Goals + +1. Let users pick a named filter preset and apply it to an image in the block editor. +2. Let users adjust the filter's intensity (0–100%). +3. Keep the rendered image accessible — alt text always describes the original image content, not the visual styling. +4. Keep the plugin small, well-organized, and easy to maintain. + +## Non-Goals + +- Server-side image processing or pixel manipulation. +- Per-channel sliders (exposure, warmth, vignette). Out of scope for v1. +- Block Style variations on `core/image`. Out of scope for v1. +- Block Bindings source. Out of scope for v1. +- A "bake" action that flattens the filter to a real image. Out of scope for v1. + +## Architecture + +### Plugin shape + +A standard modern Gutenberg block plugin using `wp-scripts` for the build. + +``` +image-filters/ +├── image-filters.php # Plugin header + bootstrap +├── readme.txt +├── package.json # wp-scripts build config +├── src/ +│ ├── index.js # Block registration entry point +│ ├── block.json # Block metadata +│ ├── edit.js # Editor component +│ ├── save.js # Static markup +│ ├── inspector.js # Filter panel: preset grid + intensity slider +│ ├── presets.js # Single source of truth for preset definitions +│ ├── hooks.js # Optional: filter presets extension point +│ └── styles/ +│ ├── editor.scss # Editor-only styles +│ └── style.scss # Front-end + editor styles +├── build/ # Generated by wp-scripts build (gitignored) +└── languages/ +``` + +### Why this shape + +- `presets.js` is the single source of truth for what each preset is. The CSS file is the single source of truth for how each preset is rendered. Adding a new preset = one entry in `presets.js` + one CSS rule. +- No file is bigger than ~50 lines. Each file does one thing. +- The plugin builds with the standard `wp-scripts` workflow. + +## Block Definition + +**Block name:** `ksolo/image-filter` +**Title:** "Filtered Image" +**Category:** Media +**Icon:** a filter / camera icon +**Description:** "An image with an Instagram-style filter applied." + +### Attributes + +| Attribute | Type | Default | Purpose | +|--------------|---------|--------------|--------------------------------------------------------| +| `filter` | string | `"normal"` | Preset slug. One of the preset slugs defined in `presets.js`. | +| `intensity` | number | `100` | 0–100. Intensity slider value. | +| `imageId` | number | `0` | Attachment ID (matches `core/image`). | +| `imageUrl` | string | `""` | Image URL (matches `core/image`). | +| `imageAlt` | string | `""` | Alt text (matches `core/image`). | +| `width` | number | `undefined` | Display width (matches `core/image`). | +| `height` | number | `undefined` | Display height (matches `core/image`). | +| `linkUrl` | string | `""` | Optional link target (matches `core/image`). | +| `caption` | string | `""` | Optional caption (matches `core/image`). | + +### Supports + +- `align: ['left', 'center', 'right', 'wide', 'full']` — same as core Image +- `anchor: true` +- `html: false` +- `spacing: { margin: true }` + +We do not duplicate `duotone`, `filter`, or `title` — those are core Image's, and we don't add them. + +### Transforms + +- **To `core/image`:** copies `imageId`, `imageUrl`, `imageAlt`, `width`, `height`, `linkUrl`, `caption`. Drops `filter` and `intensity`. +- **From `core/image`:** copies the same 7 image attributes. Sets `filter: 'normal'`, `intensity: 100`. + +## Components + +### `presets.js` + +The single source of truth for the preset list. Exports an array of `{ slug, label, color }` objects and a `DEFAULT_PRESET` constant. + +```js +export const PRESETS = [ + { slug: 'normal', label: 'Normal', color: '#f0f0f0' }, + { slug: 'warm', label: 'Warm', color: '#f4a261' }, + { slug: 'cool', label: 'Cool', color: '#a8dadc' }, + { slug: 'vivid', label: 'Vivid', color: '#e63946' }, + { slug: 'fade', label: 'Fade', color: '#cdb4db' }, + { slug: 'mono', label: 'Mono', color: '#6c757d' }, + { slug: 'dramatic', label: 'Dramatic', color: '#1d3557' }, + { slug: 'sepia', label: 'Sepia', color: '#d4a373' }, +]; + +export const DEFAULT_PRESET = 'normal'; +``` + +### `block.json` + +Standard Gutenberg block metadata. Attributes, supports, and transforms all declared here so PHP and JS both read the same source. + +### `edit.js` + +Thin editor component. Handles the MediaPlaceholder (image selection), renders the image with the filter class, defers to `Inspector` for the panel. + +### `inspector.js` + +The filter panel. Two children: +- A grid of `