# PressGo Plugin — Developer Reference

## Overview
WordPress plugin that generates Elementor landing pages from text descriptions or screenshots. Supports two API modes:
1. **PressGo API** (default) — users get a `pg_` key from pressgo.app, credits-based billing, Haiku model
2. **Own API Key** — users enter their Anthropic key directly, choose their own model

## Architecture

### PressGo API Mode (default)
```
Browser → admin-ajax (WordPress)
  → PressGo_AI_Client → POST https://pressgo.app/api/plugin/generate
    → X-PressGo-Key auth → credit check → Claude API (Haiku)
    ← SSE events: {type:'content',text}, {type:'done',usage,creditsRemaining}, {type:'error'}
  ← re-emit as browser SSE events
→ config JSON → local Generator → Elementor JSON → wp_insert_post
```

### Direct API Mode
```
Browser → admin-ajax (WordPress)
  → PressGo_AI_Client → POST https://api.anthropic.com/v1/messages
    ← Anthropic SSE events (content_block_delta, message_stop)
  ← re-emit as browser SSE events
→ config JSON → local Generator → Elementor JSON → wp_insert_post
```

## Key Files
- `pressgo.php` — Plugin bootstrap, constants, version (2.5.4 as of Jul 2026 — check the file, this doc lags)
- `includes/class-pressgo.php` — Singleton, loads dependencies
- `includes/class-pressgo-admin.php` — Admin pages, settings (API mode toggle, PressGo key, Claude key, model)
- `includes/class-pressgo-rest-api.php` — SSE streaming endpoint (admin-ajax), connection test (both modes)
- `includes/class-pressgo-ai-client.php` — Three backends: PressGo API, Anthropic direct, OpenAI-compatible
- `includes/class-pressgo-page-creator.php` — wp_insert_post + Elementor postmeta (hide_title, custom CSS)
- `includes/class-pressgo-config-validator.php` — Config schema validation
- `includes/generator/` — Elementor JSON generation

## PressGo API Integration (pressgo.app)

### Backend (Express on DO server, port 3003)
- **Plugin API**: `POST /api/plugin/generate` — key auth, credit deduction, Claude streaming
- **Credits check**: `GET /api/plugin/credits` — lightweight balance check for plugin settings page
- **Account management**: `/api/account/credits`, `/api/account/api-keys`, `/api/account/usage`
- **Stripe checkout**: `/api/account/purchase-credits` — $15 one-time 75-credit pack (canonical since May 2026; older pack names in code are legacy)

### Credit System
- 3 free credits/month per account (resets monthly)
- Per-model costs: Haiku = 1 credit, Sonnet = 2 credits, Opus = 10 credits
- Default model: Haiku (~$0.02/generation, ~9x margin on credit price)
- Race-condition safe: credit deduction wrapped in SQLite transaction
- Rate limited: 10 registrations/IP/hour, 60 generations/IP/hour

### API Key Format
- Generated: `pg_` + 32 hex chars (e.g., `pg_dfad7060958f0b674dd9e4e15a3cab1a`)
- Stored as SHA256 hash in `api_keys` table, full key shown once at creation
- Max 5 keys per user, `last_used_at` tracking

### Plugin Settings (class-pressgo-admin.php)
- `pressgo_api_mode` — 'pressgo' (default) or 'direct'
- `pressgo_account_key` — PressGo API key (pg_...)
- `pressgo_api_key` — Anthropic API key (for direct mode)
- `pressgo_model` — Claude model (for direct mode)
- `PressGo_Admin::has_api_configured()` — checks whichever mode is active
- Settings JS fetches live credit balance from `pressgo.app/api/plugin/credits`

## Generator Architecture
- `PressGo_Element_Factory` — Core primitives: `eid()`, `widget()`, `outer()`, `row()`, `col()`
- `PressGo_Widget_Helpers` — `heading_w()`, `text_w()`, `btn_w()`, `badge_w()`, `spacer_w()`, `icon_w()`, `image_w()`, `divider_w()`, `icon_box_w()`, `image_box_w()`, `star_rating_w()`, `social_icons_w()`, `testimonial_w()`, `video_w()`, `counter_w()`, `progress_bar_w()`, `google_map_w()`
- `PressGo_Style_Utils` — `hex_to_rgba()`, `hex_to_rgb()`, `card_style()`, `section_header()`
- `PressGo_Section_Builder` — Section builders with layout variants
- `PressGo_Generator` — Orchestrator with variant routing

### Layout System: Flexbox Containers
Uses `container` elements with flexbox layout (Elementor 3.6+). All layout primitives are containers with different flex configurations.
```
container (direction: column, content_width: boxed)  ← outer()
  ├─ widget (heading, text-editor, button, image, etc.)
  └─ container (direction: row, stacks on mobile)    ← row()
       ├─ container (width: 50%)                     ← col()
       │    └─ widget
       └─ container (width: 50%)                     ← col()
            └─ widget
```

### Layout Variants
The generator supports multiple layout variants per section type. Set `variant` key in the section config:

| Section | Variant | Builder Method | Description |
|---------|---------|---------------|-------------|
| hero | _(default)_ | `build_hero` | Centered text on dark gradient |
| hero | `split` | `build_hero_split` | Text-left + image-right on light bg |
| hero | `image` | `build_hero_image` | Full background image with dark overlay |
| hero | `video` | `build_hero_video` | Centered text + video embed on light bg |
| hero | `gradient` | `build_hero_gradient` | Colorful gradient bg with waves divider |
| hero | `minimal` | `build_hero_minimal` | Clean white bg, centered text, no gradient |
| features | _(default)_ | `build_features` | 3-column card grid with accent borders |
| features | `alternating` | `build_features_alternating` | Alternating text/image rows |
| features | `minimal` | `build_features_minimal` | Clean icons with text, no cards |
| features | `image_cards` | `build_features_image_cards` | Image on top of each card |
| features | `grid` | `build_features_grid` | 2-column card grid for 4+ features |
| features | `bento` | `build_features_bento` | Asymmetric bento: big gradient hero tile + stacked tiles (first item = big tile, 4-6 items, no images; <3 items falls back to default features) |
| testimonials | _(default)_ | `build_testimonials` | 3-column cards with star ratings |
| testimonials | `featured` | `build_testimonials_featured` | Single large quote + small cards |
| testimonials | `grid` | `build_testimonials_grid` | 2-column card grid with avatars |
| testimonials | `minimal` | `build_testimonials_minimal` | Editorial spotlight: first quote large + accent rule, rest 2-up below, card-free |
| competitive_edge | _(default)_ | `build_competitive_edge` | Text + icon-list checklist |
| competitive_edge | `image` | `build_competitive_edge_image` | Text + checkmarks left, image right |
| competitive_edge | `cards` | `build_competitive_edge_cards` | Benefit cards with icons in 3-col grid |
| competitive_edge | `comparison` | `build_competitive_edge_comparison` | Us-vs-them cards (them_points/us_label/them_label; falls back to default) |
| social_proof | _(default)_ | `build_social_proof` | Industry pill badges on light bg |
| social_proof | `dark` | `build_social_proof_dark` | Industry pill badges on dark bg |
| stats | _(default)_ | `build_stats` | White cards with icons, overlaps hero |
| stats | `dark` | `build_stats_dark` | Dark gradient bg with colored counters |
| stats | `inline` | `build_stats_inline` | Minimal horizontal counter row with dividers |
| steps | _(default)_ | `build_steps` | Numbered circles on light bg cards |
| steps | `compact` | `build_steps_compact` | Numbered pill badges with divider |
| steps | `timeline` | `build_steps_timeline` | Vertical timeline with connecting line |
| faq | _(default)_ | `build_faq` | Centered toggle accordion |
| faq | `split` | `build_faq_split` | Header left, accordion right |
| cta_final | _(default)_ | `build_cta_final` | Gradient bar with centered text |
| cta_final | `card` | `build_cta_final_card` | White card on light background |
| cta_final | `image` | `build_cta_final_image` | Background image with dark overlay |
| cta_final | `split` | `build_cta_final_split` | Headline+CTA left, frosted `bullets` checklist card right, dark bg (needs bullets; falls back to default) |
| newsletter | _(default)_ | `build_newsletter` | Email capture card with CTA |
| newsletter | `inline` | `build_newsletter_inline` | Gradient bar with headline + button |
| results | _(default)_ | `build_results` | Dark gradient with counter cards |
| results | `bars` | `build_results_bars` | Light bg with bold metric cards (counters keep real value shape, accent top border) |
| team | _(default)_ | `build_team` | Photo + name + role + bio + social cards |
| team | `compact` | `build_team_compact` | Small photos, name + role only, no cards |
| team | `spotlight` | `build_team_spotlight` | Single-person editorial profile (credentials/cta; auto-routed when 1 member) |
| pricing | _(default)_ | `build_pricing` | 2-4 column plan cards with feature lists |
| pricing | `compact` | `build_pricing_compact` | Left-aligned cards, smaller price, bordered highlight |
| pricing | `list` | `build_pricing_list` | Editorial service/menu price list (items name/price/desc/category, grouped; falls back to plan cards) |
| logo_bar | _(default)_ | `build_logo_bar` | "Trusted by" logo row |
| logo_bar | `dark` | `build_logo_bar_dark` | Dark bg logo row |
| map | _(default)_ | `build_map` | Google Maps embed with optional header |
| map | `contact` | `build_map_contact` | 'Visit Us': contact card (tel:/mailto:/hours/note/CTA) + map split (falls back to bare map) |
| gallery | _(default)_ | `build_gallery` | Image grid with lightbox |
| gallery | `cards` | `build_gallery_cards` | 2-col image cards with optional captions |
| gallery | `before_after` | `build_gallery_before_after` | Labeled BEFORE/AFTER pairs + result line (drops incomplete pairs) |
| gallery | `videos` | `build_gallery_videos` | 2-up YouTube/Vimeo embed cards |
| footer | _(default)_ | `build_footer` | Multi-column dark footer with brand/links/contact |
| footer | `light` | `build_footer_light` | White/light bg footer with colored icons |

### Section Types (19 types, 56 builder methods)
hero, stats, social_proof, features, steps, results, competitive_edge, testimonials, faq, blog, pricing, logo_bar, team, gallery, newsletter, cta_final, map, footer, disclaimer

## Responsive / Mobile
- **Section padding**: `outer()` auto-calculates tablet (3/4) and mobile (1/2, min 40px) padding
- **Row gaps**: `row()` auto-calculates tablet (3/4) and mobile (min 16px, 2/3 desktop) column gaps
- **Row stacking**: `row()` sets `flex_direction_mobile: column` by default — columns stack on mobile. Override with `extra` param for sections that should wrap instead (logo bar, social proof pills)
- **Spacers**: `spacer_w()` auto-sets mobile to 2/3 desktop (min 8px) for spacers >= 24px
- **Widget mobile params**: `heading_w($align_mobile)`, `text_w($line_height, $align_mobile)`, `btn_w($align_mobile)` — use for split layouts that stack on mobile
- **Split layout pattern**: On mobile, 2-column layouts stack vertically. Add `align_mobile='center'` to headings/text/buttons in the left column, and add `padding_mobile` reset to columns with desktop-only right padding
- **Font size suffixes**: `typography_font_size_mobile`, `typography_font_size_tablet` on any widget. Always add these for sizes >= 28px.
- **Counter sizes**: `counter_w()` auto-calculates tablet (7/8) and mobile (3/4) from desktop size. Raw counter widgets must set these manually.
- **Card padding**: `card_style()` includes `padding_mobile`. If overriding `padding` with `array_merge`, `padding_mobile` is preserved.
- **Map height**: `google_map_w($height_mobile)` — auto-calculated as 5/8 desktop (min 200px)
- **Image layout shift**: CSS `aspect-ratio: 3/2` on Pexels images + `min-height` on image columns prevents CLS
- **Logo bar mobile**: Uses `flex_wrap: wrap` + `flex_direction_mobile: row` + `width_mobile: 28%` so logos wrap into 3 columns instead of stacking
- **Social proof mobile**: Uses `flex_wrap: wrap` + `flex_direction_mobile: row` + `width_mobile: 45%` so pills wrap into 2 columns

## Critical Elementor Rules
1. **Use flexbox containers** — `elType: 'container'` with `container_type: 'flex'`. All primitives (outer/row/col) are containers.
2. **NEVER use `_animation`** — causes `elementor-invisible` class, content disappears
3. **Icon format must be** `array('value' => 'fas fa-name', 'library' => 'fa-solid')` — value MUST be string, never nested array
4. **Container flex settings** — `outer()`: direction column + boxed content. `row()`: direction row + `flex_wrap: nowrap` + stacks on mobile. `col()`: direction column + width set by row.
5. **Set `flex_gap: 0`** on outer/col containers — spacing is handled by spacer widgets, not flex gap
6. **`isInner` is critical** — `outer()` must set `isInner: false` (renders as `e-parent`, gets boxed centering). `row()` and `col()` must set `isInner: true` (renders as `e-child`). Getting this wrong breaks centering and layout.
7. **Flush caches** after page creation (`wp_elementor flush-css`)
8. **Toggle widget (FAQ) is Free**, accordion is Pro-only
9. **Posts widget requires Pro** — check `defined('ELEMENTOR_PRO_VERSION')`
10. **Container nesting is unlimited** — containers can nest freely (no 3-level limit like sections)
11. **Elementor data storage** — `update_post_meta($id, '_elementor_data', wp_slash(wp_json_encode($elements)))`
12. **CSS selectors for containers** — Use `.e-child.e-con` (not `.elementor-inner-section` or `.elementor-column`). Top-level containers get `.e-parent.e-con-boxed`, inner containers get `.e-child.e-con-full`.

## Image Support
- `image_w($url, $alt, $width, $radius, $shadow, $align)` creates Elementor image widgets
- Images referenced by URL (from Pexels/Unsplash) — no upload needed
- Image widget key format: `'image' => array('url' => $url, 'id' => '', 'alt' => $alt)`
- Background images on containers: set `background_image`, `background_position`, `background_size` in container settings

## Brain / Knowledge Base
- Canonical copy: `brain.json` in the plugin root — this is what `get_brain` and the `pressgo://brain` MCP resource serve
- (`/opt/pressgo-ops/brain.json` on the server is a synced mirror only; nothing consumes it)
- Contains: layout patterns, widget frequency, typography combos, color palettes, section rules, complete section_variants (all 56 builders)
- Derived from analysis of 588 Elementor template kits (10,624 JSON files) at `/opt/elementor-builder/templates/`
- Key insight: `image` is the #2 most used widget (3,732 uses) — pages need images

## Config Schema
- `config-schema.json` in plugin root — complete specification of the config dict the AI must produce
- Documents all 19 section types, all variant options, every required/optional field with types and examples
- Includes: variant pairing guide (dark_hero_flow, light_hero_flow, visual_heavy, minimal), industry recommendations (8 verticals), common FontAwesome icons, full example config
- This is the "instruction manual" for server-side Claude — if it has this file, it can generate valid configs without any prior context

## Image APIs (from old pressgo.app)
- **Pexels API** — `PEXELS_API_KEY` env var, `https://api.pexels.com/v1/search`
- **Unsplash API** — `UNSPLASH_ACCESS_KEY` env var, `https://api.unsplash.com`
- Safe search filtering built into old backend at `/var/www/pressgo.app/backend/src/routes/pexels.js`
- Image preference DB with industry-contextual search at `/var/www/pressgo.app/backend/src/routes/v4.js`
- Direct Pexels URLs work without API key: `https://images.pexels.com/photos/{ID}/pexels-photo-{ID}.jpeg?auto=compress&cs=tinysrgb&w=800`

## Testing
- PHP 7.4+ compatible (uses `intdiv()`, no union types, no named args)
- Works with Elementor Free; blog section requires Pro
- Config validation fills in missing defaults + type coercion for array fields
- Sandbox: wp.pressgo.app (DigitalOcean droplet, SSH alias: `digitalocean`)
- WordPress path on server: `/var/www/wp.pressgo.app/htdocs`
- Plugin path on server: `/var/www/wp.pressgo.app/htdocs/wp-content/plugins/pressgo-builder/`
- Screenshot test: `node test/screenshot-test.mjs` (Puppeteer, desktop 1440px + mobile 375px)
- **Batch config test**: `bash test/build-from-configs.sh` — rebuilds 23 test pages from `test/configs/*.json` via SSH
- **70 test pages** on wp.pressgo.app: 23 from pre-generated configs, 47 from API generation
- Deploy single file: `scp file.php digitalocean:/var/www/wp.pressgo.app/htdocs/wp-content/plugins/pressgo-builder/path/file.php`
- **IMPORTANT**: After scp, fix ownership: `ssh digitalocean "chown -R www-data:www-data /var/www/wp.pressgo.app/htdocs/wp-content/plugins/pressgo-builder/"`
- Flush CSS after deploy: `ssh digitalocean "cd /var/www/wp.pressgo.app/htdocs && wp elementor flush-css --allow-root"`

## WordPress.org SVN Deploy
- SVN repo: `https://plugins.svn.wordpress.org/pressgo-builder/`
- Username: `acehobojoe` (credentials cached in `~/.subversion/auth/`)
- SVN password stored locally in `.svn-credentials` (gitignored)
- Deploy flow:
  1. Update version in `pressgo.php` (header + PRESSGO_VERSION constant) and `readme.txt` (Stable tag)
  2. Add changelog entry to `readme.txt`
  3. Commit to git + push to GitHub
  4. `svn checkout https://plugins.svn.wordpress.org/pressgo-builder/ /tmp/pressgo-svn --depth immediates`
  5. `svn update /tmp/pressgo-svn/trunk --set-depth infinity`
  6. rsync plugin files to `/tmp/pressgo-svn/trunk/` (same excludes as build-zip.sh)
  7. `svn add` any new files, `svn copy trunk tags/{version}`
  8. `svn commit -m "Release {version}" --username acehobojoe`

## Settings
- **API Mode** (`pressgo_api_mode`) — 'pressgo' (default) or 'direct'
- **PressGo API Key** (`pressgo_account_key`) — `pg_` key from pressgo.app
- **Claude API Key** (`pressgo_api_key`) — Anthropic key (direct mode only)
- **Claude Model** (`pressgo_model`) — model selector (direct mode only)
