# Design Agent - Technology Research Report

Date: 2026-03-24

---

## 1. CSS Grid for Slide Layouts

### 1.1 Fixed-Viewport Approach (16:9, 1280x720)

**Recommended technique: Fixed container + CSS transform scaling.**

The slide container should be authored at a fixed "normal" size (1280x720), then scaled to fit any viewport using `transform: scale()`. This is the same approach used by reveal.js, the dominant HTML presentation framework.

```css
.slide {
    width: 1280px;
    height: 720px;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    position: relative;
}
```

For preview/embedding, wrap in a container that calculates a scale factor:
```css
.slide-wrapper {
    width: 100%;
    max-width: 1280px;
    aspect-ratio: 16 / 9;
    overflow: hidden;
}
.slide-wrapper > .slide {
    transform-origin: top left;
    transform: scale(var(--slide-scale, 1));
}
```

The scale factor can be computed with minimal JS: `containerWidth / 1280`.

**Key insight from reveal.js:** All presentations have a "normal" size at which they are authored. The framework automatically scales uniformly to fit different resolutions without changing aspect ratio or layout. Default is 960x700; for our use case, 1280x720 is the standard 16:9 HD dimension.

**Why this works for Design Agent:** The renderer produces HTML at exactly 1280x720. It never needs to be "responsive" -- it's a fixed-format document like a PDF page. Scaling is only for preview purposes.

### 1.2 Grid-Template-Areas for Block Combinations

`grid-template-areas` provides named regions that map directly to the block composition concept in the CLAUDE.md:

```css
.layout-quote-compare-cards-conclusion {
    display: grid;
    grid-template-areas:
        "quote   quote"
        "compare cards"
        "diagram diagram"
        "conclusion conclusion";
    grid-template-columns: 1fr 1fr;
    grid-template-rows: auto 1fr auto auto;
    gap: var(--spacing-block);
    padding: var(--spacing-page);
}
```

**Best practice:** Define each layout as a separate CSS class with its own `grid-template-areas`. The Sonnet agent selects which layout class to apply based on the block combination it decides. This keeps the renderer deterministic -- it just applies the class.

**Reusability:** CSS variables allow the same grid template to adapt:
- Column count: `grid-template-columns: repeat(var(--cols, 3), 1fr)`
- Gap: `gap: var(--spacing-block)`
- Row sizing: `grid-template-rows` can mix `auto` (content-sized) and `1fr` (fill remaining)

### 1.3 Design Tokens

**Naming convention:** `--{category}-{property}-{variant}`

The CLAUDE.md already defines a good token set. The industry standard approach (from EightShapes, Nord Design System) uses kebab-case with semantic naming:

```
--color-primary, --color-accent, --color-neutral
--font-title, --font-subtitle, --font-body, --font-caption
--spacing-page, --spacing-block, --spacing-inner
--radius, --border-width, --accent-border
```

This matches what's already in the project's CLAUDE.md. No changes needed.

**For slide-specific tokens, add:**
```css
--slide-width: 1280px;
--slide-height: 720px;
--slide-aspect: 16 / 9;
```

### 1.4 Overflow Handling in Fixed Pages

Three techniques for ensuring content fits within fixed dimensions:

1. **Single-line truncation:**
   ```css
   .truncate {
       overflow: hidden;
       white-space: nowrap;
       text-overflow: ellipsis;
   }
   ```

2. **Multi-line truncation (line clamping):**
   ```css
   .line-clamp-3 {
       display: -webkit-box;
       -webkit-line-clamp: 3;
       -webkit-box-orient: vertical;
       overflow: hidden;
   }
   ```

3. **Container overflow hidden (safety net):**
   ```css
   .block { overflow: hidden; }
   .slide { overflow: hidden; }
   ```

**Korean-specific consideration:** `word-break: keep-all` affects how text wraps, which impacts line count. Content fitting calculations must account for this. The Sonnet agent should be instructed with character limits per slot, not word limits.

---

## 2. Content-to-Layout Classification

### 2.1 How to Prompt an LLM for Reliable Classification

**Claude Structured Output (recommended):**

Anthropic launched Structured Outputs in November 2025, supporting Claude Sonnet 4.5 and Opus 4.1. This guarantees JSON schema conformance at the token generation level -- the model literally cannot produce tokens that violate the schema.

Implementation:
```python
from pydantic import BaseModel, Field
from anthropic import Anthropic

class ContentBlock(BaseModel):
    content_type: str = Field(description="One of: comparison, process, relationship, big-number, definition, list, timeline, emphasis, problem")
    evidence: str = Field(description="Which text patterns led to this classification")
    suggested_block: str = Field(description="Block type from the template library")

class ContentAnalysis(BaseModel):
    blocks: list[ContentBlock]
    layout_direction: str = Field(description="How blocks should be arranged on the page")
    primary_message: str = Field(description="The single key takeaway for this slide")
```

**Reliability strategies:**
- Set temperature to 0.0-0.1 for classification tasks (reduces format drift)
- Use the `output_format` parameter with JSON schema (not just prompting)
- Include one perfect example in the system prompt
- Add explicit validation instructions

### 2.2 Information Type Taxonomy for Presentations

Based on research from SlideSpeak (16 layout types), PPTAgent (EMNLP 2025), Beautiful.ai (300 templates), and Dr. Andrew Abela's Chart Chooser:

**The CLAUDE.md already defines 9 excellent content types.** Here is how they map to industry precedent:

| CLAUDE.md Type | SlideSpeak Equivalent | PPTAgent Category | Common Slide Type |
|---|---|---|---|
| comparison | SS_ITEMS_*_A/B | Content slide (multi-column) | Comparison slide |
| process | SS_STEPS_3/4/5 | Content slide (sequential) | Process/workflow slide |
| relationship | (custom) | Content slide (diagram) | Venn/tree diagram slide |
| big-number | SS_BIGNUMBER_1/3 | Content slide (metric) | KPI/statistics slide |
| definition (card-grid) | SS_ITEMS_3/4/5/6 | Content slide (grid) | Definition/feature slide |
| list | SS_CONTENT | Content slide (list) | Bullet point slide |
| timeline | (custom) | Content slide (sequential) | Timeline slide |
| emphasis (quote-block) | (custom) | Structural slide | Quote/callout slide |
| problem | (custom) | Structural slide | Problem statement slide |

**Additional types to consider (from industry):**
- **SWOT** (SlideSpeak has SS_SWOT) -- 4-quadrant grid
- **Matrix/Table** (already covered by comparison-table)
- **Cover/Title** -- for when the content is just a single title/subtitle

### 2.3 Structured Output Schema for Layout Decisions

The PPTAgent paper (EMNLP 2025) uses a two-stage approach that aligns perfectly with the Design Agent architecture:

- **Stage 1 (Opus):** Analyze content, classify into functional types, extract content schemas
- **Stage 2 (Sonnet):** Select reference layouts, fill content into slots, apply editing actions

PPTAgent represents all parsed outputs in JSON format for LLM compatibility. The Design Agent should do the same.

---

## 3. Slot-Based Template Systems

### 3.1 SlideSpeak's Named Slot System

SlideSpeak uses a comprehensive naming convention for template placeholders:

**Layout names:** SS_COVER, SS_CONTENT, SS_TABLE_OF_CONTENT, SS_BIGNUMBER_1_A, SS_BIGNUMBER_3_A, SS_ITEMS_3_A through SS_ITEMS_6_B, SS_STEPS_3 through SS_STEPS_5_ICONS, SS_SWOT

**Universal slot names:**
- `SS_TITLE` -- slide title
- `SS_SUBTITLE` -- subtitle
- `SS_LOGO` -- logo placeholder
- `SS_IMAGE` -- general image
- `SS_PAGE` -- page number
- `SS_PRESENTATION_TITLE` -- footer title

**Multi-item slot naming pattern:**
- `SS_ITEM_{N}_TITLE` -- title for item N
- `SS_ITEM_{N}_CONTENT` -- content for item N
- `SS_ITEM_{N}_NUMBER` -- number for item N (big-number layouts)
- `SS_ICON_{N}` -- icon for item N

**Key insight for Design Agent:** The `{{SLOT_NAME}}` convention in CLAUDE.md maps well. Adopt a similar systematic naming: `{{BLOCK_TITLE}}`, `{{ITEM_1_TITLE}}`, `{{ITEM_1_CONTENT}}`, etc.

### 3.2 Jinja2 for Template Rendering

Jinja2 is the recommended engine. It integrates natively with FastAPI and Python.

**Block inheritance for base layout:**
```jinja2
{# base_slide.html #}
<!DOCTYPE html>
<html lang="ko">
<head>
    <link rel="stylesheet" href="design-tokens.css">
    <style>{% block extra_style %}{% endblock %}</style>
</head>
<body>
    <div class="slide">
        {% block content %}{% endblock %}
    </div>
</body>
</html>
```

**Block-level templates:**
```jinja2
{# blocks/comparison.html #}
<div class="block-comparison">
    <div class="col-left">
        <h3>{{ left_title }}</h3>
        <p>{{ left_content }}</p>
    </div>
    <div class="col-right">
        <h3>{{ right_title }}</h3>
        <p>{{ right_content }}</p>
    </div>
</div>
```

**Composition via includes:**
```jinja2
{# Generated by renderer based on Sonnet's layout decision #}
{% extends "base_slide.html" %}
{% block content %}
    {% include "blocks/quote-block.html" %}
    <div class="grid-row-2">
        {% include "blocks/comparison.html" %}
        {% include "blocks/card-grid.html" %}
    </div>
    {% include "blocks/conclusion-bar.html" %}
{% endblock %}
```

### 3.3 Slot Constraints

Each slot should have defined constraints that Sonnet respects:

| Slot Type | Max Characters (Korean) | Required | Notes |
|---|---|---|---|
| slide_title | 30 | Yes | Single line |
| block_title | 20 | Yes | Single line |
| item_title | 15 | Yes | Single line |
| item_content | 80 | No | 2-3 lines |
| quote_text | 120 | Yes | 3-4 lines |
| big_number | 8 | Yes | Number + unit |
| conclusion | 60 | Yes | Single line |
| caption | 40 | No | Single line |

**Korean consideration:** Korean characters are roughly 2x the width of Latin characters at the same font size. Character limits should be specified in characters, not words, since Korean doesn't use spaces the same way as English.

---

## 4. HTML to PDF Conversion

### 4.1 Playwright (Recommended)

**Why Playwright over Puppeteer:**
- Native Python SDK (no Node.js dependency for a Python project)
- Multiple browser support (Chromium, Firefox, WebKit), though PDF only works in Chromium
- Growing community, active maintenance, better CI/CD integration
- Full CSS Grid support via real Chromium rendering engine

**Python implementation:**
```python
from playwright.async_api import async_playwright

async def html_to_pdf(html_content: str, output_path: str) -> None:
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        page = await browser.new_page()
        await page.set_content(html_content, wait_until="networkidle")
        await page.pdf(
            path=output_path,
            width="1280px",
            height="720px",
            print_background=True,
            prefer_css_page_size=True,
        )
        await browser.close()
```

**Key options:**
- `print_background=True` -- required for background colors/images
- `prefer_css_page_size=True` -- lets CSS `@page` rules control dimensions
- `width`/`height` -- custom page dimensions (accepts px, in, mm, cm units)

### 4.2 Print CSS for Slide Format

```css
@media print {
    @page {
        size: 1280px 720px;
        margin: 0;
    }
    body {
        margin: 0;
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }
    .slide {
        width: 1280px;
        height: 720px;
        page-break-after: always;
        overflow: hidden;
    }
}
```

**`-webkit-print-color-adjust: exact`** is critical -- without it, background colors and images may be stripped in PDF output.

### 4.3 Quality Comparison

Both Puppeteer and Playwright use Chromium's print-to-PDF engine, so output quality is identical. The choice comes down to:

| Factor | Playwright | Puppeteer |
|---|---|---|
| Language | Python, JS, C#, Java | JS/Node.js only |
| PDF engine | Chromium only | Chromium only |
| CSS Grid quality | Excellent (Chromium) | Excellent (Chromium) |
| Korean font rendering | Excellent | Excellent |
| Install size | ~400MB (browser binary) | ~300MB |
| API ergonomics | Better async patterns | More established |

**Recommendation:** Playwright, because the Design Agent backend is Python. No need to bridge to Node.js.

### 4.4 Korean-Specific Considerations

- Fonts must be available on the server. Self-host Pretendard/Noto Sans KR WOFF2 files or use CDN.
- Set `lang="ko"` on the HTML element for proper line-breaking algorithms.
- Ensure `@font-face` declarations are loaded before PDF generation (`wait_until="networkidle"`).

---

## 5. Pure CSS Diagrams

### 5.1 Venn Diagrams (Pure CSS)

**Technique:** Overlapping circles with opacity and negative margins.

```css
.venn-container { display: flex; align-items: center; justify-content: center; }
.venn-circle {
    width: 200px; height: 200px;
    border-radius: 50%;
    opacity: 0.7;
    display: flex; align-items: center; justify-content: center;
    padding: 20px;
    text-align: center;
}
.venn-a { background: var(--color-accent); }
.venn-b { background: var(--color-neutral); margin-left: -60px; }
```

**Advanced approach (Adrian Roselli):** CSS Grid + `shape-outside` for text wrapping within overlapping regions. More complex but better for text-heavy Venn diagrams.

**Limitation:** Pure CSS Venn diagrams work well for 2-3 circles. Beyond that, SVG is more practical.

### 5.2 Flowcharts / Process Arrows (Pure CSS)

**Technique:** Flexbox/Grid layout + pseudo-elements for arrows.

```css
.process-steps { display: flex; align-items: center; gap: 0; }
.process-step {
    background: var(--color-bg-subtle);
    padding: var(--spacing-inner);
    position: relative;
    flex: 1;
}
.process-step + .process-step::before {
    content: '';
    position: absolute;
    left: -12px; top: 50%;
    transform: translateY(-50%);
    border: 8px solid transparent;
    border-left-color: var(--color-accent);
}
```

**CSS Anchor Positioning (2025-2026):** A new CSS feature for connecting elements with lines. Supported in Chrome 125+, Safari 26+, not yet in Firefox. Since we target Chromium (for PDF generation), this is usable but adds complexity. For the Design Agent, pseudo-element arrows are simpler and more reliable.

### 5.3 Tree/Hierarchy Diagrams (Pure CSS)

**Technique:** Nested `<ul>/<li>` + pseudo-elements for connector lines.

Libraries:
- **Treeflex** (https://dumptyd.github.io/treeflex/) -- CSS-only library for hierarchy trees, no JS
- Custom implementation using `border-left` on `<li>` for vertical lines, `::before` for horizontal connectors, `::after` for node circles

### 5.4 When to Use SVG Instead

| Use Case | CSS | SVG | Recommendation |
|---|---|---|---|
| Venn (2-3 circles) | Good | Better | CSS for simplicity |
| Process arrows (linear) | Excellent | Overkill | CSS |
| Tree (2-3 levels) | Good | Better | CSS (Treeflex) |
| Complex flowchart (branches) | Difficult | Much better | SVG |
| Curved connectors | Impossible | Easy | SVG |
| Data-driven charts | Impossible | Required | SVG |
| Accessible diagrams | Poor | Excellent | SVG |

**Recommendation for Design Agent:** Use pure CSS for simple diagrams (process arrows, 2-circle Venn, basic tree). Generate inline SVG for anything more complex. Since the renderer produces static HTML for PDF export, there's no JS concern.

**Accessibility note:** SVG has built-in accessibility elements (`<title>`, `<desc>`, `aria-*`). For the Design Agent's output (primarily visual slides for PDF), this is less critical but good practice.

---

## 6. Korean Typography in CSS

### 6.1 Font Selection

**Primary recommendation: Pretendard**

- Modern system-ui replacement font designed for cross-platform use
- Built on Inter (Latin) + Source Han Sans (CJK) + M PLUS 1p
- 9 weights + variable font support
- Dynamic subset via CDN (Google Fonts-style loading for Korean)
- Most popular Korean web font according to HTTP Archive 2024

**CDN options:**
```css
/* Dynamic subset (recommended for web) */
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css');

/* Or from cdnjs */
@import url('https://cdnjs.cloudflare.com/ajax/libs/pretendard/1.3.9/variable/pretendardvariable-dynamic-subset.min.css');
```

**For PDF generation (self-hosted):**
Download WOFF2 files and serve locally to avoid CDN dependency during headless browser PDF generation.

**Fallback stack:**
```css
font-family: 'Pretendard Variable', 'Pretendard', -apple-system, 'Noto Sans KR',
             'Malgun Gothic', sans-serif;
```

**Alternative: Noto Sans KR**
- Google Fonts native, widest browser support
- Available via Google Fonts CDN with automatic Korean subsetting
- Good for when Pretendard is not available

### 6.2 Line-Height and Letter-Spacing

**W3C KLREQ (Korean Layout Requirements) recommendations:**

| Property | Value | Rationale |
|---|---|---|
| `line-height` | 1.6-1.8 | CJK text needs ~1.7 (vs 1.2-1.5 for Latin) due to higher information density per character |
| `letter-spacing` | 0 to -0.02em | Korean text looks best with tight or default spacing. Avoid positive letter-spacing. |
| `word-spacing` | normal | Korean uses spaces between words (unlike Japanese/Chinese) |

**For slides specifically:**
```css
body {
    line-height: 1.7;
    letter-spacing: -0.01em;
}
h1, h2, h3 {
    line-height: 1.3;
    letter-spacing: -0.02em;
}
```

### 6.3 Word-Break Rules

```css
/* Korean text: keep syllable blocks together */
body {
    word-break: keep-all;
    overflow-wrap: break-word;
}
```

**`word-break: keep-all`** prevents line breaks within Korean syllable blocks (e.g., "한글" won't break as "한" / "글" mid-word). This is essential for readable Korean typography.

**`overflow-wrap: break-word`** is the safety net for extremely long strings (URLs, technical terms) that might overflow.

**Browser support:** All modern browsers support `keep-all` since 2016. There is ongoing W3C discussion about a potential `keep-all-hangul` value that would apply `keep-all` only to Hangul characters and `normal` to everything else.

### 6.4 Mixing Korean + English

**Key challenge:** At the same point size, Latin letters appear smaller than Korean characters.

**Solutions:**
1. Use a font designed for mixed text (Pretendard handles this well, being built on Inter + Source Han Sans)
2. Set the Latin font first in `font-family` stack, CJK font second (most CJK fonts include Latin glyphs, but their Latin is often inferior)
3. No need for `font-size-adjust` if using Pretendard (it's designed for optical balance between scripts)

**Punctuation:** Korean uses proportional-width punctuation (like Latin), unlike Japanese/Chinese which use full-width. Pretendard handles this correctly.

### 6.5 Language Attribute

Always set `lang="ko"` on the HTML element:
```html
<html lang="ko">
```
This enables the browser's Korean-specific line-breaking algorithms and font selection.

---

## 7. FastAPI Integration

### 7.1 Serving the Design Agent

The Design Agent should be a FastAPI application with these endpoints:

| Endpoint | Method | Purpose |
|---|---|---|
| `/api/analyze` | POST | Step 1: Send content to Opus for classification |
| `/api/generate` | POST | Steps 2-3: Sonnet selects + renderer produces HTML |
| `/api/generate/stream` | GET (SSE) | Steps 1-3 with real-time progress |
| `/api/preview/{job_id}` | GET | Return generated HTML for iframe preview |
| `/api/download/{job_id}/pdf` | GET | Generate and return PDF |
| `/api/download/{job_id}/html` | GET | Return HTML file |
| `/api/templates` | GET | List available block templates |

### 7.2 SSE Streaming

FastAPI has native SSE support (added to official docs). Two library options:

**Option A: sse-starlette (recommended)**
- Production-ready, W3C SSE spec compliant
- Already used in the HWPX project
- `pip install sse-starlette`

**Option B: fastapi-sse**
- Lighter weight, built specifically for FastAPI
- Supports sending Pydantic models as SSE events
- `pip install fastapi-sse`

**Implementation:**
```python
from sse_starlette.sse import EventSourceResponse

@router.get("/api/generate/stream")
async def stream_generation(content: str):
    async def event_generator():
        yield {"event": "step_start", "data": "analyzing"}
        # ... Opus classification
        yield {"event": "step_complete", "data": json.dumps(analysis)}

        yield {"event": "step_start", "data": "selecting"}
        # ... Sonnet selection
        yield {"event": "step_complete", "data": json.dumps(slots)}

        yield {"event": "step_start", "data": "rendering"}
        # ... CSS Grid rendering
        yield {"event": "complete", "data": job_id}

    return EventSourceResponse(event_generator())
```

### 7.3 File Upload

```python
from fastapi import UploadFile, File

@router.post("/api/upload")
async def upload_content(file: UploadFile = File(...)):
    # Read and extract text from uploaded file
    text = await extract_text(file)
    return {"text": text, "filename": file.filename}
```

### 7.4 Static File Serving for Preview

**For development:** FastAPI's `StaticFiles` mount for serving generated HTML:
```python
from fastapi.staticfiles import StaticFiles
app.mount("/static", StaticFiles(directory="output"), name="static")
```

**For production:** Serve HTML via API response body (like the HWPX project does with `<iframe srcDoc>`). This is more secure -- no direct file path exposure.

**Font serving:** Self-hosted Pretendard WOFF2 files should be served as static files for reliable PDF generation.

---

## Summary: Technology Stack Recommendation

| Component | Technology | Version | Rationale |
|---|---|---|---|
| LLM (Analysis) | Claude Opus via Anthropic API | Structured Outputs beta | Content classification, layout direction |
| LLM (Selection) | Claude Sonnet via Anthropic API | Structured Outputs beta | Slot filling, content editing |
| Template Engine | Jinja2 | >=3.1 | Native Python, FastAPI integration, block inheritance |
| CSS Layout | CSS Grid + grid-template-areas | Native CSS | Named regions map to block composition |
| Design Tokens | CSS Custom Properties | Native CSS | Already defined in CLAUDE.md |
| Typography | Pretendard Variable | 1.3.9 | Best Korean web font, dynamic subset |
| Fallback Font | Noto Sans KR | Latest | Google Fonts CDN backup |
| PDF Generation | Playwright (Python) | >=1.40 | Native Python SDK, full CSS Grid support |
| Web Framework | FastAPI | >=0.115 | SSE support, file upload, same as HWPX project |
| SSE | sse-starlette | >=2.0 | Production-ready, W3C compliant |
| CSS Diagrams | Pure CSS + inline SVG fallback | N/A | Pseudo-elements for simple, SVG for complex |
| Slide Scaling | CSS transform: scale() | Native CSS | reveal.js-proven approach |
| Korean Line Breaking | word-break: keep-all | Native CSS | W3C KLREQ recommendation |

---

## Key References

### CSS Grid & Layout
- [CSS-Tricks Complete Guide to CSS Grid](https://css-tricks.com/complete-guide-css-grid-layout/)
- [Smashing Magazine: Understanding CSS Grid Template Areas](https://www.smashingmagazine.com/2020/02/understanding-css-grid-template-areas/)
- [MDN: aspect-ratio](https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio)
- [Grid by Example](https://gridbyexample.com/examples/)
- [reveal.js Presentation Size](https://revealjs.com/presentation-size/)

### Design Tokens
- [CSS-Tricks: What Are Design Tokens?](https://css-tricks.com/what-are-design-tokens/)
- [EightShapes: Naming Tokens in Design Systems](https://medium.com/eightshapes-llc/naming-tokens-in-design-systems-9e86c7444676)
- [FrontendTools: CSS Variables Guide](https://www.frontendtools.tech/blog/css-variables-guide-design-tokens-theming-2025)
- [Nord Design System: Naming](https://nordhealth.design/naming/)

### Content Classification & Presentation AI
- [PPTAgent (EMNLP 2025)](https://arxiv.org/abs/2501.03936)
- [SlideSpeak Layouts & Placeholders](https://docs.slidespeak.co/basics/custom-templates/layouts-and-placeholders)
- [SlideSpeak Template Preparation](https://docs.slidespeak.co/basics/custom-templates/preparing)
- [SlideModel: 12 Types of Slides](https://slidemodel.com/types-of-slides/)
- [SlideUpLift: Types of Slides](https://slideuplift.com/blog/types-of-slides/)
- [LLM-Powered Slide Decks Comparison](https://nbrosse.github.io/posts/llm-slides/llm-slides.html)

### Structured Output
- [Anthropic Structured Outputs Docs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs)
- [PromptLayer: How JSON Schema Works for Structured Outputs](https://blog.promptlayer.com/how-json-schema-works-for-structured-outputs-and-tool-integration/)

### PDF Generation
- [Playwright Python page.pdf() API](https://playwright.dev/python/docs/api/class-page)
- [PDF Generation from HTML Comparison (2026)](https://medium.com/@coders.stop/pdf-generation-from-html-i-tested-puppeteer-playwright-and-wkhtmltopdf-so-you-dont-have-to-d14228d28c4c)
- [Checkly: Generating PDFs with Playwright](https://www.checklyhq.com/docs/learn/playwright/generating-pdfs/)
- [Print CSS Cheatsheet](https://www.customjs.space/blog/print-css-cheatsheet/)

### Pure CSS Diagrams
- [Adrian Roselli: A CSS Venn Diagram](https://adrianroselli.com/2018/12/a-css-venn-diagram.html)
- [CSS-Tricks: A CSS Venn Diagram](https://css-tricks.com/a-css-venn-diagram/)
- [FreeFrontend: 17 Pure CSS Flowcharts](https://freefrontend.com/css-flowcharts/)
- [Cory Rylan: Flow Charts with CSS Anchor Positioning](https://coryrylan.com/blog/flow-charts-with-css-anchor-positioning)
- [Treeflex: CSS Tree Library](https://dumptyd.github.io/treeflex/)
- [MDN: CSS Anchor Positioning](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Anchor_positioning)

### Korean Typography
- [W3C KLREQ: Requirements for Hangul Text Layout](https://www.w3.org/TR/klreq/)
- [Pretendard GitHub](https://github.com/orioncactus/pretendard)
- [Noto Sans KR on Google Fonts](https://fonts.google.com/noto/specimen/Noto+Sans+KR)
- [CJK Typesetting in 2025](https://asianabsolute.co.uk/blog/cjk-typesetting-challenges-workflows-and-best-practices/)
- [Typotheque: CJK Typesetting Principles](https://www.typotheque.com/articles/typesetting-cjk-text)
- [CSS WG: word-break for Korean (Issue #4285)](https://github.com/w3c/csswg-drafts/issues/4285)

### FastAPI & SSE
- [FastAPI SSE Tutorial](https://fastapi.tiangolo.com/tutorial/server-sent-events/)
- [sse-starlette on PyPI](https://pypi.org/project/sse-starlette/)
- [Real Python: FastAPI with Jinja2](https://realpython.com/fastapi-jinja2-template/)

### SVG vs CSS
- [Adobe Blog: CSS vs SVG](https://blog.adobe.com/en/publish/2015/09/16/css-vs-svg-the-final-roundup)
- [Sara Soueidan: Accessible Data Charts](https://www.sarasoueidan.com/blog/accessible-data-charts-for-khan-academy-2018-annual-report/)
