diff --git a/design-html/SKILL.md b/design-html/SKILL.md index dba83d9f8..0ad7d67b6 100644 --- a/design-html/SKILL.md +++ b/design-html/SKILL.md @@ -402,6 +402,20 @@ approximations. Computed layout via Pretext. Text reflows on resize, heights adj to content, cards size themselves, chat bubbles shrinkwrap, editorial spreads flow around obstacles. +--- + +## Section index — Read each section when its situation applies + +This skill is a decision-tree skeleton. The steps below point to on-demand +sections. Read a section in full before doing its step; do not work from memory. + +| When | Read this section | +|------|-------------------| +| analyzing the design or making any layout/visual decision (Step 1 onward) — the UX-principles doctrine governs every design choice | `sections/doctrine.md` | +| writing the finalized HTML in Step 3 — the Pretext wiring patterns and API cheatsheet are the required reference for all text-layout code | `sections/pretext-patterns.md` | + +--- + ## DESIGN SETUP (run this check BEFORE any design mockup command) ```bash @@ -445,90 +459,8 @@ MUST be saved to `~/.gstack/projects/$SLUG/designs/`, NEVER to `.context/`, `docs/designs/`, `/tmp/`, or any project-local directory. Design artifacts are USER data, not project files. They persist across branches, conversations, and workspaces. -## UX Principles: How Users Actually Behave - -These principles govern how real humans interact with interfaces. They are observed -behavior, not preferences. Apply them before, during, and after every design decision. - -### The Three Laws of Usability - -1. **Don't make me think.** Every page should be self-evident. If a user stops - to think "What do I click?" or "What does this mean?", the design has failed. - Self-evident > self-explanatory > requires explanation. - -2. **Clicks don't matter, thinking does.** Three mindless, unambiguous clicks - beat one click that requires thought. Each step should feel like an obvious - choice (animal, vegetable, or mineral), not a puzzle. - -3. **Omit, then omit again.** Get rid of half the words on each page, then get - rid of half of what's left. Happy talk (self-congratulatory text) must die. - Instructions must die. If they need reading, the design has failed. - -### How Users Actually Behave - -- **Users scan, they don't read.** Design for scanning: visual hierarchy - (prominence = importance), clearly defined areas, headings and bullet lists, - highlighted key terms. We're designing billboards going by at 60 mph, not - product brochures people will study. -- **Users satisfice.** They pick the first reasonable option, not the best. - Make the right choice the most visible choice. -- **Users muddle through.** They don't figure out how things work. They wing - it. If they accomplish their goal by accident, they won't seek the "right" way. - Once they find something that works, no matter how badly, they stick to it. -- **Users don't read instructions.** They dive in. Guidance must be brief, - timely, and unavoidable, or it won't be seen. - -### Billboard Design for Interfaces - -- **Use conventions.** Logo top-left, nav top/left, search = magnifying glass. - Don't innovate on navigation to be clever. Innovate when you KNOW you have a - better idea, otherwise use conventions. Even across languages and cultures, - web conventions let people identify the logo, nav, search, and main content. -- **Visual hierarchy is everything.** Related things are visually grouped. Nested - things are visually contained. More important = more prominent. If everything - shouts, nothing is heard. Start with the assumption everything is visual noise, - guilty until proven innocent. -- **Make clickable things obviously clickable.** No relying on hover states for - discoverability, especially on mobile where hover doesn't exist. Shape, location, - and formatting (color, underlining) must signal clickability without interaction. -- **Eliminate noise.** Three sources: too many things shouting for attention - (shouting), things not organized logically (disorganization), and too much stuff - (clutter). Fix noise by removal, not addition. -- **Clarity trumps consistency.** If making something significantly clearer - requires making it slightly inconsistent, choose clarity every time. - -### Navigation as Wayfinding - -Users on the web have no sense of scale, direction, or location. Navigation -must always answer: What site is this? What page am I on? What are the major -sections? What are my options at this level? Where am I? How can I search? - -Persistent navigation on every page. Breadcrumbs for deep hierarchies. -Current section visually indicated. The "trunk test": cover everything except -the navigation. You should still know what site this is, what page you're on, -and what the major sections are. If not, the navigation has failed. - -### The Goodwill Reservoir - -Users start with a reservoir of goodwill. Every friction point depletes it. - -**Deplete faster:** Hiding info users want (pricing, contact, shipping). Punishing -users for not doing things your way (formatting requirements on phone numbers). -Asking for unnecessary information. Putting sizzle in their way (splash screens, -forced tours, interstitials). Unprofessional or sloppy appearance. - -**Replenish:** Know what users want to do and make it obvious. Tell them what they -want to know upfront. Save them steps wherever possible. Make it easy to recover -from errors. When in doubt, apologize. - -### Mobile: Same Rules, Higher Stakes - -All the above applies on mobile, just more so. Real estate is scarce, but never -sacrifice usability for space savings. Affordances must be VISIBLE: no cursor -means no hover-to-discover. Touch targets must be big enough (44px minimum). -Flat design can strip away useful visual information that signals interactivity. -Prioritize ruthlessly: things needed in a hurry go close at hand, everything -else a few taps away with an obvious path to get there. +> **STOP.** Before analyzing the design or making any layout/visual decision (Step 1 onward) — the UX-principles doctrine governs every design choice, Read `~/.claude/skills/gstack/design-html/sections/doctrine.md` and execute it +> in full. Do not work from memory — that section is the source of truth for this step. ## SETUP (run this check BEFORE any browse command) @@ -741,6 +673,9 @@ If no framework detected: default to vanilla HTML, no question needed. ## Step 3: Generate Pretext-Native HTML +> **STOP.** Before writing the finalized HTML in Step 3 — the Pretext wiring patterns and API cheatsheet are the required reference for all text-layout code, Read `~/.claude/skills/gstack/design-html/sections/pretext-patterns.md` and execute it +> in full. Do not work from memory — that section is the source of truth for this step. + ### Pretext Source Embedding For **vanilla HTML output**, check for the vendored Pretext bundle: @@ -802,160 +737,6 @@ For framework output, save to: - Generic testimonial sections - Cookie-cutter hero sections with left-text right-image -### Pretext Wiring Patterns - -Use these patterns based on the tier selected in Step 2. These are the correct -Pretext API usage patterns. Follow them exactly. - -**Pattern 1: Basic height computation (Simple layout, Card/grid)** -```js -import { prepare, layout } from './pretext-inline.js' -// Or if inlined: const { prepare, layout } = window.Pretext - -// 1. PREPARE — one-time, after fonts load -await document.fonts.ready -const elements = document.querySelectorAll('[data-pretext]') -const prepared = new Map() - -for (const el of elements) { - const text = el.textContent - const font = getComputedStyle(el).font - prepared.set(el, prepare(text, font)) -} - -// 2. LAYOUT — cheap, call on every resize -function relayout() { - for (const [el, handle] of prepared) { - const { height } = layout(handle, el.clientWidth, parseFloat(getComputedStyle(el).lineHeight)) - el.style.height = `${height}px` - } -} - -// 3. RESIZE-AWARE -new ResizeObserver(() => relayout()).observe(document.body) -relayout() - -// 4. CONTENT-EDITABLE — re-prepare when text changes -for (const el of elements) { - if (el.contentEditable === 'true') { - new MutationObserver(() => { - const font = getComputedStyle(el).font - prepared.set(el, prepare(el.textContent, font)) - relayout() - }).observe(el, { characterData: true, subtree: true, childList: true }) - } -} -``` - -**Pattern 2: Shrinkwrap / tight-fit containers (Chat bubbles)** -```js -import { prepareWithSegments, walkLineRanges } from './pretext-inline.js' - -// Find the tightest width that produces the same line count -function shrinkwrap(text, font, maxWidth, lineHeight) { - const segs = prepareWithSegments(text, font) - let bestWidth = maxWidth - walkLineRanges(segs, maxWidth, (lineCount, startIdx, endIdx) => { - // walkLineRanges calls back with progressively narrower widths - // The first call gives us the line count at maxWidth - // We want the narrowest width that still produces this line count - }) - // Binary search for tightest width with same line count - const { lineCount: targetLines } = layout(prepare(text, font), maxWidth, lineHeight) - let lo = 0, hi = maxWidth - while (hi - lo > 1) { - const mid = (lo + hi) / 2 - const { lineCount } = layout(prepare(text, font), mid, lineHeight) - if (lineCount === targetLines) hi = mid - else lo = mid - } - return hi -} -``` - -**Pattern 3: Text around obstacles (Editorial layout)** -```js -import { prepareWithSegments, layoutNextLine } from './pretext-inline.js' - -function layoutAroundObstacles(text, font, containerWidth, lineHeight, obstacles) { - const segs = prepareWithSegments(text, font) - let state = null - let y = 0 - const lines = [] - - while (true) { - // Calculate available width at current y position, accounting for obstacles - let availWidth = containerWidth - for (const obs of obstacles) { - if (y >= obs.top && y < obs.top + obs.height) { - availWidth -= obs.width - } - } - - const result = layoutNextLine(segs, state, availWidth, lineHeight) - if (!result) break - - lines.push({ text: result.text, width: result.width, x: 0, y }) - state = result.state - y += lineHeight - } - - return { lines, totalHeight: y } -} -``` - -**Pattern 4: Full line-by-line rendering (Complex editorial)** -```js -import { prepareWithSegments, layoutWithLines } from './pretext-inline.js' - -const segs = prepareWithSegments(text, font) -const { lines, height } = layoutWithLines(segs, containerWidth, lineHeight) - -// lines = [{ text, width, x, y }, ...] -// Use for Canvas/SVG rendering or custom DOM positioning -for (const line of lines) { - const span = document.createElement('span') - span.textContent = line.text - span.style.position = 'absolute' - span.style.left = `${line.x}px` - span.style.top = `${line.y}px` - container.appendChild(span) -} -``` - -### Pretext API Reference - -``` -PRETEXT API CHEATSHEET: - -prepare(text, font) → handle - One-time text measurement. Call after document.fonts.ready. - Font: CSS shorthand like '16px Inter' or 'bold 24px Georgia'. - -layout(prepared, maxWidth, lineHeight) → { height, lineCount } - Fast layout computation. Call on every resize. Sub-millisecond. - -prepareWithSegments(text, font) → handle - Like prepare() but enables line-level APIs below. - -layoutWithLines(segs, maxWidth, lineHeight) → { lines: [{text, width, x, y}...], height } - Full line-by-line breakdown. For Canvas/SVG rendering. - -walkLineRanges(segs, maxWidth, onLine) → void - Calls onLine(lineCount, startIdx, endIdx) for each possible layout. - Find minimum width for N lines. For tight-fit containers. - -layoutNextLine(segs, state, maxWidth, lineHeight) → { text, width, state } | null - Iterator. Different maxWidth per line = text around obstacles. - Pass null as initial state. Returns null when text is exhausted. - -clearCache() → void - Clears internal measurement caches. Use when cycling many fonts. - -setLocale(locale?) → void - Retargets word segmenter for future prepare() calls. -``` - --- ## Step 3.5: Live Reload Server diff --git a/design-html/SKILL.md.tmpl b/design-html/SKILL.md.tmpl index 3cdec9a14..816381737 100644 --- a/design-html/SKILL.md.tmpl +++ b/design-html/SKILL.md.tmpl @@ -39,9 +39,15 @@ approximations. Computed layout via Pretext. Text reflows on resize, heights adj to content, cards size themselves, chat bubbles shrinkwrap, editorial spreads flow around obstacles. +--- + +{{SECTION_INDEX:design-html}} + +--- + {{DESIGN_SETUP}} -{{UX_PRINCIPLES}} +{{SECTION:doctrine}} {{BROWSE_SETUP}} @@ -220,6 +226,8 @@ If no framework detected: default to vanilla HTML, no question needed. ## Step 3: Generate Pretext-Native HTML +{{SECTION:pretext-patterns}} + ### Pretext Source Embedding For **vanilla HTML output**, check for the vendored Pretext bundle: @@ -281,160 +289,6 @@ For framework output, save to: - Generic testimonial sections - Cookie-cutter hero sections with left-text right-image -### Pretext Wiring Patterns - -Use these patterns based on the tier selected in Step 2. These are the correct -Pretext API usage patterns. Follow them exactly. - -**Pattern 1: Basic height computation (Simple layout, Card/grid)** -```js -import { prepare, layout } from './pretext-inline.js' -// Or if inlined: const { prepare, layout } = window.Pretext - -// 1. PREPARE — one-time, after fonts load -await document.fonts.ready -const elements = document.querySelectorAll('[data-pretext]') -const prepared = new Map() - -for (const el of elements) { - const text = el.textContent - const font = getComputedStyle(el).font - prepared.set(el, prepare(text, font)) -} - -// 2. LAYOUT — cheap, call on every resize -function relayout() { - for (const [el, handle] of prepared) { - const { height } = layout(handle, el.clientWidth, parseFloat(getComputedStyle(el).lineHeight)) - el.style.height = `${height}px` - } -} - -// 3. RESIZE-AWARE -new ResizeObserver(() => relayout()).observe(document.body) -relayout() - -// 4. CONTENT-EDITABLE — re-prepare when text changes -for (const el of elements) { - if (el.contentEditable === 'true') { - new MutationObserver(() => { - const font = getComputedStyle(el).font - prepared.set(el, prepare(el.textContent, font)) - relayout() - }).observe(el, { characterData: true, subtree: true, childList: true }) - } -} -``` - -**Pattern 2: Shrinkwrap / tight-fit containers (Chat bubbles)** -```js -import { prepareWithSegments, walkLineRanges } from './pretext-inline.js' - -// Find the tightest width that produces the same line count -function shrinkwrap(text, font, maxWidth, lineHeight) { - const segs = prepareWithSegments(text, font) - let bestWidth = maxWidth - walkLineRanges(segs, maxWidth, (lineCount, startIdx, endIdx) => { - // walkLineRanges calls back with progressively narrower widths - // The first call gives us the line count at maxWidth - // We want the narrowest width that still produces this line count - }) - // Binary search for tightest width with same line count - const { lineCount: targetLines } = layout(prepare(text, font), maxWidth, lineHeight) - let lo = 0, hi = maxWidth - while (hi - lo > 1) { - const mid = (lo + hi) / 2 - const { lineCount } = layout(prepare(text, font), mid, lineHeight) - if (lineCount === targetLines) hi = mid - else lo = mid - } - return hi -} -``` - -**Pattern 3: Text around obstacles (Editorial layout)** -```js -import { prepareWithSegments, layoutNextLine } from './pretext-inline.js' - -function layoutAroundObstacles(text, font, containerWidth, lineHeight, obstacles) { - const segs = prepareWithSegments(text, font) - let state = null - let y = 0 - const lines = [] - - while (true) { - // Calculate available width at current y position, accounting for obstacles - let availWidth = containerWidth - for (const obs of obstacles) { - if (y >= obs.top && y < obs.top + obs.height) { - availWidth -= obs.width - } - } - - const result = layoutNextLine(segs, state, availWidth, lineHeight) - if (!result) break - - lines.push({ text: result.text, width: result.width, x: 0, y }) - state = result.state - y += lineHeight - } - - return { lines, totalHeight: y } -} -``` - -**Pattern 4: Full line-by-line rendering (Complex editorial)** -```js -import { prepareWithSegments, layoutWithLines } from './pretext-inline.js' - -const segs = prepareWithSegments(text, font) -const { lines, height } = layoutWithLines(segs, containerWidth, lineHeight) - -// lines = [{ text, width, x, y }, ...] -// Use for Canvas/SVG rendering or custom DOM positioning -for (const line of lines) { - const span = document.createElement('span') - span.textContent = line.text - span.style.position = 'absolute' - span.style.left = `${line.x}px` - span.style.top = `${line.y}px` - container.appendChild(span) -} -``` - -### Pretext API Reference - -``` -PRETEXT API CHEATSHEET: - -prepare(text, font) → handle - One-time text measurement. Call after document.fonts.ready. - Font: CSS shorthand like '16px Inter' or 'bold 24px Georgia'. - -layout(prepared, maxWidth, lineHeight) → { height, lineCount } - Fast layout computation. Call on every resize. Sub-millisecond. - -prepareWithSegments(text, font) → handle - Like prepare() but enables line-level APIs below. - -layoutWithLines(segs, maxWidth, lineHeight) → { lines: [{text, width, x, y}...], height } - Full line-by-line breakdown. For Canvas/SVG rendering. - -walkLineRanges(segs, maxWidth, onLine) → void - Calls onLine(lineCount, startIdx, endIdx) for each possible layout. - Find minimum width for N lines. For tight-fit containers. - -layoutNextLine(segs, state, maxWidth, lineHeight) → { text, width, state } | null - Iterator. Different maxWidth per line = text around obstacles. - Pass null as initial state. Returns null when text is exhausted. - -clearCache() → void - Clears internal measurement caches. Use when cycling many fonts. - -setLocale(locale?) → void - Retargets word segmenter for future prepare() calls. -``` - --- ## Step 3.5: Live Reload Server diff --git a/design-html/sections/doctrine.md b/design-html/sections/doctrine.md new file mode 100644 index 000000000..7ec4bdca9 --- /dev/null +++ b/design-html/sections/doctrine.md @@ -0,0 +1,86 @@ + + +## UX Principles: How Users Actually Behave + +These principles govern how real humans interact with interfaces. They are observed +behavior, not preferences. Apply them before, during, and after every design decision. + +### The Three Laws of Usability + +1. **Don't make me think.** Every page should be self-evident. If a user stops + to think "What do I click?" or "What does this mean?", the design has failed. + Self-evident > self-explanatory > requires explanation. + +2. **Clicks don't matter, thinking does.** Three mindless, unambiguous clicks + beat one click that requires thought. Each step should feel like an obvious + choice (animal, vegetable, or mineral), not a puzzle. + +3. **Omit, then omit again.** Get rid of half the words on each page, then get + rid of half of what's left. Happy talk (self-congratulatory text) must die. + Instructions must die. If they need reading, the design has failed. + +### How Users Actually Behave + +- **Users scan, they don't read.** Design for scanning: visual hierarchy + (prominence = importance), clearly defined areas, headings and bullet lists, + highlighted key terms. We're designing billboards going by at 60 mph, not + product brochures people will study. +- **Users satisfice.** They pick the first reasonable option, not the best. + Make the right choice the most visible choice. +- **Users muddle through.** They don't figure out how things work. They wing + it. If they accomplish their goal by accident, they won't seek the "right" way. + Once they find something that works, no matter how badly, they stick to it. +- **Users don't read instructions.** They dive in. Guidance must be brief, + timely, and unavoidable, or it won't be seen. + +### Billboard Design for Interfaces + +- **Use conventions.** Logo top-left, nav top/left, search = magnifying glass. + Don't innovate on navigation to be clever. Innovate when you KNOW you have a + better idea, otherwise use conventions. Even across languages and cultures, + web conventions let people identify the logo, nav, search, and main content. +- **Visual hierarchy is everything.** Related things are visually grouped. Nested + things are visually contained. More important = more prominent. If everything + shouts, nothing is heard. Start with the assumption everything is visual noise, + guilty until proven innocent. +- **Make clickable things obviously clickable.** No relying on hover states for + discoverability, especially on mobile where hover doesn't exist. Shape, location, + and formatting (color, underlining) must signal clickability without interaction. +- **Eliminate noise.** Three sources: too many things shouting for attention + (shouting), things not organized logically (disorganization), and too much stuff + (clutter). Fix noise by removal, not addition. +- **Clarity trumps consistency.** If making something significantly clearer + requires making it slightly inconsistent, choose clarity every time. + +### Navigation as Wayfinding + +Users on the web have no sense of scale, direction, or location. Navigation +must always answer: What site is this? What page am I on? What are the major +sections? What are my options at this level? Where am I? How can I search? + +Persistent navigation on every page. Breadcrumbs for deep hierarchies. +Current section visually indicated. The "trunk test": cover everything except +the navigation. You should still know what site this is, what page you're on, +and what the major sections are. If not, the navigation has failed. + +### The Goodwill Reservoir + +Users start with a reservoir of goodwill. Every friction point depletes it. + +**Deplete faster:** Hiding info users want (pricing, contact, shipping). Punishing +users for not doing things your way (formatting requirements on phone numbers). +Asking for unnecessary information. Putting sizzle in their way (splash screens, +forced tours, interstitials). Unprofessional or sloppy appearance. + +**Replenish:** Know what users want to do and make it obvious. Tell them what they +want to know upfront. Save them steps wherever possible. Make it easy to recover +from errors. When in doubt, apologize. + +### Mobile: Same Rules, Higher Stakes + +All the above applies on mobile, just more so. Real estate is scarce, but never +sacrifice usability for space savings. Affordances must be VISIBLE: no cursor +means no hover-to-discover. Touch targets must be big enough (44px minimum). +Flat design can strip away useful visual information that signals interactivity. +Prioritize ruthlessly: things needed in a hurry go close at hand, everything +else a few taps away with an obvious path to get there. diff --git a/design-html/sections/doctrine.md.tmpl b/design-html/sections/doctrine.md.tmpl new file mode 100644 index 000000000..7de81c4db --- /dev/null +++ b/design-html/sections/doctrine.md.tmpl @@ -0,0 +1 @@ +{{UX_PRINCIPLES}} diff --git a/design-html/sections/manifest.json b/design-html/sections/manifest.json new file mode 100644 index 000000000..caa6940de --- /dev/null +++ b/design-html/sections/manifest.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://gstack.dev/schemas/section-manifest.json", + "skill": "design-html", + "version": 1, + "note": "PASSIVE registry (v2 plan T9 / CM2). Fields are IDs, file paths, human titles, and human-readable trigger text ONLY. The skeleton's decision-tree prose is the ONLY place that decides WHEN to read a section; required-reads live in the E2E fixtures. No machine predicate here — see docs/designs/v2_PLAN.md:663.", + "sections": [ + { + "id": "doctrine", + "file": "doctrine.md", + "title": "UX principles doctrine (how users actually behave)", + "trigger": "analyzing the design or making any layout/visual decision (Step 1 onward) — the UX-principles doctrine governs every design choice" + }, + { + "id": "pretext-patterns", + "file": "pretext-patterns.md", + "title": "Pretext wiring patterns + API reference", + "trigger": "writing the finalized HTML in Step 3 — the Pretext wiring patterns and API cheatsheet are the required reference for all text-layout code" + } + ] +} diff --git a/design-html/sections/pretext-patterns.md b/design-html/sections/pretext-patterns.md new file mode 100644 index 000000000..8823c853a --- /dev/null +++ b/design-html/sections/pretext-patterns.md @@ -0,0 +1,155 @@ + + +### Pretext Wiring Patterns + +Use these patterns based on the tier selected in Step 2. These are the correct +Pretext API usage patterns. Follow them exactly. + +**Pattern 1: Basic height computation (Simple layout, Card/grid)** +```js +import { prepare, layout } from './pretext-inline.js' +// Or if inlined: const { prepare, layout } = window.Pretext + +// 1. PREPARE — one-time, after fonts load +await document.fonts.ready +const elements = document.querySelectorAll('[data-pretext]') +const prepared = new Map() + +for (const el of elements) { + const text = el.textContent + const font = getComputedStyle(el).font + prepared.set(el, prepare(text, font)) +} + +// 2. LAYOUT — cheap, call on every resize +function relayout() { + for (const [el, handle] of prepared) { + const { height } = layout(handle, el.clientWidth, parseFloat(getComputedStyle(el).lineHeight)) + el.style.height = `${height}px` + } +} + +// 3. RESIZE-AWARE +new ResizeObserver(() => relayout()).observe(document.body) +relayout() + +// 4. CONTENT-EDITABLE — re-prepare when text changes +for (const el of elements) { + if (el.contentEditable === 'true') { + new MutationObserver(() => { + const font = getComputedStyle(el).font + prepared.set(el, prepare(el.textContent, font)) + relayout() + }).observe(el, { characterData: true, subtree: true, childList: true }) + } +} +``` + +**Pattern 2: Shrinkwrap / tight-fit containers (Chat bubbles)** +```js +import { prepareWithSegments, walkLineRanges } from './pretext-inline.js' + +// Find the tightest width that produces the same line count +function shrinkwrap(text, font, maxWidth, lineHeight) { + const segs = prepareWithSegments(text, font) + let bestWidth = maxWidth + walkLineRanges(segs, maxWidth, (lineCount, startIdx, endIdx) => { + // walkLineRanges calls back with progressively narrower widths + // The first call gives us the line count at maxWidth + // We want the narrowest width that still produces this line count + }) + // Binary search for tightest width with same line count + const { lineCount: targetLines } = layout(prepare(text, font), maxWidth, lineHeight) + let lo = 0, hi = maxWidth + while (hi - lo > 1) { + const mid = (lo + hi) / 2 + const { lineCount } = layout(prepare(text, font), mid, lineHeight) + if (lineCount === targetLines) hi = mid + else lo = mid + } + return hi +} +``` + +**Pattern 3: Text around obstacles (Editorial layout)** +```js +import { prepareWithSegments, layoutNextLine } from './pretext-inline.js' + +function layoutAroundObstacles(text, font, containerWidth, lineHeight, obstacles) { + const segs = prepareWithSegments(text, font) + let state = null + let y = 0 + const lines = [] + + while (true) { + // Calculate available width at current y position, accounting for obstacles + let availWidth = containerWidth + for (const obs of obstacles) { + if (y >= obs.top && y < obs.top + obs.height) { + availWidth -= obs.width + } + } + + const result = layoutNextLine(segs, state, availWidth, lineHeight) + if (!result) break + + lines.push({ text: result.text, width: result.width, x: 0, y }) + state = result.state + y += lineHeight + } + + return { lines, totalHeight: y } +} +``` + +**Pattern 4: Full line-by-line rendering (Complex editorial)** +```js +import { prepareWithSegments, layoutWithLines } from './pretext-inline.js' + +const segs = prepareWithSegments(text, font) +const { lines, height } = layoutWithLines(segs, containerWidth, lineHeight) + +// lines = [{ text, width, x, y }, ...] +// Use for Canvas/SVG rendering or custom DOM positioning +for (const line of lines) { + const span = document.createElement('span') + span.textContent = line.text + span.style.position = 'absolute' + span.style.left = `${line.x}px` + span.style.top = `${line.y}px` + container.appendChild(span) +} +``` + +### Pretext API Reference + +``` +PRETEXT API CHEATSHEET: + +prepare(text, font) → handle + One-time text measurement. Call after document.fonts.ready. + Font: CSS shorthand like '16px Inter' or 'bold 24px Georgia'. + +layout(prepared, maxWidth, lineHeight) → { height, lineCount } + Fast layout computation. Call on every resize. Sub-millisecond. + +prepareWithSegments(text, font) → handle + Like prepare() but enables line-level APIs below. + +layoutWithLines(segs, maxWidth, lineHeight) → { lines: [{text, width, x, y}...], height } + Full line-by-line breakdown. For Canvas/SVG rendering. + +walkLineRanges(segs, maxWidth, onLine) → void + Calls onLine(lineCount, startIdx, endIdx) for each possible layout. + Find minimum width for N lines. For tight-fit containers. + +layoutNextLine(segs, state, maxWidth, lineHeight) → { text, width, state } | null + Iterator. Different maxWidth per line = text around obstacles. + Pass null as initial state. Returns null when text is exhausted. + +clearCache() → void + Clears internal measurement caches. Use when cycling many fonts. + +setLocale(locale?) → void + Retargets word segmenter for future prepare() calls. +``` diff --git a/design-html/sections/pretext-patterns.md.tmpl b/design-html/sections/pretext-patterns.md.tmpl new file mode 100644 index 000000000..15d33171c --- /dev/null +++ b/design-html/sections/pretext-patterns.md.tmpl @@ -0,0 +1,153 @@ +### Pretext Wiring Patterns + +Use these patterns based on the tier selected in Step 2. These are the correct +Pretext API usage patterns. Follow them exactly. + +**Pattern 1: Basic height computation (Simple layout, Card/grid)** +```js +import { prepare, layout } from './pretext-inline.js' +// Or if inlined: const { prepare, layout } = window.Pretext + +// 1. PREPARE — one-time, after fonts load +await document.fonts.ready +const elements = document.querySelectorAll('[data-pretext]') +const prepared = new Map() + +for (const el of elements) { + const text = el.textContent + const font = getComputedStyle(el).font + prepared.set(el, prepare(text, font)) +} + +// 2. LAYOUT — cheap, call on every resize +function relayout() { + for (const [el, handle] of prepared) { + const { height } = layout(handle, el.clientWidth, parseFloat(getComputedStyle(el).lineHeight)) + el.style.height = `${height}px` + } +} + +// 3. RESIZE-AWARE +new ResizeObserver(() => relayout()).observe(document.body) +relayout() + +// 4. CONTENT-EDITABLE — re-prepare when text changes +for (const el of elements) { + if (el.contentEditable === 'true') { + new MutationObserver(() => { + const font = getComputedStyle(el).font + prepared.set(el, prepare(el.textContent, font)) + relayout() + }).observe(el, { characterData: true, subtree: true, childList: true }) + } +} +``` + +**Pattern 2: Shrinkwrap / tight-fit containers (Chat bubbles)** +```js +import { prepareWithSegments, walkLineRanges } from './pretext-inline.js' + +// Find the tightest width that produces the same line count +function shrinkwrap(text, font, maxWidth, lineHeight) { + const segs = prepareWithSegments(text, font) + let bestWidth = maxWidth + walkLineRanges(segs, maxWidth, (lineCount, startIdx, endIdx) => { + // walkLineRanges calls back with progressively narrower widths + // The first call gives us the line count at maxWidth + // We want the narrowest width that still produces this line count + }) + // Binary search for tightest width with same line count + const { lineCount: targetLines } = layout(prepare(text, font), maxWidth, lineHeight) + let lo = 0, hi = maxWidth + while (hi - lo > 1) { + const mid = (lo + hi) / 2 + const { lineCount } = layout(prepare(text, font), mid, lineHeight) + if (lineCount === targetLines) hi = mid + else lo = mid + } + return hi +} +``` + +**Pattern 3: Text around obstacles (Editorial layout)** +```js +import { prepareWithSegments, layoutNextLine } from './pretext-inline.js' + +function layoutAroundObstacles(text, font, containerWidth, lineHeight, obstacles) { + const segs = prepareWithSegments(text, font) + let state = null + let y = 0 + const lines = [] + + while (true) { + // Calculate available width at current y position, accounting for obstacles + let availWidth = containerWidth + for (const obs of obstacles) { + if (y >= obs.top && y < obs.top + obs.height) { + availWidth -= obs.width + } + } + + const result = layoutNextLine(segs, state, availWidth, lineHeight) + if (!result) break + + lines.push({ text: result.text, width: result.width, x: 0, y }) + state = result.state + y += lineHeight + } + + return { lines, totalHeight: y } +} +``` + +**Pattern 4: Full line-by-line rendering (Complex editorial)** +```js +import { prepareWithSegments, layoutWithLines } from './pretext-inline.js' + +const segs = prepareWithSegments(text, font) +const { lines, height } = layoutWithLines(segs, containerWidth, lineHeight) + +// lines = [{ text, width, x, y }, ...] +// Use for Canvas/SVG rendering or custom DOM positioning +for (const line of lines) { + const span = document.createElement('span') + span.textContent = line.text + span.style.position = 'absolute' + span.style.left = `${line.x}px` + span.style.top = `${line.y}px` + container.appendChild(span) +} +``` + +### Pretext API Reference + +``` +PRETEXT API CHEATSHEET: + +prepare(text, font) → handle + One-time text measurement. Call after document.fonts.ready. + Font: CSS shorthand like '16px Inter' or 'bold 24px Georgia'. + +layout(prepared, maxWidth, lineHeight) → { height, lineCount } + Fast layout computation. Call on every resize. Sub-millisecond. + +prepareWithSegments(text, font) → handle + Like prepare() but enables line-level APIs below. + +layoutWithLines(segs, maxWidth, lineHeight) → { lines: [{text, width, x, y}...], height } + Full line-by-line breakdown. For Canvas/SVG rendering. + +walkLineRanges(segs, maxWidth, onLine) → void + Calls onLine(lineCount, startIdx, endIdx) for each possible layout. + Find minimum width for N lines. For tight-fit containers. + +layoutNextLine(segs, state, maxWidth, lineHeight) → { text, width, state } | null + Iterator. Different maxWidth per line = text around obstacles. + Pass null as initial state. Returns null when text is exhausted. + +clearCache() → void + Clears internal measurement caches. Use when cycling many fonts. + +setLocale(locale?) → void + Retargets word segmenter for future prepare() calls. +``` diff --git a/design-shotgun/SKILL.md b/design-shotgun/SKILL.md index 32804ed9c..63f13694f 100644 --- a/design-shotgun/SKILL.md +++ b/design-shotgun/SKILL.md @@ -415,6 +415,19 @@ You are a design brainstorming partner. Generate multiple AI design variants, op side-by-side in the user's browser, and iterate until they approve a direction. This is visual brainstorming, not a review process. +--- + +## Section index — Read each section when its situation applies + +This skill is a decision-tree skeleton. The steps below point to on-demand +sections. Read a section in full before doing its step; do not work from memory. + +| When | Read this section | +|------|-------------------| +| writing variant concepts or design briefs (Step 3 onward) — the UX-principles doctrine governs every design direction | `sections/doctrine.md` | + +--- + ## DESIGN SETUP (run this check BEFORE any design mockup command) ```bash @@ -458,90 +471,8 @@ MUST be saved to `~/.gstack/projects/$SLUG/designs/`, NEVER to `.context/`, `docs/designs/`, `/tmp/`, or any project-local directory. Design artifacts are USER data, not project files. They persist across branches, conversations, and workspaces. -## UX Principles: How Users Actually Behave - -These principles govern how real humans interact with interfaces. They are observed -behavior, not preferences. Apply them before, during, and after every design decision. - -### The Three Laws of Usability - -1. **Don't make me think.** Every page should be self-evident. If a user stops - to think "What do I click?" or "What does this mean?", the design has failed. - Self-evident > self-explanatory > requires explanation. - -2. **Clicks don't matter, thinking does.** Three mindless, unambiguous clicks - beat one click that requires thought. Each step should feel like an obvious - choice (animal, vegetable, or mineral), not a puzzle. - -3. **Omit, then omit again.** Get rid of half the words on each page, then get - rid of half of what's left. Happy talk (self-congratulatory text) must die. - Instructions must die. If they need reading, the design has failed. - -### How Users Actually Behave - -- **Users scan, they don't read.** Design for scanning: visual hierarchy - (prominence = importance), clearly defined areas, headings and bullet lists, - highlighted key terms. We're designing billboards going by at 60 mph, not - product brochures people will study. -- **Users satisfice.** They pick the first reasonable option, not the best. - Make the right choice the most visible choice. -- **Users muddle through.** They don't figure out how things work. They wing - it. If they accomplish their goal by accident, they won't seek the "right" way. - Once they find something that works, no matter how badly, they stick to it. -- **Users don't read instructions.** They dive in. Guidance must be brief, - timely, and unavoidable, or it won't be seen. - -### Billboard Design for Interfaces - -- **Use conventions.** Logo top-left, nav top/left, search = magnifying glass. - Don't innovate on navigation to be clever. Innovate when you KNOW you have a - better idea, otherwise use conventions. Even across languages and cultures, - web conventions let people identify the logo, nav, search, and main content. -- **Visual hierarchy is everything.** Related things are visually grouped. Nested - things are visually contained. More important = more prominent. If everything - shouts, nothing is heard. Start with the assumption everything is visual noise, - guilty until proven innocent. -- **Make clickable things obviously clickable.** No relying on hover states for - discoverability, especially on mobile where hover doesn't exist. Shape, location, - and formatting (color, underlining) must signal clickability without interaction. -- **Eliminate noise.** Three sources: too many things shouting for attention - (shouting), things not organized logically (disorganization), and too much stuff - (clutter). Fix noise by removal, not addition. -- **Clarity trumps consistency.** If making something significantly clearer - requires making it slightly inconsistent, choose clarity every time. - -### Navigation as Wayfinding - -Users on the web have no sense of scale, direction, or location. Navigation -must always answer: What site is this? What page am I on? What are the major -sections? What are my options at this level? Where am I? How can I search? - -Persistent navigation on every page. Breadcrumbs for deep hierarchies. -Current section visually indicated. The "trunk test": cover everything except -the navigation. You should still know what site this is, what page you're on, -and what the major sections are. If not, the navigation has failed. - -### The Goodwill Reservoir - -Users start with a reservoir of goodwill. Every friction point depletes it. - -**Deplete faster:** Hiding info users want (pricing, contact, shipping). Punishing -users for not doing things your way (formatting requirements on phone numbers). -Asking for unnecessary information. Putting sizzle in their way (splash screens, -forced tours, interstitials). Unprofessional or sloppy appearance. - -**Replenish:** Know what users want to do and make it obvious. Tell them what they -want to know upfront. Save them steps wherever possible. Make it easy to recover -from errors. When in doubt, apologize. - -### Mobile: Same Rules, Higher Stakes - -All the above applies on mobile, just more so. Real estate is scarce, but never -sacrifice usability for space savings. Affordances must be VISIBLE: no cursor -means no hover-to-discover. Touch targets must be big enough (44px minimum). -Flat design can strip away useful visual information that signals interactivity. -Prioritize ruthlessly: things needed in a hurry go close at hand, everything -else a few taps away with an obvious path to get there. +> **STOP.** Before writing variant concepts or design briefs (Step 3 onward) — the UX-principles doctrine governs every design direction, Read `~/.claude/skills/gstack/design-shotgun/sections/doctrine.md` and execute it +> in full. Do not work from memory — that section is the source of truth for this step. ## Step 0: Session Detection diff --git a/design-shotgun/SKILL.md.tmpl b/design-shotgun/SKILL.md.tmpl index 230dbc292..7b9c038d8 100644 --- a/design-shotgun/SKILL.md.tmpl +++ b/design-shotgun/SKILL.md.tmpl @@ -50,9 +50,15 @@ You are a design brainstorming partner. Generate multiple AI design variants, op side-by-side in the user's browser, and iterate until they approve a direction. This is visual brainstorming, not a review process. +--- + +{{SECTION_INDEX:design-shotgun}} + +--- + {{DESIGN_SETUP}} -{{UX_PRINCIPLES}} +{{SECTION:doctrine}} ## Step 0: Session Detection diff --git a/design-shotgun/sections/doctrine.md b/design-shotgun/sections/doctrine.md new file mode 100644 index 000000000..7ec4bdca9 --- /dev/null +++ b/design-shotgun/sections/doctrine.md @@ -0,0 +1,86 @@ + + +## UX Principles: How Users Actually Behave + +These principles govern how real humans interact with interfaces. They are observed +behavior, not preferences. Apply them before, during, and after every design decision. + +### The Three Laws of Usability + +1. **Don't make me think.** Every page should be self-evident. If a user stops + to think "What do I click?" or "What does this mean?", the design has failed. + Self-evident > self-explanatory > requires explanation. + +2. **Clicks don't matter, thinking does.** Three mindless, unambiguous clicks + beat one click that requires thought. Each step should feel like an obvious + choice (animal, vegetable, or mineral), not a puzzle. + +3. **Omit, then omit again.** Get rid of half the words on each page, then get + rid of half of what's left. Happy talk (self-congratulatory text) must die. + Instructions must die. If they need reading, the design has failed. + +### How Users Actually Behave + +- **Users scan, they don't read.** Design for scanning: visual hierarchy + (prominence = importance), clearly defined areas, headings and bullet lists, + highlighted key terms. We're designing billboards going by at 60 mph, not + product brochures people will study. +- **Users satisfice.** They pick the first reasonable option, not the best. + Make the right choice the most visible choice. +- **Users muddle through.** They don't figure out how things work. They wing + it. If they accomplish their goal by accident, they won't seek the "right" way. + Once they find something that works, no matter how badly, they stick to it. +- **Users don't read instructions.** They dive in. Guidance must be brief, + timely, and unavoidable, or it won't be seen. + +### Billboard Design for Interfaces + +- **Use conventions.** Logo top-left, nav top/left, search = magnifying glass. + Don't innovate on navigation to be clever. Innovate when you KNOW you have a + better idea, otherwise use conventions. Even across languages and cultures, + web conventions let people identify the logo, nav, search, and main content. +- **Visual hierarchy is everything.** Related things are visually grouped. Nested + things are visually contained. More important = more prominent. If everything + shouts, nothing is heard. Start with the assumption everything is visual noise, + guilty until proven innocent. +- **Make clickable things obviously clickable.** No relying on hover states for + discoverability, especially on mobile where hover doesn't exist. Shape, location, + and formatting (color, underlining) must signal clickability without interaction. +- **Eliminate noise.** Three sources: too many things shouting for attention + (shouting), things not organized logically (disorganization), and too much stuff + (clutter). Fix noise by removal, not addition. +- **Clarity trumps consistency.** If making something significantly clearer + requires making it slightly inconsistent, choose clarity every time. + +### Navigation as Wayfinding + +Users on the web have no sense of scale, direction, or location. Navigation +must always answer: What site is this? What page am I on? What are the major +sections? What are my options at this level? Where am I? How can I search? + +Persistent navigation on every page. Breadcrumbs for deep hierarchies. +Current section visually indicated. The "trunk test": cover everything except +the navigation. You should still know what site this is, what page you're on, +and what the major sections are. If not, the navigation has failed. + +### The Goodwill Reservoir + +Users start with a reservoir of goodwill. Every friction point depletes it. + +**Deplete faster:** Hiding info users want (pricing, contact, shipping). Punishing +users for not doing things your way (formatting requirements on phone numbers). +Asking for unnecessary information. Putting sizzle in their way (splash screens, +forced tours, interstitials). Unprofessional or sloppy appearance. + +**Replenish:** Know what users want to do and make it obvious. Tell them what they +want to know upfront. Save them steps wherever possible. Make it easy to recover +from errors. When in doubt, apologize. + +### Mobile: Same Rules, Higher Stakes + +All the above applies on mobile, just more so. Real estate is scarce, but never +sacrifice usability for space savings. Affordances must be VISIBLE: no cursor +means no hover-to-discover. Touch targets must be big enough (44px minimum). +Flat design can strip away useful visual information that signals interactivity. +Prioritize ruthlessly: things needed in a hurry go close at hand, everything +else a few taps away with an obvious path to get there. diff --git a/design-shotgun/sections/doctrine.md.tmpl b/design-shotgun/sections/doctrine.md.tmpl new file mode 100644 index 000000000..7de81c4db --- /dev/null +++ b/design-shotgun/sections/doctrine.md.tmpl @@ -0,0 +1 @@ +{{UX_PRINCIPLES}} diff --git a/design-shotgun/sections/manifest.json b/design-shotgun/sections/manifest.json new file mode 100644 index 000000000..198220262 --- /dev/null +++ b/design-shotgun/sections/manifest.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://gstack.dev/schemas/section-manifest.json", + "skill": "design-shotgun", + "version": 1, + "note": "PASSIVE registry (v2 plan T9 / CM2). Fields are IDs, file paths, human titles, and human-readable trigger text ONLY. The skeleton's decision-tree prose is the ONLY place that decides WHEN to read a section; required-reads live in the E2E fixtures. No machine predicate here — see docs/designs/v2_PLAN.md:663.", + "sections": [ + { + "id": "doctrine", + "file": "doctrine.md", + "title": "UX principles doctrine (how users actually behave)", + "trigger": "writing variant concepts or design briefs (Step 3 onward) — the UX-principles doctrine governs every design direction" + } + ] +}