# Pages Editor Improvements Design

**Issue:** #641
**Date:** 2026-01-27
**Status:** Approved

## Overview

Enhance the Puck-based Pages Editor with font customization, image linking, video embedding, and flexible grid layouts. Changes apply to both the dealer Pages Editor and admin Resource Guide CMS since they share `lib/cms/puck-config.tsx`.

## Features

### 1. Font Size Control

**Scope:** Heading component and Lexical WYSIWYG toolbar

**Heading Component:**

- New `fontSize` field: numeric input with stepper buttons
- Range: 12px to 72px
- Defaults by level: H2 = 32px, H3 = 24px, H4 = 20px
- Stored as pixels, rendered as rem for accessibility

**Lexical Toolbar:**

- Font size dropdown added to toolbar
- Options: 12, 14, 16, 18, 20, 24, 28, 32, 36, 48, 72
- Applied as inline style to selected text

### 2. Font Color Control

**Scope:** Heading component and Lexical WYSIWYG toolbar

**New ColorPickerField Component:**

- Brand palette swatches:
  - Primary blue: #093b66
  - Accent orange: #f8981d
  - Black: #000000
  - White: #ffffff
  - Gray: #757575
- Custom hex input with color preview
- Reusable across components

**Heading Component:**

- New `color` field using ColorPickerField
- Default: #093b66 (primary blue, matches current behavior)

**Lexical Toolbar:**

- Color picker button added to toolbar
- Same brand palette + custom hex
- Applied as inline style to selected text

### 3. Image Linking

**Scope:** Image component

**New Fields:**

- `linkUrl` (text): Destination URL
- `linkBehavior` (select): "navigate" | "lightbox"
- `openInNewTab` (checkbox): Shown only when behavior is "navigate"

**Link Security (matching Lexical):**

- External links get `rel="noopener noreferrer"`
- External links with openInNewTab get `target="_blank"`
- URL validation prevents `javascript:` protocol
- Internal links (same domain) skip noopener/noreferrer

**Lightbox Behavior:**

- Modal overlay displays full-size image
- Close button (X) and click-outside-to-close
- Keyboard accessible: Escape to close, focus trap
- Static HTML uses `<dialog>` element with minimal JS

**Conditional Field Display:**

- No linkUrl: hide linkBehavior and openInNewTab
- linkBehavior is "lightbox": hide openInNewTab

### 4. Video Embedding

**Scope:** New Video component in "Content" category

**Source Fields:**

- `embedType` (select): "youtube" | "vimeo" | "url" | "embed"
- `videoId` (text): YouTube/Vimeo ID or full URL (parsed automatically)
- `videoUrl` (text): Direct MP4/WebM URL (Media Library compatible)
- `embedCode` (textarea): Custom embed code (Wistia, Loom, etc.)

**Customization Fields:**

- `aspectRatio` (select): "16:9" | "4:3" | "1:1" | "9:16"
- `autoplay` (checkbox): Auto-starts muted (browser requirement)
- `loop` (checkbox): Loop playback
- `showControls` (checkbox): Show/hide player controls (default: true)

**Embed Security:**

- Custom embed sanitized: only `<iframe>` tags permitted
- Allowed attributes: src, width, height, frameborder, allow, allowfullscreen, title
- Strip `<script>` tags and event handlers
- Validate src URLs against known video platforms

**Responsive Container:**

```css
.cms-video-container {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9; /* or selected ratio */
}
```

### 5. Grid Layout

**Scope:** New Grid component in "Layout" category

**Configuration Fields:**

- `columns` (select): 2 | 3 | 4
- `gap` (select): "none" | "small" | "medium" | "large" (0, 16px, 24px, 32px)
- `verticalAlign` (select): "top" | "center" | "bottom"

**Slots:**

- Dynamic slots: column1, column2, column3, column4
- Each slot is a dropzone accepting any components
- Slot count matches column selection

**Responsive Behavior:**

- 4 columns: 2 on tablet (<768px), 1 on mobile (<480px)
- 3 columns: 2 on tablet, 1 on mobile
- 2 columns: 1 on mobile

**Backward Compatibility:**

- TwoColumn component retained for existing pages
- Grid documented as preferred choice for new content

## File Changes

### New Files

| File                           | Purpose                                               |
| ------------------------------ | ----------------------------------------------------- |
| `lib/cms/ColorPickerField.tsx` | Reusable color picker with brand palette + custom hex |
| `lib/cms/video-utils.ts`       | URL parsing, embed sanitization helpers               |

### Modified Files

| File                       | Changes                                                                                |
| -------------------------- | -------------------------------------------------------------------------------------- |
| `lib/cms/puck-config.tsx`  | Add fontSize/color to Heading; add link fields to Image; add Video and Grid components |
| `lib/cms/LexicalField.tsx` | Add font size dropdown and color picker to toolbar                                     |
| `lib/cms/publisher.ts`     | Render fontSize, color, image links, lightbox, video embeds, grid layouts              |
| `public/assets/cms.css`    | Lightbox modal, video container, grid responsive styles                                |

## Implementation Notes

### Puck Field Types

- Font size: `number` field with min/max
- Color: `custom` field rendering ColorPickerField
- Link behavior: `select` field
- Video embed type: `select` field with conditional field visibility
- Grid columns: `select` field controlling slot count

### Publisher Output

All features must render to static HTML for dealer pages:

- Font size/color: inline styles
- Image lightbox: `<dialog>` with inline JS handler
- Video: iframe or video element in aspect-ratio container
- Grid: CSS Grid with responsive media queries

### Security Considerations

- DOMPurify sanitization for all HTML content
- URL validation for links (no javascript: protocol)
- Iframe src allowlist for video embeds
- External link attributes (noopener, noreferrer)

## Testing Checklist

- [ ] Font size changes render correctly in editor preview
- [ ] Font size changes render correctly in published pages
- [ ] Color picker shows brand swatches and accepts custom hex
- [ ] Image links navigate correctly
- [ ] Image lightbox opens/closes with keyboard and mouse
- [ ] YouTube/Vimeo embeds parse URLs correctly
- [ ] Direct video URLs play in browser
- [ ] Custom embed codes are sanitized
- [ ] Video aspect ratios display correctly
- [ ] Grid columns render with correct gaps
- [ ] Grid is responsive at all breakpoints
- [ ] All features work in Resource Guide editor
