Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
dbd7124
tools: add OCR block-extraction pipeline
fluid-chey Apr 24, 2026
333e2c7
validator: accept instagram-portrait 1080x1350 + meta
fluid-chey Apr 24, 2026
fb0e948
spec: add Section 10 self-documenting metadata for instagram-portrait…
fluid-chey Apr 24, 2026
b3320d3
archetypes: add stat/data 4:5 archetypes (hero-stat-45, big-number-ca…
fluid-chey Apr 24, 2026
effe5e0
archetypes: add quote/testimonial 4:5 archetypes (3 archetypes)
fluid-chey Apr 24, 2026
1b53e5c
archetypes: add announcement 4:5 archetypes (coming-soon-minimal, web…
fluid-chey Apr 24, 2026
d011d2a
archetypes: add photo collage 4:5 archetypes (4 archetypes)
fluid-chey Apr 24, 2026
6473449
archetypes: add hero-photo 4:5 archetypes (photo-darken-headline, spl…
fluid-chey Apr 24, 2026
f4b1e8f
archetypes: add remaining 10 4:5 archetypes (tips, personal, product,…
fluid-chey Apr 24, 2026
a18186d
agent-tools: extend listArchetypes with category/imageRole/platform f…
fluid-chey Apr 24, 2026
ca46931
agent: Instagram portrait (4:5) becomes default; filter-first archety…
fluid-chey Apr 24, 2026
e45e9ee
tests: phase-23 golden tasks + archetype invariants (370 assertions, …
fluid-chey Apr 24, 2026
29dba16
review: restore 9 missing legacy archetype iframes (linkedin + one-pa…
fluid-chey Apr 24, 2026
dbd4743
archetypes: fix meta.slotCount to match fields.length (stat-compariso…
fluid-chey Apr 24, 2026
7d1f7de
agent: add instagram-portrait to KNOWN_PLATFORMS + align downstream t…
fluid-chey Apr 24, 2026
d63ca65
tools: clean up ocr-to-blocks.awk (remove dead code + explicit empty …
fluid-chey Apr 24, 2026
5a6793a
agent-tools: deterministic listArchetypes + typed shapes + logged par…
fluid-chey Apr 24, 2026
3a10e28
tools: install social-media-taste + gemini-social-image skills
fluid-chey Apr 24, 2026
3d82032
db: add brand_assets.metadata + tool_audit_log + spend helpers
fluid-chey Apr 24, 2026
eedd357
harness: capabilities registry + sse-events constants + observability…
fluid-chey Apr 24, 2026
437fbac
agent: search_brand_images tool + /api/brand-assets?q= search endpoint
fluid-chey Apr 24, 2026
c142e5b
tests: phase-24 dispatch 1 (foundation + search)
fluid-chey Apr 24, 2026
034c462
dispatch: tool-dispatch wrapper with permission tiers + cost cap + au…
fluid-chey Apr 24, 2026
9b19aed
chat-routes: POST /api/chats/:id/permission-response
fluid-chey Apr 24, 2026
cc4aedb
agent: route executeTool through dispatcher with session-scoped appro…
fluid-chey Apr 24, 2026
f3c4edf
env: document FLUID_DAILY_COST_CAP_USD
fluid-chey Apr 24, 2026
8c202c6
tests: phase-24 dispatch 2 (tool-dispatch + permission flow + cost cap)
fluid-chey Apr 24, 2026
56ced5c
deps: add @google/genai for Gemini 2.5 Flash Image
fluid-chey Apr 24, 2026
dd100ea
gemini: generate_image tool with idempotency + safety + provenance
fluid-chey Apr 24, 2026
cf5b600
tools: promote_generated_image + read_skill + image-led workflow + ta…
fluid-chey Apr 24, 2026
3efe205
tests: phase-24 dispatch 3 (gemini + skills + system prompt)
fluid-chey Apr 24, 2026
3ada1c0
health: GET /api/health subsystem status + /api/chats/:id/usage-rollup
fluid-chey Apr 24, 2026
3c17a58
store: handle tool_start/end/progress + permission_prompt + budget_wa…
fluid-chey Apr 24, 2026
3db26e5
ui: image-attach button in chat composer + upload preview chip + budg…
fluid-chey Apr 24, 2026
0ae0d3a
ui: PermissionPrompt component — inline permission gate for ask-first…
fluid-chey Apr 24, 2026
147903d
tests: phase-24 dispatch 4 (store reducers + health + rollup + upload…
fluid-chey Apr 24, 2026
a357a8d
fix(store): remove activeTools entry on tool_end (not tool_result)
fluid-chey Apr 24, 2026
f4ce718
deps: install @anthropic-ai/claude-agent-sdk@0.2.119
fluid-chey Apr 24, 2026
6fbaa8e
agent: build SDK MCP server modules per tool group
fluid-chey Apr 24, 2026
5f13c08
agent: migrate runAgent to SDK query() + SSE translation layer
fluid-chey Apr 24, 2026
5a58943
health: recognize claude-login auth path
fluid-chey Apr 24, 2026
0ad68cb
docs: claude login setup + env example update
fluid-chey Apr 24, 2026
a5be090
tests: phase-25 (MCP server parity + SSE translation + auth paths)
fluid-chey Apr 24, 2026
0479254
deps: @anthropic-ai/sdk removal deferred — blocked by test dependencies
fluid-chey Apr 24, 2026
729c054
fix(agent): restore tool_result + creation_ready + validation_result …
fluid-chey Apr 24, 2026
b2c021e
fix(agent): move tool permission prompts from dispatchTool to SDK can…
fluid-chey Apr 24, 2026
2934e37
fix(agent): route campaignless save_creation to __standalone__ sentinel
fluid-chey Apr 24, 2026
0559a25
cleanup: stale-iteration sweeper + 0-byte write guard + 404 placeholder
fluid-chey Apr 24, 2026
1604a28
fix(agent-tools): apply 0-byte write guard to editCreation too
fluid-chey Apr 24, 2026
c70191b
fix(render-engine): transparent-PNG fallback for unresolvable brand-a…
fluid-chey Apr 24, 2026
6a38e82
fix(archetypes): strip CSS rules migrated to global.css
fluid-chey Apr 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,8 @@ canvas/.env
.env.local
# Overnight review snapshots (dated)
tools/overnight-review-[0-9]*.html
# OCR working artifacts (per-archetype, not source-controlled)
archetypes/*/.ocr-notes.txt
# Phase 24: AI-generated images (never commit generated content)
assets/generated/
canvas/assets/generated/
31 changes: 28 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,34 @@ cd canvas && npm install && npm run dev
- **Template library:** http://localhost:5174/
- **Brand assets served at:** `/fluid-assets/*`

Environment variables (`.env` at repo root):
- `ANTHROPIC_API_KEY` — required for generation
## Running Locally — Claude Authentication

The agent runtime uses `@anthropic-ai/claude-agent-sdk`. Two auth paths are supported:

**Option A — API key (CI + production):**
```bash
# Add to .env at repo root (or export in your shell)
ANTHROPIC_API_KEY=sk-ant-...
```

**Option B — Claude CLI login (local dev, one-time setup):**
```bash
# Install the Claude CLI if not already present, then:
claude login
# Follow the OAuth flow. Credentials are saved to ~/.claude/.credentials.json
# No API key provisioning needed; the SDK picks up the session automatically.
```

API key takes precedence if both are set. `GET /api/health` returns `anthropic: 'ok'`
for either path.

## Other Environment Variables

- `VITE_FLUID_DAM_TOKEN` — optional, for DAM picker integration
- `GEMINI_API_KEY` — required for AI image generation (generate_image tool)
- `FLUID_AGENT_MODEL` — override the Claude model (default: `claude-sonnet-4-6`)
- `FLUID_DISPATCH_TRUSTED` — set to `true` to bypass ask-first tool permission prompts
- `FLUID_DAILY_COST_CAP_USD` — daily spend cap for image generation (default: `10.00`)

## Tech Stack

Expand All @@ -23,7 +48,7 @@ Environment variables (`.env` at repo root):
| Frontend | React 19, TypeScript 5.6, Zustand 5, Vite 6 |
| Backend | Vite middleware plugin (no Express) |
| Database | SQLite (better-sqlite3, WAL mode) |
| AI | Anthropic SDK with tool use, SSE streaming |
| AI | Claude Agent SDK (`@anthropic-ai/claude-agent-sdk`) with MCP tool servers, SSE streaming |
| Testing | Vitest + Playwright |
| Styling | Plain CSS (no Tailwind in the app itself) |

Expand Down
93 changes: 86 additions & 7 deletions archetypes/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -490,15 +490,94 @@ components/testimonial-block/ # quote + name + title (3 fields)

## Section 9: Platform Dimensions Reference

| Platform | Width (px) | Height (px) | Phase |
|----------|------------|-------------|-------|
| Instagram Square | 1080 | 1080 | 19 |
| LinkedIn Landscape | 1200 | 627 | 21 |
| One-Pager (US Letter) | 612 | 792 | 21 |
| Platform | Width (px) | Height (px) | Phase | `schema.platform` value |
|----------|------------|-------------|-------|------------------------|
| Instagram Portrait (4:5) | 1080 | 1350 | 23 | `instagram-portrait` |
| Instagram Square | 1080 | 1080 | 19 | `instagram-square` |
| LinkedIn Landscape | 1200 | 627 | 21 | `linkedin-landscape` |
| One-Pager (US Letter) | 612 | 792 | 21 | `one-pager` |

**Phase 19 scope:** Instagram Square only (`1080 × 1080`). LinkedIn and One-Pager archetypes are Phase 21.
**Phase 23 default:** Instagram Portrait (`1080 × 1350`) is the standard for all new archetypes. Instagram Square is legacy — only use on explicit request.

All Phase 19 archetypes MUST use `"width": 1080, "height": 1080` in `schema.json` and `width: 1080px; height: 1080px` on `body` in `index.html`.
New Phase 23 archetypes MUST use `"width": 1080, "height": 1350` in `schema.json`, `width: 1080px; height: 1350px` on `body` in `index.html`, and `"platform": "instagram-portrait"` as a top-level field.

---

## Section 10: Self-Documenting Metadata (Phase 23 — instagram-portrait archetypes)

Phase 23 introduces a `meta` object in `schema.json` for all `instagram-portrait` archetypes. The `meta` object makes archetypes self-documenting so `list_archetypes()` can surface them with rich discovery signals without loading HTML.

**This section is forward-only.** Existing archetypes (Phases 19–22) are unaffected and do not need to add `meta`.

### Required `meta` fields (instagram-portrait only)

| Field | Type | Description |
|-------|------|-------------|
| `category` | `string` | Functional category for filtering (e.g., `"hero-photo"`, `"quote-testimonial"`) |
| `imageRole` | `"none" \| "accent" \| "background" \| "hero" \| "grid"` | How image assets are used in the layout |
| `useCases` | `string[]` (≥1) | Concrete scenarios where this archetype excels |
| `slotCount` | `number` (integer, ≥1) | Total number of editable slots (text + image fields) |

### Recommended `meta` fields

| Field | Type | Description |
|-------|------|-------------|
| `mood` | `string[]` | Descriptive mood tags (e.g., `["editorial", "bold"]`) |
| `contentDensity` | `"sparse" \| "moderate" \| "dense"` | How much content the layout can hold |
| `imageHints` | `object` | Guidance for selecting images from the DAM |
| `avoidCases` | `string[]` | When NOT to use this archetype |

### Full `meta` example

```json
{
"archetypeId": "photo-darken-headline",
"platform": "instagram-portrait",
"width": 1080,
"height": 1350,
"meta": {
"category": "hero-photo",
"mood": ["editorial", "cinematic", "bold"],
"contentDensity": "sparse",
"imageRole": "background",
"imageHints": {
"suggestedAspect": "4:5 or wider",
"suggestedSubject": "single person, landscape, or abstract texture",
"treatment": "darken overlay 40–60% for text legibility",
"damPreference": ["lifestyle", "people", "abstract"]
},
"useCases": [
"Brand manifesto / mission statement",
"Launch hero with dramatic mood",
"Hiring post with team photo"
],
"avoidCases": [
"Data-heavy content",
"Posts needing multiple bullet points",
"Brands without strong photography in DAM"
],
"slotCount": 3
},
"fields": [],
"brush": null,
"brushAdditional": []
}
```

### Valid category values (Phase 23)

| Category string | Slug group |
|----------------|------------|
| `"stat-data"` | hero-stat-45, big-number-card, stat-comparison |
| `"quote-testimonial"` | photocentric-quote, typographic-quote, book-quote-highlight |
| `"announcement"` | coming-soon-minimal, website-launch-mockup, event-promo |
| `"photo-collage"` | vintage-scrapbook, fashion-moodboard, memory-grid-4up, asymmetric-photo-collage |
| `"hero-photo"` | photo-darken-headline, split-photo-feature |
| `"tips-howto"` | numbered-tips-cover, how-to-step-card |
| `"personal-about"` | about-me-portrait, hiring-portrait-cta |
| `"product"` | product-hero-backlit, product-feature-grid, product-callout-macro |
| `"motivational"` | affirmation-note, handwritten-quote-photo |
| `"carousel-cover"` | carousel-cover-typographic |

---

Expand Down
117 changes: 102 additions & 15 deletions archetypes/_review.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,40 @@
.card-left { display: flex; flex-direction: column; gap: 8px; }
.card-label { font-size: 14px; font-weight: 600; color: #555; text-transform: uppercase; letter-spacing: 0.05em; }
.frame-wrap {
width: 540px; height: 540px;
width: 270px; height: 338px;
border: 1px solid #d0d0d0;
background: #f5f5f5;
overflow: hidden;
position: relative;
}
.frame-wrap.square {
width: 270px; height: 270px;
}
.frame-wrap.linkedin {
width: 360px; height: 188px;
}
.frame-wrap.onepager {
width: 245px; height: 317px;
}
.frame-wrap iframe {
width: 1080px; height: 1080px;
transform: scale(0.5);
width: 1080px; height: 1350px;
transform: scale(0.25);
transform-origin: top left;
border: none;
display: block;
}
.frame-wrap.square iframe {
width: 1080px; height: 1080px;
transform: scale(0.25);
}
.frame-wrap.linkedin iframe {
width: 1200px; height: 627px;
transform: scale(0.3);
}
.frame-wrap.onepager iframe {
width: 612px; height: 792px;
transform: scale(0.4);
}
.card-right { display: flex; flex-direction: column; gap: 6px; flex: 1; min-width: 320px; }
.card-right label { font-size: 13px; font-weight: 500; color: #777; }
.card-right textarea {
Expand Down Expand Up @@ -54,7 +75,7 @@
</head>
<body>

<h1>Phase 19 — Archetype Review</h1>
<h1>Phase 19–23 — Archetype Review</h1>

<div class="grid" id="grid"></div>

Expand All @@ -66,28 +87,94 @@ <h1>Phase 19 — Archetype Review</h1>

<script>
const archetypes = [
{ slug: 'stat-hero-single', label: 'Stat Hero Single (Phase 18 PoC)', desc: 'Original PoC — giant stat, headline, subtext, 68px margins' },
{ slug: 'hero-stat', label: 'Hero Stat', desc: 'Eyebrow + headline + body copy, 3-stat row at bottom' },
{ slug: 'hero-stat-split', label: 'Hero Stat Split (NEW)', desc: 'Photo left, headline + body + 2 stats right' },
{ slug: 'photo-bg-overlay', label: 'Photo BG Overlay', desc: 'Full-bleed photo with gradient text overlay' },
{ slug: 'split-photo-text', label: 'Split Photo / Text', desc: '50/50 split — photo left, headline + body right' },
{ slug: 'split-photo-quote', label: 'Split Photo / Quote (NEW)', desc: '50/50 split — photo left, pull quote + attribution right' },
{ slug: 'quote-testimonial', label: 'Quote Testimonial', desc: 'Full-width pull quote, portrait + attribution row' },
{ slug: 'minimal-statement', label: 'Minimal Statement', desc: 'Bold statement + subheader, grouped tight' },
{ slug: 'minimal-photo-top', label: 'Minimal Photo Top (NEW)', desc: 'Photo top half, headline + subheader bottom' },
{ slug: 'data-dashboard', label: 'Data Dashboard', desc: '2x2 stat grid, centered headline' },
// ── Phase 19–22 legacy (1080×1080 square) ──────────────────────────────────
{ slug: 'stat-hero-single', label: 'Stat Hero Single (Phase 18 PoC)', desc: 'Original PoC — giant stat, headline, subtext, 68px margins' },
{ slug: 'hero-stat', label: 'Hero Stat', desc: 'Eyebrow + headline + body copy, 3-stat row at bottom' },
{ slug: 'hero-stat-split', label: 'Hero Stat Split', desc: 'Photo left, headline + body + 2 stats right' },
{ slug: 'photo-bg-overlay', label: 'Photo BG Overlay', desc: 'Full-bleed photo with gradient text overlay' },
{ slug: 'split-photo-text', label: 'Split Photo / Text', desc: '50/50 split — photo left, headline + body right' },
{ slug: 'split-photo-quote', label: 'Split Photo / Quote', desc: '50/50 split — photo left, pull quote + attribution right' },
{ slug: 'quote-testimonial', label: 'Quote Testimonial', desc: 'Full-width pull quote, portrait + attribution row' },
{ slug: 'minimal-statement', label: 'Minimal Statement', desc: 'Bold statement + subheader, grouped tight' },
{ slug: 'minimal-photo-top', label: 'Minimal Photo Top', desc: 'Photo top half, headline + subheader bottom' },
{ slug: 'data-dashboard', label: 'Data Dashboard', desc: '2x2 stat grid, centered headline' },

// ── Phase 21 legacy (1200×627 LinkedIn landscape) ──────────────────────────
{ slug: 'hero-stat-li', label: 'Hero Stat (LinkedIn)', desc: 'LinkedIn landscape variant — eyebrow + headline + 3 stats' },
{ slug: 'data-dashboard-li', label: 'Data Dashboard (LinkedIn)', desc: 'LinkedIn landscape — 2x2 stat grid with centered headline' },
{ slug: 'minimal-statement-li', label: 'Minimal Statement (LinkedIn)', desc: 'LinkedIn landscape — bold statement + subheader' },
{ slug: 'split-photo-text-li', label: 'Split Photo / Text (LinkedIn)', desc: 'LinkedIn landscape — photo left, headline + body right' },
{ slug: 'quote-testimonial-li', label: 'Quote Testimonial (LinkedIn)', desc: 'LinkedIn landscape — pull quote + attribution row' },
{ slug: 'article-preview-li', label: 'Article Preview (LinkedIn)', desc: 'LinkedIn landscape — article title + excerpt + author row' },

// ── Phase 21 legacy (612×792 one-pager / US Letter) ────────────────────────
{ slug: 'case-study-op', label: 'Case Study (One-Pager)', desc: 'US Letter — case study layout with results and narrative zones' },
{ slug: 'company-overview-op', label: 'Company Overview (One-Pager)', desc: 'US Letter — company summary + key facts + services grid' },
{ slug: 'product-feature-op', label: 'Product Feature (One-Pager)', desc: 'US Letter — product hero + feature breakdown + specs' },

// ── Phase 23 new (1080×1350 instagram-portrait) ────────────────────────────
// Stat / Data
{ slug: 'hero-stat-45', label: 'Hero Stat 4:5', desc: 'Portrait 4:5 variant — headline + body + 3-stat row' },
{ slug: 'big-number-card', label: 'Big Number Card', desc: 'Single dominant milestone number with unit and note' },
{ slug: 'stat-comparison', label: 'Stat Comparison', desc: 'Before/after two-column split with headline and note' },
// Quote / Testimonial
{ slug: 'photocentric-quote', label: 'Photocentric Quote', desc: 'Hero photo upper zone + pull-quote lower zone' },
{ slug: 'typographic-quote', label: 'Typographic Quote', desc: 'Asymmetric display-word left / body-quote right (no photo)' },
{ slug: 'book-quote-highlight', label: 'Book Quote Highlight', desc: 'Cover image + 3 annotation callouts + excerpt footer' },
// Announcement
{ slug: 'coming-soon-minimal', label: 'Coming Soon Minimal', desc: 'Centered countdown — brand, headline, date, URL' },
{ slug: 'website-launch-mockup', label: 'Website Launch Mockup', desc: 'Bold headline + right-column screen/mockup image' },
{ slug: 'event-promo', label: 'Event Promo', desc: 'Full-bleed event photo + info band (date/time/location)' },
// Photo Collage
{ slug: 'vintage-scrapbook', label: 'Vintage Scrapbook', desc: '2x2 full-canvas photo grid + metadata overlay' },
{ slug: 'fashion-moodboard', label: 'Fashion Moodboard', desc: 'Hero photo + typography zone + 3-image detail strip' },
{ slug: 'memory-grid-4up', label: 'Memory Grid 4up', desc: 'Asymmetric 4-panel (1 large + 1 side + 2 bottom)' },
{ slug: 'asymmetric-photo-collage', label: 'Asymmetric Photo Collage', desc: '3-photo left-dominant split + headline overlay' },
// Hero Photo
{ slug: 'photo-darken-headline', label: 'Photo Darken Headline', desc: 'Full-bleed photo + gradient darkening + bottom text' },
{ slug: 'split-photo-feature', label: 'Split Photo Feature', desc: 'Photo left column / editorial text right column' },
// Tips / How-to
{ slug: 'numbered-tips-cover', label: 'Numbered Tips Cover', desc: 'Carousel cover with series label, headline, tip count, accent photo' },
{ slug: 'how-to-step-card', label: 'How-To Step Card', desc: 'Single step: photo + step number/headline + body + page indicator' },
// Personal / About
{ slug: 'about-me-portrait', label: 'About Me Portrait', desc: 'Arch-cropped portrait, name, orbital tags, tagline' },
{ slug: 'hiring-portrait-cta', label: 'Hiring Portrait CTA', desc: 'Badge + bold headline + team photo + role description + CTA' },
// Product
{ slug: 'product-hero-backlit', label: 'Product Hero Backlit', desc: 'Brand name + product display name + centered photo + price' },
{ slug: 'product-feature-grid', label: 'Product Feature Grid', desc: '2x2 product grid with name + price per cell' },
{ slug: 'product-callout-macro', label: 'Product Callout Macro', desc: 'Large product photo + bold callout + 3 feature bullets + CTA' },
// Motivational
{ slug: 'affirmation-note', label: 'Affirmation Note', desc: 'Text-only: category + date corners, bold affirmation, body, handle' },
{ slug: 'handwritten-quote-photo', label: 'Handwritten Quote Photo', desc: 'Lifestyle photo BG + large italic quote + attribution' },
// Carousel Cover
{ slug: 'carousel-cover-typographic', label: 'Carousel Cover Typographic', desc: 'Topic tag + ghost number + bold headline + subtitle + swipe badge' },
];

const grid = document.getElementById('grid');

// Legacy square (1080×1080) slugs — all Phase 19 archetypes without a suffix
const squareSlugs = new Set([
'stat-hero-single','hero-stat','hero-stat-split','data-dashboard',
'minimal-statement','minimal-photo-top','photo-bg-overlay',
'split-photo-text','split-photo-quote','quote-testimonial',
]);

function frameClass(slug) {
if (slug.endsWith('-li')) return 'linkedin'; // 1200×627
if (slug.endsWith('-op')) return 'onepager'; // 612×792
if (squareSlugs.has(slug)) return 'square'; // 1080×1080
return ''; // default: 1080×1350 portrait
}

archetypes.forEach(a => {
const cls = frameClass(a.slug);
const card = document.createElement('div');
card.className = 'card';
card.innerHTML = `
<div class="card-left">
<div class="card-label">${a.label}</div>
<div class="status">${a.desc}</div>
<div class="frame-wrap">
<div class="frame-wrap${cls ? ' ' + cls : ''}">
<iframe src="${a.slug}/index.html" loading="lazy"></iframe>
</div>
</div>
Expand Down
34 changes: 34 additions & 0 deletions archetypes/about-me-portrait/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# about-me-portrait

**Platform:** Instagram Portrait (1080 × 1350)

## What it is

A personal introduction layout: italic name at the top, a large arch-cropped portrait in the center, four interest/personality tags flanking the photo on the sides, and a tagline or mission statement at the bottom.

## Structural pattern

Name sits centered at y=100px in italic 72px. The portrait photo (720×820px) sits in the center with a rounded arch top (360px border-radius) creating an organic framing. Four short tags (28px, 500 weight) orbit the photo at mid-height — two on the left column (y=420, y=600) and two on the right (y=380, y=680). A centered tagline spans the bottom zone (bottom:120px, padded 80px each side).

## Content type fit

- Personal brand intro posts
- Founder / team member meet-and-greet posts
- Creator or influencer introduction
- "Hi, I'm [name]" posts for new audience segments

## When to use

- When building a personal brand and introducing yourself to followers
- When the message is "who I am and what I do"
- When a conversational, personality-driven aesthetic is the brand voice

## When NOT to use

- For purely professional hiring contexts (use `hiring-portrait-cta`)
- When the portrait is group shot or landscape-oriented
- When more than 4 interest tags are needed

## Components

- Centered arch-portrait + name + orbital interest tags + bottom tagline
Loading
Loading