ima2-gen 2.0.15 → 2.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/bin/commands/skill.js +336 -17
- package/bin/ima2.js +22 -2
- package/bin/lib/doctor-checks.js +6 -4
- package/config.js +1 -0
- package/docs/CLI.md +25 -3
- package/docs/PROMPT_STUDIO.ko.md +29 -0
- package/docs/PROMPT_STUDIO.md +31 -0
- package/docs/migration/runtime-test-inventory.md +7 -1
- package/lib/agentImageVideoGen.js +8 -0
- package/lib/agentQueueStore.js +36 -10
- package/lib/agentQueueWorker.js +79 -7
- package/lib/agentRuntime.js +2 -0
- package/lib/assetLifecycle.js +40 -8
- package/lib/canvasVersionStore.js +86 -0
- package/lib/cardNewsGenerator.js +37 -3
- package/lib/cardNewsJobStore.js +10 -0
- package/lib/cardNewsManifestStore.js +6 -12
- package/lib/cardNewsPath.js +25 -0
- package/lib/db.js +2 -0
- package/lib/generatePipeline.js +474 -0
- package/lib/generationInputValidation.js +32 -0
- package/lib/grokImageCore.js +18 -1
- package/lib/historyIndex.js +11 -4
- package/lib/historyList.js +3 -0
- package/lib/imageMetadata.js +3 -0
- package/lib/inflight.js +30 -3
- package/lib/multimodePipeline.js +462 -0
- package/lib/nodeGeneration.js +440 -0
- package/lib/nodeValidation.js +23 -0
- package/lib/oauthProxy/generators.js +3 -246
- package/lib/oauthProxy/multimodeGenerators.js +251 -0
- package/lib/routeHelpers.js +9 -2
- package/package.json +2 -2
- package/routes/agent.js +4 -4
- package/routes/canvasVersions.js +21 -1
- package/routes/cardNews.js +2 -1
- package/routes/edit.js +25 -9
- package/routes/events.js +5 -1
- package/routes/generate.js +2 -481
- package/routes/keys.js +31 -39
- package/routes/multimode.js +2 -502
- package/routes/nodes.js +3 -448
- package/routes/video.js +12 -3
- package/routes/videoExtended.js +19 -4
- package/server.js +53 -1
- package/skills/ima2/SKILL.md +204 -0
- package/skills/ima2-front/SKILL.md +611 -0
- package/skills/ima2-front/references/a11y-patterns.md +134 -0
- package/skills/ima2-front/references/aesthetics.md +327 -0
- package/skills/ima2-front/references/anti-slop.md +241 -0
- package/skills/ima2-front/references/asset-requirements.md +431 -0
- package/skills/ima2-front/references/brand-asset-sourcing.md +122 -0
- package/skills/ima2-front/references/color-system.md +132 -0
- package/skills/ima2-front/references/consistency-locks.md +20 -0
- package/skills/ima2-front/references/crud-ui.md +53 -0
- package/skills/ima2-front/references/dropdown-layer.md +110 -0
- package/skills/ima2-front/references/i18n-global.md +104 -0
- package/skills/ima2-front/references/iterative-design.md +90 -0
- package/skills/ima2-front/references/korea-2026.md +174 -0
- package/skills/ima2-front/references/layout-discipline.md +161 -0
- package/skills/ima2-front/references/liquid-glass.md +181 -0
- package/skills/ima2-front/references/logo-sections.md +194 -0
- package/skills/ima2-front/references/mobile-ux.md +144 -0
- package/skills/ima2-front/references/performance-budget.md +106 -0
- package/skills/ima2-front/references/preflight-full.md +58 -0
- package/skills/ima2-front/references/product-density.md +53 -0
- package/skills/ima2-front/references/prototype-variants.md +54 -0
- package/skills/ima2-front/references/reference-capture.md +120 -0
- package/skills/ima2-front/references/responsive-viewport.md +139 -0
- package/skills/ima2-front/references/seo-baseline.md +123 -0
- package/skills/ima2-front/references/soft-3d-asset-gates.md +70 -0
- package/skills/ima2-front/references/stacks/astro.md +204 -0
- package/skills/ima2-front/references/stacks/mobile-native.md +281 -0
- package/skills/ima2-front/references/stacks/nextjs.md +219 -0
- package/skills/ima2-front/references/stacks/react.md +264 -0
- package/skills/ima2-front/references/stacks/svelte.md +235 -0
- package/skills/ima2-front/references/stacks/vanilla.md +226 -0
- package/skills/ima2-front/references/theme-switching.md +82 -0
- package/skills/ima2-front/references/top-bar.md +126 -0
- package/skills/ima2-front/references/typography-wrapping.md +270 -0
- package/skills/ima2-front/references/ux-writing-ko.md +62 -0
- package/skills/ima2-front/references/visual-verification.md +102 -0
- package/skills/ima2-uiux/SKILL.md +477 -0
- package/skills/ima2-uiux/references/anti-slop-judgment.md +39 -0
- package/skills/ima2-uiux/references/color-system.md +80 -0
- package/skills/ima2-uiux/references/design-isms.md +194 -0
- package/skills/ima2-uiux/references/design-read-example.md +33 -0
- package/skills/ima2-uiux/references/design-system-bootstrap.md +133 -0
- package/skills/ima2-uiux/references/favicon-logo.md +355 -0
- package/skills/ima2-uiux/references/form-patterns.md +118 -0
- package/skills/ima2-uiux/references/intent-discovery-ladder.md +86 -0
- package/skills/ima2-uiux/references/korean-design-vocabulary.md +52 -0
- package/skills/ima2-uiux/references/layout-macrostructures.md +54 -0
- package/skills/ima2-uiux/references/logo-trust-sections.md +96 -0
- package/skills/ima2-uiux/references/mobile-native-ux.md +207 -0
- package/skills/ima2-uiux/references/product-personalities.md +118 -0
- package/skills/ima2-uiux/references/responsive-nav.md +66 -0
- package/skills/ima2-uiux/references/typography-line-breaks.md +193 -0
- package/skills/ima2-uiux/references/ux-preflight.md +65 -0
- package/skills/ima2-uiux/references/ux-states.md +197 -0
- package/skills/ima2-uiux/references/visual-hierarchy.md +115 -0
- package/ui/dist/.vite/manifest.json +13 -13
- package/ui/dist/assets/AgentWorkspace-CtYt4SF5.js +3 -0
- package/ui/dist/assets/{CardNewsWorkspace-Cdxn1ohU.js → CardNewsWorkspace-DN5cVqG5.js} +1 -1
- package/ui/dist/assets/GenerationRequestLogPanel-DnmUQdKR.js +1 -0
- package/ui/dist/assets/{NodeCanvas-C67sLboA.js → NodeCanvas-CxpfIBI0.js} +1 -1
- package/ui/dist/assets/PromptBuilderPanel-C2R3ObtR.js +2 -0
- package/ui/dist/assets/{PromptImportDialog-DeQXsEEv.js → PromptImportDialog-BIxz_yEx.js} +2 -2
- package/ui/dist/assets/PromptImportDiscoverySection-BQTlPMME.js +1 -0
- package/ui/dist/assets/PromptImportFolderSection-Ds_Th9O3.js +1 -0
- package/ui/dist/assets/{PromptLibraryPanel-Db3xEPM_.js → PromptLibraryPanel-Rs4l45pV.js} +2 -2
- package/ui/dist/assets/{SettingsWorkspace-BDk_1W-V.js → SettingsWorkspace-p1AZ6uT9.js} +1 -1
- package/ui/dist/assets/index-0-_vgFGs.js +4 -0
- package/ui/dist/assets/index-Dm3pFxV4.js +23 -0
- package/ui/dist/assets/index-J8yDF3Ch.css +1 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/AgentWorkspace-DAg_7zpY.js +0 -3
- package/ui/dist/assets/GenerationRequestLogPanel-C_qvEUi2.js +0 -1
- package/ui/dist/assets/PromptBuilderPanel-DjvVdAie.js +0 -2
- package/ui/dist/assets/PromptImportDiscoverySection-CrwyUKYI.js +0 -1
- package/ui/dist/assets/PromptImportFolderSection-CToGMqYE.js +0 -1
- package/ui/dist/assets/index-2oG6in1i.css +0 -1
- package/ui/dist/assets/index-CoxMOXAU.js +0 -23
- package/ui/dist/assets/index-p8egnGHk.js +0 -4
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Vanilla Stack — HTML + CSS + JS (No Framework)
|
|
2
|
+
|
|
3
|
+
Rules for building production-quality frontends without React/Vue/etc.
|
|
4
|
+
Read `core/aesthetics.md` + `core/anti-slop.md` first.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Core Principle: Zero Dependencies
|
|
9
|
+
|
|
10
|
+
Single self-contained HTML files with inline CSS/JS. No npm, no build tools, no bundler.
|
|
11
|
+
The file IS the deliverable.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Structure
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<!DOCTYPE html>
|
|
19
|
+
<html lang="ko">
|
|
20
|
+
<head>
|
|
21
|
+
<meta charset="UTF-8" />
|
|
22
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
23
|
+
<title>Page Title</title>
|
|
24
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
25
|
+
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;500;700&display=swap" rel="stylesheet" />
|
|
26
|
+
<style>
|
|
27
|
+
/* === DESIGN TOKENS === */
|
|
28
|
+
:root {
|
|
29
|
+
--color-primary: #0ea5e9;
|
|
30
|
+
--color-surface: #fafafa;
|
|
31
|
+
--color-text: #18181b;
|
|
32
|
+
--color-muted: #71717a;
|
|
33
|
+
--font-display: 'Outfit', sans-serif;
|
|
34
|
+
--font-body: 'Outfit', sans-serif;
|
|
35
|
+
--radius: 0.75rem;
|
|
36
|
+
--max-width: 1200px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* === RESET === */
|
|
40
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
41
|
+
body { font-family: var(--font-body); color: var(--color-text); background: var(--color-surface); }
|
|
42
|
+
|
|
43
|
+
/* === LAYOUT === */
|
|
44
|
+
/* ... */
|
|
45
|
+
|
|
46
|
+
/* === COMPONENTS === */
|
|
47
|
+
/* ... */
|
|
48
|
+
|
|
49
|
+
/* === ANIMATIONS === */
|
|
50
|
+
/* ... */
|
|
51
|
+
|
|
52
|
+
/* === RESPONSIVE === */
|
|
53
|
+
@media (max-width: 768px) { /* ... */ }
|
|
54
|
+
|
|
55
|
+
/* === REDUCED MOTION === */
|
|
56
|
+
@media (prefers-reduced-motion: reduce) {
|
|
57
|
+
*, *::before, *::after {
|
|
58
|
+
animation-duration: 0.01ms !important;
|
|
59
|
+
transition-duration: 0.01ms !important;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
</style>
|
|
63
|
+
</head>
|
|
64
|
+
<body>
|
|
65
|
+
<!-- Semantic HTML -->
|
|
66
|
+
<script>
|
|
67
|
+
// Minimal JS — progressive enhancement only
|
|
68
|
+
</script>
|
|
69
|
+
</body>
|
|
70
|
+
</html>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Typography
|
|
76
|
+
|
|
77
|
+
Use fonts from **Google Fonts** or **Fontshare** — never system fonts.
|
|
78
|
+
|
|
79
|
+
```html
|
|
80
|
+
<!-- Fontshare (free, high quality) -->
|
|
81
|
+
<link href="https://api.fontshare.com/v2/css?f[]=satoshi@400;500;700&display=swap" rel="stylesheet" />
|
|
82
|
+
|
|
83
|
+
<!-- Google Fonts -->
|
|
84
|
+
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;500;700&display=swap" rel="stylesheet" />
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Fluid Typography (Mandatory)
|
|
88
|
+
```css
|
|
89
|
+
h1 { font-size: clamp(2rem, 1rem + 3.6vw, 4rem); }
|
|
90
|
+
h2 { font-size: clamp(1.5rem, 1rem + 1.4vw, 2.25rem); }
|
|
91
|
+
p { font-size: clamp(1rem, 0.95rem + 0.2vw, 1.125rem); }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**NEVER** use fixed `px` for text. Always `clamp()`.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## CSS Animation (Preferred over JS)
|
|
99
|
+
|
|
100
|
+
### Staggered Reveal on Load
|
|
101
|
+
```css
|
|
102
|
+
.reveal {
|
|
103
|
+
opacity: 0;
|
|
104
|
+
transform: translateY(20px);
|
|
105
|
+
animation: fadeUp 0.6s ease forwards;
|
|
106
|
+
}
|
|
107
|
+
.reveal:nth-child(1) { animation-delay: 0.1s; }
|
|
108
|
+
.reveal:nth-child(2) { animation-delay: 0.2s; }
|
|
109
|
+
.reveal:nth-child(3) { animation-delay: 0.3s; }
|
|
110
|
+
|
|
111
|
+
@keyframes fadeUp {
|
|
112
|
+
to { opacity: 1; transform: translateY(0); }
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### CSS Custom Property for Stagger Index
|
|
117
|
+
```css
|
|
118
|
+
.reveal {
|
|
119
|
+
animation-delay: calc(var(--index, 0) * 100ms);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
```html
|
|
123
|
+
<div class="reveal" style="--index: 0">First</div>
|
|
124
|
+
<div class="reveal" style="--index: 1">Second</div>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Hover Effects
|
|
128
|
+
```css
|
|
129
|
+
.card {
|
|
130
|
+
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1),
|
|
131
|
+
box-shadow 0.3s ease;
|
|
132
|
+
}
|
|
133
|
+
.card:hover {
|
|
134
|
+
transform: translateY(-4px);
|
|
135
|
+
box-shadow: 0 20px 40px -15px rgba(0,0,0,0.08);
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Viewport Fitting (Critical for Full-Page Layouts)
|
|
142
|
+
|
|
143
|
+
```css
|
|
144
|
+
.section-full {
|
|
145
|
+
min-height: 100dvh; /* NOT 100vh — iOS Safari breaks */
|
|
146
|
+
display: grid;
|
|
147
|
+
place-items: center;
|
|
148
|
+
overflow: hidden;
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Content Density Limits per Section:**
|
|
153
|
+
|
|
154
|
+
| Type | Maximum |
|
|
155
|
+
| ------------ | --------------------------------------- |
|
|
156
|
+
| Hero | 1 heading + 1 subtitle + 1 CTA |
|
|
157
|
+
| Content | 1 heading + 4-6 bullets OR 2 paragraphs |
|
|
158
|
+
| Feature grid | 1 heading + 6 cards max (2×3) |
|
|
159
|
+
| Quote | 1 quote (max 3 lines) + attribution |
|
|
160
|
+
|
|
161
|
+
Content exceeds limits? Split into multiple sections.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Responsive Layout
|
|
166
|
+
|
|
167
|
+
```css
|
|
168
|
+
/* Mobile-first */
|
|
169
|
+
.grid {
|
|
170
|
+
display: grid;
|
|
171
|
+
gap: 1.5rem;
|
|
172
|
+
grid-template-columns: 1fr;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@media (min-width: 768px) {
|
|
176
|
+
.grid { grid-template-columns: repeat(2, 1fr); }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@media (min-width: 1024px) {
|
|
180
|
+
.grid { grid-template-columns: repeat(3, 1fr); }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/* Container */
|
|
184
|
+
.container {
|
|
185
|
+
width: 100%;
|
|
186
|
+
max-width: var(--max-width);
|
|
187
|
+
margin: 0 auto;
|
|
188
|
+
padding: 0 1rem;
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**NEVER** use `calc()` flexbox math. Always CSS Grid.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Progressive Enhancement
|
|
197
|
+
|
|
198
|
+
1. Content readable without JS
|
|
199
|
+
2. Layout works without CSS animations
|
|
200
|
+
3. JS adds interactivity, not structure
|
|
201
|
+
4. Use `<noscript>` for critical JS-dependent features
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Semantic HTML (Mandatory)
|
|
206
|
+
|
|
207
|
+
```html
|
|
208
|
+
<header>...</header>
|
|
209
|
+
<nav aria-label="Main navigation">...</nav>
|
|
210
|
+
<main id="main-content">
|
|
211
|
+
<section aria-labelledby="features-heading">
|
|
212
|
+
<h2 id="features-heading">Features</h2>
|
|
213
|
+
</section>
|
|
214
|
+
</main>
|
|
215
|
+
<footer>...</footer>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Use `<button>` for actions, `<a>` for navigation. Never `<div onclick>`.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## File Delivery
|
|
223
|
+
|
|
224
|
+
Output a single `.html` file. All CSS in `<style>`, all JS in `<script>`.
|
|
225
|
+
Every section needs `/* === SECTION NAME === */` comments.
|
|
226
|
+
The file should be openable directly in a browser — no build step.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Theme / Dark Mode
|
|
2
|
+
|
|
3
|
+
## Implementation
|
|
4
|
+
|
|
5
|
+
Use `data-theme` attribute on `<html>`, not class-based toggle.
|
|
6
|
+
|
|
7
|
+
```css
|
|
8
|
+
:root {
|
|
9
|
+
--bg-primary: oklch(98% 0 0);
|
|
10
|
+
--text-primary: oklch(15% 0 0);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
:root[data-theme="dark"] {
|
|
14
|
+
--bg-primary: oklch(12% 0 0);
|
|
15
|
+
--text-primary: oklch(92% 0 0);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@media (prefers-color-scheme: dark) {
|
|
19
|
+
:root:not([data-theme]) {
|
|
20
|
+
--bg-primary: oklch(12% 0 0);
|
|
21
|
+
--text-primary: oklch(92% 0 0);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- `data-theme` takes precedence over media query
|
|
27
|
+
- System preference is the fallback when no explicit choice
|
|
28
|
+
- All colors MUST use CSS custom properties
|
|
29
|
+
(token layering + `oklch()`/`light-dark()` mechanics: `color-system.md`)
|
|
30
|
+
|
|
31
|
+
## Flash of Wrong Theme (FOWT) Prevention
|
|
32
|
+
|
|
33
|
+
```html
|
|
34
|
+
<script>
|
|
35
|
+
(function() {
|
|
36
|
+
var t = localStorage.getItem('theme');
|
|
37
|
+
if (t === 'dark' || t === 'light') {
|
|
38
|
+
document.documentElement.setAttribute('data-theme', t);
|
|
39
|
+
}
|
|
40
|
+
})();
|
|
41
|
+
</script>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- Inline in `<head>`, before any stylesheet (< 200 bytes)
|
|
45
|
+
- SSR: read theme cookie server-side, set `data-theme` in HTML response
|
|
46
|
+
|
|
47
|
+
## Toggle Component
|
|
48
|
+
|
|
49
|
+
Three states: Light → Dark → System → Light.
|
|
50
|
+
|
|
51
|
+
- Store: `localStorage.setItem('theme', 'dark' | 'light' | 'system')`
|
|
52
|
+
- `system` removes `data-theme`, lets media query decide
|
|
53
|
+
- Accessible: `role="button"`, `aria-label="Switch to dark theme"`
|
|
54
|
+
- Position: header or settings, never hidden in submenu
|
|
55
|
+
|
|
56
|
+
## Theme Transition
|
|
57
|
+
|
|
58
|
+
```css
|
|
59
|
+
:root[data-theme-transitioning] * {
|
|
60
|
+
transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease !important;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- Add transitioning attribute on toggle, remove after 200ms
|
|
65
|
+
- Only transition color properties
|
|
66
|
+
- Respect `prefers-reduced-motion`: skip transition
|
|
67
|
+
- Never animate on page load
|
|
68
|
+
|
|
69
|
+
## Component Checklist
|
|
70
|
+
|
|
71
|
+
- [ ] All colors use custom properties
|
|
72
|
+
- [ ] Shadows lighter in dark mode
|
|
73
|
+
- [ ] White-background images get border in dark mode
|
|
74
|
+
- [ ] SVG icons use `currentColor`
|
|
75
|
+
- [ ] Borders use surface-level difference in dark mode
|
|
76
|
+
- [ ] Charts/data-viz legible in both themes
|
|
77
|
+
- [ ] Form inputs have sufficient contrast in both themes
|
|
78
|
+
|
|
79
|
+
## Cross-references
|
|
80
|
+
|
|
81
|
+
- Dark token derivation: `dev-uiux-design/references/color-system.md`
|
|
82
|
+
- One theme per page: `consistency-locks.md`
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Top Bar Composition - Implementation Guide
|
|
2
|
+
|
|
3
|
+
Canonical owner for top-bar composition: geometry, slots, scroll-state
|
|
4
|
+
contract, spawned surfaces, and verification hooks. Material recipes live in
|
|
5
|
+
`liquid-glass.md`; do not duplicate opacity/blur recipes here. The top-bar
|
|
6
|
+
domain GATE lives in this file (FE-TOPBAR-DOMAIN-01 below); broader brand/
|
|
7
|
+
style judgment beyond that gate lives in `ima2-uiux`.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Domain Gate (FE-TOPBAR-DOMAIN-01, STRICT)
|
|
12
|
+
|
|
13
|
+
Liquid pill top bars are for expressive surfaces: landing, campaign,
|
|
14
|
+
editorial, portfolio, brand, and visual product pages.
|
|
15
|
+
|
|
16
|
+
Dashboards, admin, finance, government, B2B repeated-work tools, auth,
|
|
17
|
+
payment, security, and developer consoles keep standard solid headers by
|
|
18
|
+
default. Their chrome should be predictable, dense, and readable before it is
|
|
19
|
+
expressive.
|
|
20
|
+
|
|
21
|
+
## Geometry
|
|
22
|
+
|
|
23
|
+
Prefer a floating detached pill bar only when the first viewport can support it.
|
|
24
|
+
The bar must have a capped max-width, top inset, safe-area handling, and a
|
|
25
|
+
single outer capsule radius (`9999px` or >= 24px).
|
|
26
|
+
|
|
27
|
+
| Surface | Geometry | Notes |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| Landing / campaign / portfolio | Floating detached pill | Legal when the hero has a calm bar zone and enough top breathing room. |
|
|
30
|
+
| Editorial / brand story | Floating pill or quiet full-width | Choose based on reading comfort and hero image complexity. |
|
|
31
|
+
| Consumer app shell | Full-width or compact solid header | Use pill only for promotional/home surfaces, not repeated workflows. |
|
|
32
|
+
| Dashboard / admin / finance / gov / B2B tool | Full-width solid header | Avoid detached liquid chrome; preserve scanning and task speed. |
|
|
33
|
+
|
|
34
|
+
STYLE_SAMPLE:
|
|
35
|
+
|
|
36
|
+
```css
|
|
37
|
+
.top-bar {
|
|
38
|
+
position: sticky;
|
|
39
|
+
top: max(12px, env(safe-area-inset-top));
|
|
40
|
+
z-index: 40;
|
|
41
|
+
width: min(calc(100% - 32px), 1120px);
|
|
42
|
+
margin-inline: auto;
|
|
43
|
+
border-radius: 9999px;
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Slots
|
|
48
|
+
|
|
49
|
+
Keep the bar sparse enough that the pill still reads as one piece of chrome:
|
|
50
|
+
|
|
51
|
+
- Logo: use the real brand SVG; see `brand-asset-sourcing.md`.
|
|
52
|
+
- Navigation: 2-5 links. More destinations move into a menu or full navigation
|
|
53
|
+
surface.
|
|
54
|
+
- Controls: only include controls that earn their place in the first viewport
|
|
55
|
+
(for example theme, language, account, or search when essential).
|
|
56
|
+
- CTA: one filled pill CTA is allowed. It must not create a row of competing
|
|
57
|
+
primary actions.
|
|
58
|
+
|
|
59
|
+
Mobile collapses to logo + menu trigger. The menu opens a sheet or drawer with
|
|
60
|
+
a complete tap path; do not rely on hover behavior on touch devices.
|
|
61
|
+
|
|
62
|
+
## Scroll State (FE-TOPBAR-STATE-01, DEFAULT)
|
|
63
|
+
|
|
64
|
+
Top bars may be scroll-adaptive:
|
|
65
|
+
|
|
66
|
+
- At top: a lighter material state around 70-80% opacity is allowed only over a
|
|
67
|
+
hero authored with a calm bar zone behind the chrome.
|
|
68
|
+
- Scrolled: use the near-opaque 85-95% opacity range and keep blur when the
|
|
69
|
+
selected material state calls for it.
|
|
70
|
+
- Transition: animate `background-color` and `opacity` only. Do not morph size,
|
|
71
|
+
width, radius, or layout on blurred bars.
|
|
72
|
+
- Accessibility supremacy: `FE-LIQUID-A11Y-01` in `liquid-glass.md` overrides
|
|
73
|
+
every state here. Reduced-transparency, increased-contrast, unsupported
|
|
74
|
+
`backdrop-filter`, or failed worst-case contrast collapses all states to
|
|
75
|
+
solid or near-opaque.
|
|
76
|
+
|
|
77
|
+
STYLE_SAMPLE:
|
|
78
|
+
|
|
79
|
+
```css
|
|
80
|
+
.top-bar {
|
|
81
|
+
transition: background-color 180ms ease, opacity 180ms ease;
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Hover Surfaces (FE-TOPBAR-HOVER-01, DEFAULT)
|
|
86
|
+
|
|
87
|
+
Any surface spawned from the bar is unconditionally near-opaque, blur-free, and
|
|
88
|
+
solid: dropdown, mega-menu, popover, account menu, language picker, or search
|
|
89
|
+
panel. Content must remain legible over the busiest background that can appear
|
|
90
|
+
behind it.
|
|
91
|
+
|
|
92
|
+
Hover surfaces also need a tap path. Mobile uses the same visual skin where
|
|
93
|
+
appropriate, but the interaction becomes a sheet, drawer, or proven picker
|
|
94
|
+
primitive with correct ARIA and keyboard behavior.
|
|
95
|
+
|
|
96
|
+
## Child Visuals
|
|
97
|
+
|
|
98
|
+
Nested pill visuals are governed by `FE-PILL-NEST-01` in `liquid-glass.md`.
|
|
99
|
+
Inside pill chrome, child controls render no capsule borders or outlines at
|
|
100
|
+
rest. Solid fills are not restricted by that rule; use hierarchy and accent
|
|
101
|
+
budget to decide how many strong fills the bar can carry.
|
|
102
|
+
|
|
103
|
+
Use fill or tint, not rest-state capsule borders, for `aria-current` and active
|
|
104
|
+
navigation state.
|
|
105
|
+
|
|
106
|
+
## Accessibility
|
|
107
|
+
|
|
108
|
+
The full bar and every spawned surface must be keyboard reachable:
|
|
109
|
+
|
|
110
|
+
- Tab order enters the logo, links, controls, CTA, and spawned surfaces in a
|
|
111
|
+
predictable path.
|
|
112
|
+
- Escape closes spawned surfaces and returns focus to the trigger.
|
|
113
|
+
- Arrow-key behavior follows the underlying primitive pattern for menu,
|
|
114
|
+
combobox, listbox, or picker.
|
|
115
|
+
- Sticky chrome never hides focused content; apply `scroll-margin-top` or
|
|
116
|
+
equivalent offsets to in-page targets.
|
|
117
|
+
- Skip links remain visible and usable with the sticky bar present.
|
|
118
|
+
|
|
119
|
+
## Verification Hooks
|
|
120
|
+
|
|
121
|
+
Screenshot or otherwise inspect:
|
|
122
|
+
|
|
123
|
+
- At-top and scrolled states over their real hero/background.
|
|
124
|
+
- A bar-spawned hover surface over the busiest possible background.
|
|
125
|
+
- Reduced-transparency / increased-contrast collapse to solid or near-opaque.
|
|
126
|
+
- Mobile logo + menu trigger and the resulting sheet or drawer tap path.
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Typography Wrapping — CSS Text Control
|
|
2
|
+
|
|
3
|
+
Production CSS for text wrapping, line breaks, orphan prevention, and readable line lengths.
|
|
4
|
+
Apply globally in resets or design system base styles.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Global Production Template (2026 Standard)
|
|
9
|
+
|
|
10
|
+
```css
|
|
11
|
+
/* Headings — balance for visual hierarchy */
|
|
12
|
+
h1, h2, h3, h4, h5, h6,
|
|
13
|
+
blockquote, figcaption, .hero-title {
|
|
14
|
+
text-wrap: balance;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* Short descriptors (1-3 lines) — balance, NOT pretty */
|
|
18
|
+
.hero-desc, .card-desc, .feature-desc,
|
|
19
|
+
.caption, .subtitle, .blurb {
|
|
20
|
+
text-wrap: balance;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Body text (4+ lines) — pretty for orphan prevention */
|
|
24
|
+
p, li, dt, dd, .prose {
|
|
25
|
+
text-wrap: pretty;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Editable content — stable for no-reflow typing */
|
|
29
|
+
[contenteditable], .live-text {
|
|
30
|
+
text-wrap: stable;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Readable line lengths */
|
|
34
|
+
h1 { max-width: 50ch; }
|
|
35
|
+
h2 { max-width: 55ch; }
|
|
36
|
+
h3 { max-width: 60ch; }
|
|
37
|
+
p, li, article, .prose { max-width: 65ch; }
|
|
38
|
+
|
|
39
|
+
/* Korean: wrap by 어절 (word), never mid-word. Scope to ko ONLY —
|
|
40
|
+
global keep-all degrades Chinese/Japanese wrapping (verified 2026-07-07,
|
|
41
|
+
MDN + Naver SmartStudio). */
|
|
42
|
+
[lang|="ko"] {
|
|
43
|
+
word-break: keep-all;
|
|
44
|
+
overflow-wrap: break-word; /* escape hatch for long 어절 in narrow boxes */
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* URLs and unbreakable strings */
|
|
48
|
+
a[href], code {
|
|
49
|
+
overflow-wrap: break-word;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Never-wrap elements */
|
|
53
|
+
.cta-label, .badge, .nav-link, .tag {
|
|
54
|
+
white-space: nowrap;
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Fallback for older browsers:**
|
|
59
|
+
```css
|
|
60
|
+
@supports not (text-wrap: balance) {
|
|
61
|
+
h1, h2, h3 { max-width: min(90vw, 45ch); }
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## `text-wrap: balance`
|
|
68
|
+
|
|
69
|
+
Distributes characters evenly across lines. Prevents long first line + stubby orphan last line.
|
|
70
|
+
|
|
71
|
+
**When to use:** Headings, captions, blockquotes, hero titles, card titles.
|
|
72
|
+
**When NOT to use:** Body paragraphs (performance cost, limited to ~6 lines in Chromium, 10 in Firefox).
|
|
73
|
+
|
|
74
|
+
**Tailwind:** `text-balance`
|
|
75
|
+
|
|
76
|
+
Test with both centered AND left-aligned headings. Combine with `max-width` in `ch` units for best results.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## `text-wrap: pretty`
|
|
81
|
+
|
|
82
|
+
Higher-quality algorithm focused on the last few lines. Prevents typographic orphans.
|
|
83
|
+
|
|
84
|
+
**When to use:** Body copy, articles, prose, content-heavy sections.
|
|
85
|
+
**Tailwind:** `text-pretty`
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## `text-wrap: stable`
|
|
90
|
+
|
|
91
|
+
Prevents reflow of previous lines during editing. Content before the cursor stays fixed.
|
|
92
|
+
|
|
93
|
+
**When to use:** `contenteditable`, note-taking fields, real-time collaborative editing, live chat input.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Short Descriptors — `balance`, not `pretty`
|
|
98
|
+
|
|
99
|
+
Text that is 1-3 lines long (hero subtitle, card description, caption, feature blurb) is NOT a body paragraph. `text-wrap: pretty` has no meaningful effect on 1-3 lines — it targets the tail of long paragraphs.
|
|
100
|
+
|
|
101
|
+
**Use `text-wrap: balance` for all short descriptors.**
|
|
102
|
+
|
|
103
|
+
```css
|
|
104
|
+
/* Short descriptors — balance for orphan-free wrapping */
|
|
105
|
+
.hero-desc, .card-desc, .feature-desc,
|
|
106
|
+
.caption, .subtitle, .blurb {
|
|
107
|
+
text-wrap: balance;
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Add these to the Global Production Template alongside headings.
|
|
112
|
+
|
|
113
|
+
### `-webkit-line-clamp` disables `text-wrap`
|
|
114
|
+
|
|
115
|
+
When `-webkit-line-clamp` is active, the browser uses a legacy layout mode. `text-wrap: balance` and `pretty` have **zero effect**. If you need truncation with ellipsis, rely on `max-width` to control where breaks land — `text-wrap` cannot help.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## `max-width` in `ch` Units
|
|
120
|
+
|
|
121
|
+
| Element | Recommended `max-width` | Rationale |
|
|
122
|
+
|---------|------------------------|-----------|
|
|
123
|
+
| Hero title | 40–45ch | Short, punchy, fits viewport |
|
|
124
|
+
| **Hero subtitle/desc** | **35–40ch** | **Short descriptor — prevent orphans in 1-2 lines** |
|
|
125
|
+
| Section title | 50–55ch | Room for longer phrases |
|
|
126
|
+
| Card title | 30–35ch | Compact containers |
|
|
127
|
+
| **Card description** | **30–35ch** | **Short descriptor — tight containers amplify orphans** |
|
|
128
|
+
| **Caption / label** | **25–30ch** | **Short descriptor** |
|
|
129
|
+
| Body paragraph | 60–65ch | Optimal readability (45–75ch range) |
|
|
130
|
+
| Article/prose | 65ch | Standard reading measure |
|
|
131
|
+
|
|
132
|
+
Combine with fluid typography:
|
|
133
|
+
```css
|
|
134
|
+
h1 {
|
|
135
|
+
font-size: clamp(2rem, 5vw + 1rem, 4rem);
|
|
136
|
+
max-width: min(90vw, 45ch);
|
|
137
|
+
text-wrap: balance;
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Rag Control
|
|
144
|
+
|
|
145
|
+
| Problem | Solution |
|
|
146
|
+
|---------|----------|
|
|
147
|
+
| Uneven rag (jagged right edge) | `text-wrap: pretty` on paragraphs |
|
|
148
|
+
| Orphaned last word | `text-wrap: pretty` or `text-wrap: balance` for short blocks |
|
|
149
|
+
| Very long lines | `max-width: 65ch` |
|
|
150
|
+
| Rivers in justified text | Avoid `text-align: justify` or add `hyphens: auto` + `lang` attribute |
|
|
151
|
+
| Heading stubs | `text-wrap: balance` + `max-width` in `ch` |
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Korean Short Text — `pretty` Does Not Work
|
|
156
|
+
|
|
157
|
+
`text-wrap: pretty` does NOT prevent orphans in Korean text under 3 lines. The algorithm targets the tail of long paragraphs; on short Korean text with `word-break: keep-all`, orphans like "합니다.", "화.", "입니다." pass through unchanged.
|
|
158
|
+
|
|
159
|
+
**Mandatory rule for Korean short text (< 40 characters):** Always use `text-wrap: balance`. If `balance` still produces an orphan, adjust `max-width` by ±2-3ch.
|
|
160
|
+
|
|
161
|
+
```css
|
|
162
|
+
/* Korean short descriptors — explicit balance override */
|
|
163
|
+
[lang="ko"] .hero-desc,
|
|
164
|
+
[lang="ko"] .card-desc,
|
|
165
|
+
[lang="ko"] .subtitle {
|
|
166
|
+
text-wrap: balance;
|
|
167
|
+
word-break: keep-all;
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
This is the single most common Korean typography failure in AI-generated pages.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## `word-break` vs `overflow-wrap`
|
|
176
|
+
|
|
177
|
+
| Scenario | Use |
|
|
178
|
+
|----------|-----|
|
|
179
|
+
| Long URLs in narrow containers | `overflow-wrap: break-word` |
|
|
180
|
+
| Maximum flexibility with accurate sizing | `overflow-wrap: anywhere` |
|
|
181
|
+
| Aggressive character-level breaking | `word-break: break-all` |
|
|
182
|
+
| Korean 어절 (word) wrapping — `:lang(ko)` scope ONLY, degrades zh/ja globally | `word-break: keep-all` |
|
|
183
|
+
| User-generated content | `overflow-wrap: anywhere` |
|
|
184
|
+
|
|
185
|
+
**`word-break: break-word` is DEPRECATED** — use `overflow-wrap: break-word` instead.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Responsive Heading Breaks
|
|
190
|
+
|
|
191
|
+
Do NOT use manual `<br>` tags for line breaks. They break on different viewports.
|
|
192
|
+
|
|
193
|
+
```css
|
|
194
|
+
/* If you must use <br> for a specific breakpoint: */
|
|
195
|
+
h1 br { display: none; }
|
|
196
|
+
@media (min-width: 768px) { h1 br { display: block; } }
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Preferred approach:** Let `text-wrap: balance` + `max-width` handle it naturally.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## `widows` and `orphans` (Print Only)
|
|
204
|
+
|
|
205
|
+
These CSS properties only work in multi-column layouts and paged media. NOT for standard web layouts.
|
|
206
|
+
|
|
207
|
+
For web, use `text-wrap: pretty` instead.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Production Pattern
|
|
212
|
+
|
|
213
|
+
Common across Vercel, Linear, Stripe: `text-wrap: balance` on headings, `text-wrap: pretty` on body, `font-size: clamp()` for fluid scaling, tighter line-height (1.1–1.2) for headings.
|
|
214
|
+
|
|
215
|
+
For Korean-specific typography considerations, see also `korea-2026.md`.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Natural Phrase Breaks at Any Width (FE-WRAP-NATURAL-01, DEFAULT)
|
|
220
|
+
|
|
221
|
+
Text MUST break at natural phrase boundaries — between thought units, never
|
|
222
|
+
mid-word/mid-phrase — and it must hold at ANY container width, not only
|
|
223
|
+
canonical breakpoints. Containers resize continuously (drag-resize,
|
|
224
|
+
split-screen, foldables, mobile URL-bar dvh changes, container-query
|
|
225
|
+
crossings); a heading that breaks well at 390/768/1024/1440 can still orphan
|
|
226
|
+
at 700px. Control break OPPORTUNITIES (`keep-all`, `max-width` in `ch`,
|
|
227
|
+
`text-wrap: balance`) instead of width-specific hacks (`<br>`, media-query
|
|
228
|
+
text swaps).
|
|
229
|
+
|
|
230
|
+
Korean facts (verified 2026-07-07, Tier-2: MDN, CSSWG Text 4, KLREQ, Chrome
|
|
231
|
+
DevRel, Naver SmartStudio):
|
|
232
|
+
|
|
233
|
+
- With `keep-all`, break opportunities are 어절 (space) boundaries;
|
|
234
|
+
`text-wrap` only CHOOSES among existing opportunities — it cannot create
|
|
235
|
+
Korean phrase boundaries.
|
|
236
|
+
- `word-break: auto-phrase` ships for JAPANESE only (Chrome 119+); do NOT
|
|
237
|
+
rely on it for Korean. Treat it as ja-scoped progressive enhancement.
|
|
238
|
+
- KLREQ line rules are real for Korean (not just Japanese kinsoku): closing
|
|
239
|
+
punctuation must not start a line; opening brackets must not end one;
|
|
240
|
+
number/symbol runs should not split.
|
|
241
|
+
- Advanced editorial tactic: `Intl.Segmenter("ko", {granularity: "word"})`
|
|
242
|
+
(Baseline 2024) + render-time `<wbr>` insertion at approved phrase
|
|
243
|
+
boundaries under `keep-all`. Insert at RENDER time only — ZWSP/joiners
|
|
244
|
+
stored in canonical content break search/copy/diff.
|
|
245
|
+
- Overflow risk: `keep-all` + long 어절 + narrow chips/buttons/table cells =
|
|
246
|
+
clipping; always pair with `overflow-wrap: break-word` (or `anywhere` for
|
|
247
|
+
user-generated/URL content — note it also shrinks min-content size).
|
|
248
|
+
|
|
249
|
+
### Dynamic-Viewport Verification (checklist)
|
|
250
|
+
|
|
251
|
+
- Sweep CONTAINER widths, not device presets: 160-900px in 8-16px steps for
|
|
252
|
+
headings, cards, buttons, chips, sidebars, resizable panes, table cells.
|
|
253
|
+
- Test dvh/svh URL-bar changes, split-screen halves (640-1024px band, see
|
|
254
|
+
`responsive-viewport.md`), foldable-narrow panes, container-query
|
|
255
|
+
crossings, and 200% zoom / OS text scaling separately.
|
|
256
|
+
- Assert no overflow: `scrollWidth <= clientWidth` on text containers, plus
|
|
257
|
+
screenshots for clipping/overlap at 2-3 arbitrary mid-widths.
|
|
258
|
+
- Korean fixtures: long single 어절, no-space strings, mixed
|
|
259
|
+
Korean/Latin/number copy, URLs, punctuation-heavy lines; flag any
|
|
260
|
+
mid-syllable split where `keep-all` is expected.
|
|
261
|
+
- Fluid type uses `clamp()` with rem/em bounds — never bare `vw`/`cqi` font
|
|
262
|
+
sizes (zoom/user-font-size must keep working).
|
|
263
|
+
|
|
264
|
+
Judgment layer (when a break is "natural", review criteria):
|
|
265
|
+
`dev-uiux-design/references/typography-line-breaks.md § Dynamic Rewrap Judgment`.
|
|
266
|
+
|
|
267
|
+
## Sources
|
|
268
|
+
|
|
269
|
+
- [MDN: text-wrap](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/text-wrap)
|
|
270
|
+
- [Chrome: CSS text-wrap balance](https://developer.chrome.com/docs/css-ui/css-text-wrap-balance)
|