diff --git a/native/wordpress/maple-icons-wp/CLAUDE.MD b/native/wordpress/maple-icons-wp/CLAUDE.MD
new file mode 100644
index 0000000..00e1b07
--- /dev/null
+++ b/native/wordpress/maple-icons-wp/CLAUDE.MD
@@ -0,0 +1,827 @@
+# CLAUDE.md — Maple Icons WordPress Plugin
+
+## Quick Reference
+
+| Document | When to Reference |
+|----------|-------------------|
+| **CLAUDE.md** (this file) | Architecture, file structure, build order |
+| **SECURITY.md** | Writing ANY PHP code (same patterns as Maple Local Fonts) |
+
+---
+
+## Project Overview
+
+Build a WordPress plugin called **Maple Icons** that:
+1. Fetches open-source icon sets from CDN and stores them locally
+2. Provides preset icon sets: Heroicons, Lucide, Feather, Phosphor, Material
+3. Only ONE icon set can be active at a time (though multiple can be downloaded)
+4. Offers a Gutenberg block "Maple Icons" for inserting icons
+5. Icons use `currentColor` to inherit text color from Global Styles
+
+**Key principle:** Download once, serve locally. Zero runtime external requests after initial fetch.
+
+---
+
+## Requirements
+
+- **Minimum PHP:** 7.4
+- **Minimum WordPress:** 6.5 (for block.json apiVersion 3 and Global Styles)
+- **License:** GPL-2.0-or-later
+
+---
+
+## Preset Icon Sets
+
+All icons fetched from jsdelivr CDN with pinned versions:
+
+| Set | Package | Version | Styles | ~Icons | viewBox |
+|-----|---------|---------|--------|--------|---------|
+| Heroicons | `heroicons` | 2.1.1 | outline, solid, mini | 290 | 24×24 |
+| Lucide | `lucide-static` | 0.303.0 | icons | 1,400 | 24×24 |
+| Feather | `feather-icons` | 4.29.1 | icons | 280 | 24×24 |
+| Phosphor | `@phosphor-icons/core` | 2.1.1 | regular, bold, light, thin, fill, duotone | 1,200× styles | 256×256 (normalize) |
+| Material | `@material-design-icons/svg` | 0.14.13 | filled, outlined, round, sharp, two-tone | 2,500 | 24×24 |
+
+### CDN URL Patterns
+
+```
+Heroicons: https://cdn.jsdelivr.net/npm/heroicons@2.1.1/24/outline/{name}.svg
+Lucide: https://cdn.jsdelivr.net/npm/lucide-static@0.303.0/icons/{name}.svg
+Feather: https://cdn.jsdelivr.net/npm/feather-icons@4.29.1/dist/icons/{name}.svg
+Phosphor: https://cdn.jsdelivr.net/npm/@phosphor-icons/core@2.1.1/assets/{style}/{name}.svg
+Material: https://cdn.jsdelivr.net/npm/@material-design-icons/svg@0.14.13/{style}/{name}.svg
+```
+
+### SVG Normalization Required
+
+| Set | Issue | Fix |
+|-----|-------|-----|
+| Phosphor | viewBox="0 0 256 256" | Normalize to 24×24 |
+| Material | `fill="#000000"` hardcoded | Replace with `currentColor` |
+| All | May have XML declarations, comments | Strip during save |
+
+---
+
+## User Flow
+
+```
+Admin visits Settings → Maple Icons (or plugin action link)
+ ↓
+Sees list of preset icon sets with Download/Delete buttons
+ ↓
+Clicks "Download" on Heroicons → progress bar → icons saved locally
+ ↓
+Clicks "Set Active" to make Heroicons the active set
+ ↓
+User inserts "Maple Icons" block in Gutenberg editor
+ ↓
+Block shows + button → click → icon picker modal with search
+ ↓
+User searches/filters → selects icon → SVG inserted inline
+ ↓
+SVG uses currentColor → inherits from Global Styles
+ ↓
+All icons served from local storage (wp-content/maple-icons/)
+```
+
+---
+
+## File Structure
+
+```
+maple-icons-wp/
+├── maple-icons.php # Main plugin file
+├── index.php # Silence is golden
+├── uninstall.php # Clean removal
+├── readme.txt # WordPress.org readme
+├── package.json # Block build config
+├── includes/
+│ ├── index.php
+│ ├── class-mi-icon-sets.php # Preset icon set definitions
+│ ├── class-mi-icon-registry.php # Icon management & search
+│ ├── class-mi-downloader.php # CDN fetch & local storage
+│ ├── class-mi-admin-page.php # Settings page
+│ └── class-mi-ajax-handler.php # AJAX handlers
+├── presets/ # Bundled manifests (icon names, tags)
+│ ├── index.php
+│ ├── heroicons.json
+│ ├── lucide.json
+│ ├── feather.json
+│ ├── phosphor.json
+│ └── material.json
+├── build/ # Compiled block assets (generated)
+│ ├── index.php
+│ ├── index.js
+│ ├── index.asset.php
+│ └── style-index.css
+├── src/ # Block source (for development)
+│ ├── index.js # Block registration
+│ ├── edit.js # Editor component
+│ ├── save.js # Save component (inline SVG)
+│ ├── icon-picker.js # Icon selection modal
+│ ├── editor.scss # Editor-only styles
+│ └── block.json # Block metadata
+├── assets/
+│ ├── index.php
+│ ├── admin.css # Settings page styles
+│ └── admin.js # Settings page JS (download progress, etc.)
+└── languages/
+ ├── index.php
+ └── maple-icons.pot
+```
+
+### Local Icon Storage
+
+Icons are stored in:
+```
+wp-content/maple-icons/{set-slug}/{style}/{icon-name}.svg
+```
+
+Example:
+```
+wp-content/maple-icons/heroicons/outline/academic-cap.svg
+wp-content/maple-icons/heroicons/solid/academic-cap.svg
+wp-content/maple-icons/lucide/icons/activity.svg
+wp-content/maple-icons/phosphor/regular/airplane.svg
+```
+
+Using `wp-content/maple-icons/` (not uploads) to avoid cleanup plugin interference.
+
+---
+
+## Class Responsibilities
+
+### MI_Icon_Sets
+Static definitions of all preset icon sets.
+
+```php
+class MI_Icon_Sets {
+ public static function get_all(): array;
+ public static function get(string $slug): ?array;
+ public static function get_cdn_url(string $slug, string $style, string $name): string;
+ public static function get_manifest_path(string $slug): string;
+}
+```
+
+Returns structure:
+```php
+[
+ 'slug' => 'heroicons',
+ 'name' => 'Heroicons',
+ 'version' => '2.1.1',
+ 'license' => 'MIT',
+ 'url' => 'https://heroicons.com',
+ 'cdn_base' => 'https://cdn.jsdelivr.net/npm/heroicons@2.1.1/',
+ 'styles' => [
+ 'outline' => ['path' => '24/outline', 'label' => 'Outline'],
+ 'solid' => ['path' => '24/solid', 'label' => 'Solid'],
+ 'mini' => ['path' => '20/solid', 'label' => 'Mini'],
+ ],
+ 'default_style' => 'outline',
+ 'viewbox' => '0 0 24 24',
+ 'normalize' => false, // true for Phosphor (256→24)
+ 'color_fix' => false, // true for Material (replace hardcoded colors)
+]
+```
+
+### MI_Icon_Registry
+Manages downloaded sets and provides icon search/retrieval.
+
+```php
+class MI_Icon_Registry {
+ public function get_downloaded_sets(): array;
+ public function get_active_set(): ?string;
+ public function set_active(string $slug): bool;
+ public function is_downloaded(string $slug): bool;
+ public function get_icons_for_set(string $slug, string $style): array;
+ public function search_icons(string $query, int $limit = 50): array;
+ public function get_icon_svg(string $slug, string $style, string $name): string|WP_Error;
+ public function delete_set(string $slug): bool;
+}
+```
+
+### MI_Downloader
+Handles fetching icons from CDN and storing locally.
+
+```php
+class MI_Downloader {
+ public function download_set(string $slug, callable $progress_callback = null): array|WP_Error;
+ public function download_icon(string $slug, string $style, string $name): string|WP_Error;
+ private function normalize_svg(string $svg, array $set_config): string;
+ private function get_local_path(string $slug, string $style, string $name): string;
+}
+```
+
+### MI_Admin_Page
+Settings page under Settings → Maple Icons.
+
+- List all preset icon sets
+- Show download status (downloaded/not downloaded)
+- Download button with progress indicator
+- Delete button for downloaded sets
+- Radio buttons to select active set
+- Preview sample icons from each downloaded set
+
+### MI_Ajax_Handler
+AJAX endpoints:
+
+- `mi_download_set` — Download an icon set from CDN
+- `mi_delete_set` — Delete a downloaded icon set
+- `mi_set_active` — Set the active icon set
+- `mi_search_icons` — Search icons in active set (for block picker)
+- `mi_get_icon_svg` — Get specific icon SVG (for block)
+
+---
+
+## Settings Storage
+
+```php
+// Option name: maple_icons_settings
+[
+ 'active_set' => 'heroicons', // Slug of active set, or empty string
+ 'downloaded_sets' => [
+ 'heroicons' => [
+ 'version' => '2.1.1',
+ 'downloaded_at' => '2024-01-15 10:30:00',
+ 'icon_count' => 876, // Total across all styles
+ ],
+ 'lucide' => [
+ 'version' => '0.303.0',
+ 'downloaded_at' => '2024-01-15 11:00:00',
+ 'icon_count' => 1400,
+ ],
+ ],
+]
+```
+
+---
+
+## Manifest Format (presets/*.json)
+
+Each preset ships with a manifest listing all icons:
+
+```json
+{
+ "slug": "heroicons",
+ "name": "Heroicons",
+ "version": "2.1.1",
+ "icons": [
+ {
+ "name": "academic-cap",
+ "tags": ["education", "graduation", "school", "hat"],
+ "category": "objects",
+ "styles": ["outline", "solid", "mini"]
+ },
+ {
+ "name": "adjustments-horizontal",
+ "tags": ["settings", "controls", "sliders"],
+ "category": "ui",
+ "styles": ["outline", "solid", "mini"]
+ }
+ ]
+}
+```
+
+Manifests are bundled with the plugin to avoid runtime dependency on jsdelivr API.
+
+---
+
+## Gutenberg Block Architecture
+
+### Block Registration (block.json)
+
+```json
+{
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 3,
+ "name": "jetrails/maple-icons",
+ "version": "1.0.0",
+ "title": "Maple Icons",
+ "category": "design",
+ "icon": "star-filled",
+ "description": "Insert an icon from your downloaded icon sets.",
+ "keywords": ["icon", "svg", "symbol", "maple"],
+ "textdomain": "maple-icons",
+ "attributes": {
+ "iconSet": {
+ "type": "string",
+ "default": ""
+ },
+ "iconStyle": {
+ "type": "string",
+ "default": ""
+ },
+ "iconName": {
+ "type": "string",
+ "default": ""
+ },
+ "iconSVG": {
+ "type": "string",
+ "default": ""
+ },
+ "size": {
+ "type": "number",
+ "default": 24
+ },
+ "label": {
+ "type": "string",
+ "default": ""
+ },
+ "strokeWidth": {
+ "type": "number",
+ "default": 0
+ },
+ "strokeColor": {
+ "type": "string",
+ "default": ""
+ }
+ },
+ "supports": {
+ "html": false,
+ "align": ["left", "center", "right"],
+ "color": {
+ "text": true,
+ "background": true,
+ "gradients": true
+ },
+ "spacing": {
+ "margin": true,
+ "padding": true
+ },
+ "shadow": true
+ },
+ "editorScript": "file:./index.js",
+ "editorStyle": "file:./style-index.css"
+}
+```
+
+### Block Style Controls (Inspector Panel)
+
+The block sidebar will include these controls:
+
+**Icon Settings:**
+- Icon picker button (change icon)
+- Size slider (8-256px, default 24)
+- Accessibility label text input
+
+**Color Settings (via WordPress supports):**
+- Icon color (text color) — inherited by SVG via `currentColor`
+- Background color
+- Gradient support
+
+**Stroke Settings (custom panel):**
+- Stroke width slider (0-10px)
+- Stroke color picker
+
+**Spacing (via WordPress supports):**
+- Padding controls
+- Margin controls
+
+**Effects:**
+- Drop shadow presets (via WordPress `shadow` support)
+
+### Style Application
+
+```jsx
+// In edit.js and save.js, compute styles:
+const iconStyles = {
+ width: `${size}px`,
+ height: `${size}px`,
+ display: 'inline-flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+};
+
+// Stroke is applied via CSS filter or SVG manipulation
+const svgStyles = strokeWidth > 0 ? {
+ filter: `drop-shadow(0 0 0 ${strokeColor})`,
+ WebkitTextStroke: `${strokeWidth}px ${strokeColor}`,
+} : {};
+
+// Note: For true SVG stroke, we modify the SVG's stroke attribute
+// This is handled in the SVG wrapper, not CSS
+```
+
+### SVG Stroke Handling
+
+For stroke on SVG icons, we wrap the SVG output:
+
+```jsx
+// The SVG itself uses currentColor for fill/stroke
+// Additional stroke effect applied via CSS or inline style
+
+```
+
+With CSS:
+```css
+.wp-block-maple-icon__svg svg {
+ stroke: var(--mi-stroke-color, currentColor);
+ stroke-width: var(--mi-stroke-width, 0);
+ paint-order: stroke fill;
+}
+```
+
+### Block Behavior
+
+**Initial State (no icon selected):**
+- Shows + button placeholder
+- Click opens icon picker modal
+
+**With icon selected:**
+- Displays inline SVG
+- Sidebar shows: size control, accessibility label, change icon button
+
+**Icon Picker Modal:**
+- Search input (filters as you type)
+- Style selector dropdown (if set has multiple styles)
+- Grid of icon thumbnails
+- Click to select → SVG fetched and stored in attributes
+
+### Save Output (save.js)
+
+```jsx
+export default function save({ attributes }) {
+ const { iconSVG, size, label } = attributes;
+
+ if (!iconSVG) {
+ return null;
+ }
+
+ const blockProps = useBlockProps.save({
+ className: 'wp-block-maple-icon',
+ style: {
+ width: `${size}px`,
+ height: `${size}px`,
+ display: 'inline-flex',
+ },
+ });
+
+ return (
+
+
+
+ );
+}
+```
+
+---
+
+## Download Process
+
+### Batch Download Flow
+
+1. User clicks "Download" for an icon set
+2. Frontend disables button, shows progress bar
+3. AJAX request to `mi_download_set`
+4. Backend:
+ a. Load manifest from `presets/{slug}.json`
+ b. Create local directory structure
+ c. For each icon in manifest:
+ - Fetch SVG from CDN
+ - Normalize (viewBox, currentColor)
+ - Save to local filesystem
+ d. Update settings with download info
+5. Return success with icon count
+6. Frontend updates UI to show "Downloaded" state
+
+### Batching Strategy
+
+To avoid timeouts and memory issues:
+- Process icons in batches of 50
+- Use streaming/chunked approach
+- Allow resume on failure (track progress in transient)
+
+```php
+// Download in batches
+$batch_size = 50;
+$icons = $manifest['icons'];
+$total = count($icons);
+
+for ($i = 0; $i < $total; $i += $batch_size) {
+ $batch = array_slice($icons, $i, $batch_size);
+ foreach ($batch as $icon) {
+ // Download each icon in batch
+ }
+ // Update progress transient
+ set_transient('mi_download_progress_' . $slug, [
+ 'completed' => min($i + $batch_size, $total),
+ 'total' => $total,
+ ], HOUR_IN_SECONDS);
+}
+```
+
+---
+
+## SVG Sanitization
+
+Same approach as original CLAUDE.md — strict allowlist of SVG elements and attributes.
+
+```php
+function mi_sanitize_svg($svg) {
+ $allowed_tags = [
+ 'svg' => ['xmlns', 'viewbox', 'width', 'height', 'fill', 'stroke', ...],
+ 'path' => ['d', 'fill', 'stroke', 'stroke-width', ...],
+ 'circle' => ['cx', 'cy', 'r', 'fill', 'stroke', ...],
+ 'rect' => ['x', 'y', 'width', 'height', 'rx', 'ry', ...],
+ 'line' => ['x1', 'y1', 'x2', 'y2', ...],
+ 'polyline' => ['points', ...],
+ 'polygon' => ['points', ...],
+ 'ellipse' => ['cx', 'cy', 'rx', 'ry', ...],
+ 'g' => ['fill', 'stroke', 'transform', ...],
+ ];
+
+ // Remove dangerous content
+ $svg = preg_replace('/