@3xhaust/oh-my-design 0.16.1

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.
Files changed (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.ko.md +282 -0
  3. package/README.md +282 -0
  4. package/adapters/build.ts +184 -0
  5. package/adapters/claude.ts +111 -0
  6. package/adapters/codex.ts +68 -0
  7. package/adapters/tokens.ts +21 -0
  8. package/adapters/tool-map.json +7 -0
  9. package/bin/omd-install.ts +76 -0
  10. package/bin/omd.ts +1774 -0
  11. package/core/asset-sourcing/index.ts +187 -0
  12. package/core/coach/index.ts +98 -0
  13. package/core/composition/app-shell-workbench.md +168 -0
  14. package/core/composition/asymmetric-diagonal-grid.md +178 -0
  15. package/core/composition/bento-grid.md +223 -0
  16. package/core/composition/editorial-index-labels.md +173 -0
  17. package/core/composition/form-wizard-stepper.md +132 -0
  18. package/core/composition/master-detail-flow.md +129 -0
  19. package/core/composition/section-inversion.md +182 -0
  20. package/core/composition/sidebar-margin-annotation.md +184 -0
  21. package/core/composition/split-screen-hero.md +224 -0
  22. package/core/composition/sticky-sidebar-scroll.md +219 -0
  23. package/core/composition/typographic-hero.md +100 -0
  24. package/core/composition-contract/index.ts +233 -0
  25. package/core/composition-contract/visual-richness.ts +101 -0
  26. package/core/config/index.ts +27 -0
  27. package/core/copy/index.ts +296 -0
  28. package/core/craft/finish-pass.md +356 -0
  29. package/core/craft/index.ts +33 -0
  30. package/core/design/index.ts +581 -0
  31. package/core/design/interaction-states.ts +189 -0
  32. package/core/eval-harness/index.ts +225 -0
  33. package/core/evidence/final.ts +444 -0
  34. package/core/evidence/task.ts +459 -0
  35. package/core/figma/client.ts +312 -0
  36. package/core/figma/diff.ts +261 -0
  37. package/core/figma/responsive.ts +310 -0
  38. package/core/figma/system.ts +466 -0
  39. package/core/figma/types.ts +185 -0
  40. package/core/frame/check-ux.ts +180 -0
  41. package/core/frame/index.ts +39 -0
  42. package/core/frame/write.ts +168 -0
  43. package/core/graphics/css-illustration-primitives.md +189 -0
  44. package/core/graphics/duotone-image-presets.md +173 -0
  45. package/core/graphics/gradient-mesh.md +178 -0
  46. package/core/graphics/noise-grain-texture.md +150 -0
  47. package/core/graphics/placeholder-policy.md +185 -0
  48. package/core/graphics/svg-geometric-patterns.md +145 -0
  49. package/core/history/index.ts +39 -0
  50. package/core/install/detect.ts +32 -0
  51. package/core/install/install.ts +321 -0
  52. package/core/install/patch-claude.ts +109 -0
  53. package/core/install/patch-codex.ts +82 -0
  54. package/core/interaction/index.ts +101 -0
  55. package/core/interaction/recipes/signature-lighting.md +203 -0
  56. package/core/ir/dom.ts +352 -0
  57. package/core/ir/normalize.ts +177 -0
  58. package/core/motion/easing.md +151 -0
  59. package/core/motion/energy.ts +188 -0
  60. package/core/motion/recipes/image-hover-distortion.md +198 -0
  61. package/core/motion/recipes/magnetic-hover.md +229 -0
  62. package/core/motion/recipes/marquee.md +202 -0
  63. package/core/motion/recipes/number-counter.md +242 -0
  64. package/core/motion/recipes/page-loader.md +240 -0
  65. package/core/motion/recipes/parallax.md +233 -0
  66. package/core/motion/recipes/scroll-reveal.md +210 -0
  67. package/core/motion/recipes/section-color-inversion.md +217 -0
  68. package/core/motion/recipes/split-text-entrance.md +222 -0
  69. package/core/motion/recipes/stagger-orchestrator.md +227 -0
  70. package/core/motion/recipes/sticky-scene-transition.md +246 -0
  71. package/core/motion/recipes/view-transitions.md +239 -0
  72. package/core/probe/index.ts +186 -0
  73. package/core/probe/schema.json +46 -0
  74. package/core/protocol/composition-contract.md +192 -0
  75. package/core/protocol/copy-deck.md +96 -0
  76. package/core/protocol/human-design-loop.md +447 -0
  77. package/core/protocol/slop-review.md +71 -0
  78. package/core/ref/batch.ts +94 -0
  79. package/core/ref/blueprint.ts +199 -0
  80. package/core/ref/distance.ts +225 -0
  81. package/core/ref/invariants.ts +162 -0
  82. package/core/ref/signal.ts +37 -0
  83. package/core/ref/store.ts +122 -0
  84. package/core/render/index.ts +607 -0
  85. package/core/rules/attribution.ts +159 -0
  86. package/core/rules/builtin/contrast.yaml +8 -0
  87. package/core/rules/builtin/focus.yaml +8 -0
  88. package/core/rules/builtin/hit-area.yaml +8 -0
  89. package/core/rules/builtin/ko.yaml +6 -0
  90. package/core/rules/builtin/motion.yaml +80 -0
  91. package/core/rules/builtin/slop.yaml +365 -0
  92. package/core/rules/builtin/spacing.yaml +9 -0
  93. package/core/rules/builtin/system.yaml +42 -0
  94. package/core/rules/builtin/token.yaml +8 -0
  95. package/core/rules/builtin/ux.yaml +125 -0
  96. package/core/rules/engine.ts +88 -0
  97. package/core/rules/leakage.ts +72 -0
  98. package/core/rules/motion-spec.ts +204 -0
  99. package/core/site/index.ts +100 -0
  100. package/core/slop/index.ts +427 -0
  101. package/core/slop/text-slop.ts +220 -0
  102. package/core/source-seal/index.ts +174 -0
  103. package/core/stack/index.ts +53 -0
  104. package/core/target/index.ts +183 -0
  105. package/core/theory/color.md +285 -0
  106. package/core/theory/components.md +272 -0
  107. package/core/theory/craft.md +270 -0
  108. package/core/theory/expressive.md +213 -0
  109. package/core/theory/imagegen.md +105 -0
  110. package/core/theory/layout.md +159 -0
  111. package/core/theory/motion.md +283 -0
  112. package/core/theory/typography.md +134 -0
  113. package/core/theory/ux.md +580 -0
  114. package/core/theory/voice.md +595 -0
  115. package/core/types.ts +684 -0
  116. package/package.json +40 -0
  117. package/src/agents/composer.agent.yaml +174 -0
  118. package/src/agents/eye.agent.yaml +208 -0
  119. package/src/agents/framer.agent.yaml +52 -0
  120. package/src/agents/glance.agent.yaml +19 -0
  121. package/src/agents/hand.agent.yaml +200 -0
  122. package/src/agents/scout.agent.yaml +71 -0
  123. package/src/agents/sketch.agent.yaml +64 -0
  124. package/src/agents/typesetter.agent.yaml +52 -0
  125. package/src/agents/writer.agent.yaml +63 -0
  126. package/src/skills/omd-coach/SKILL.md +40 -0
  127. package/src/skills/omd-critique/SKILL.md +77 -0
  128. package/src/skills/omd-figma/SKILL.md +256 -0
  129. package/src/skills/omd-humanize/SKILL.md +160 -0
  130. package/src/skills/omd-scout/SKILL.md +77 -0
  131. package/src/skills/omd-ultradesign/SKILL.md +521 -0
@@ -0,0 +1,223 @@
1
+ # Bento grid
2
+
3
+ > Candidate hypothesis only. Revalidate its condition, values, and responsive transition
4
+ > against the current composition contract; do not transfer this page recipe unchanged.
5
+
6
+ A bento grid divides a section into cells of intentionally different sizes, arranged as a
7
+ mosaic. The cell size hierarchy communicates content hierarchy: the largest cell holds
8
+ the most important content, and the eye reads that before anything else. The grid earns
9
+ its distinctiveness from non-uniformity — if all cells are the same size, it is a card
10
+ grid, not a bento grid, regardless of what it is called.
11
+
12
+ ## When it earns its place / When it does not
13
+
14
+ Condition: the section contains varied content types that benefit from non-uniform visual
15
+ weight — a primary feature with more depth, supporting features with less, and perhaps a
16
+ decorative or data element at a different proportion. The arrangement must be a statement
17
+ of hierarchy: the user can tell at a glance which cell matters most because it is largest.
18
+ A homepage overview where one product capability is the anchor and three or four support
19
+ it, a feature highlight section where capabilities are genuinely asymmetric — these earn
20
+ the grid.
21
+
22
+ `core/theory/layout.md` on card hierarchy: "Every card must have an internal hierarchy:
23
+ one primary piece of information that the eye finds first, and secondary information that
24
+ serves it." The bento grid applies this principle at the section level: one cell is the
25
+ primary; all others serve it.
26
+
27
+ **Anti-slop clause.** A bento grid that fills all cells with equal-weight cards is
28
+ SLOP-TRIPLE-CARD rendered at scale. The grid earns its place only when the cell-size
29
+ hierarchy is non-recoverable: the arrangement itself tells the reader what matters most.
30
+ If you can swap cells without loss of meaning, the bento grid is the wrong layout.
31
+ The `SLOP-TRIPLE-CARD` slop rule fires on three or more identically-treated cards
32
+ regardless of whether they are arranged in a standard grid or a bento mosaic — the
33
+ violation is in the equal treatment, not in the grid structure.
34
+
35
+ Condition against: content where all items are genuinely equal in importance — a pricing
36
+ table, a team member grid, a portfolio gallery. Forced hierarchy on equal content
37
+ misinforms the user. Also: narrow viewports where the bento mosaic must collapse to a
38
+ single column, losing the hierarchical arrangement that justified it. If the layout reads
39
+ as a simple stacked list on mobile, the bento grid is carrying the hierarchy only on
40
+ desktop — which means the hierarchy is not in the content, it is in the layout trick.
41
+
42
+ ## Parameters
43
+
44
+ ```css
45
+ :root {
46
+ /* Grid gap between cells. Consistent gap size is important — uneven gaps
47
+ read as different object weights. Use a single token. */
48
+ --bento-gap: clamp(8px, 1.5vw, 16px);
49
+
50
+ /* Base grid unit. All cell spans are multiples of this.
51
+ A 4-column base allows: 1 (quarter), 2 (half), 3 (three-quarter), 4 (full).
52
+ A 6-column base allows more granular spans. */
53
+ --bento-columns: 4;
54
+
55
+ /* Minimum row height. Cells taller than this are defined by row-span.
56
+ This sets the rhythm of the grid. */
57
+ --bento-row-height: clamp(160px, 22vw, 280px);
58
+
59
+ /* Cell radius: consistent across all cells.
60
+ Bento grids on award-winning sites use a consistent radius to read as one object.
61
+ Inconsistent radii break the mosaic reading. */
62
+ --bento-radius: var(--radius-card, 12px);
63
+ }
64
+ ```
65
+
66
+ ## Implementation
67
+
68
+ ```html
69
+ <!-- The grid container defines the column structure.
70
+ Each cell declares its own span — no JavaScript layout library needed.
71
+ The hierarchy is declared in the HTML: the primary cell comes first
72
+ and spans the largest area. -->
73
+ <div class="bento-grid">
74
+ <!-- Primary cell: largest, most important content. Spans 2 columns, 2 rows. -->
75
+ <div class="bento-cell bento-cell--primary">
76
+ <h2>The primary capability.</h2>
77
+ <p>The claim with the most depth, given the most space.</p>
78
+ </div>
79
+
80
+ <!-- Supporting cells: smaller, each with a distinct capability. -->
81
+ <div class="bento-cell bento-cell--secondary">
82
+ <h3>Supporting capability one.</h3>
83
+ </div>
84
+
85
+ <div class="bento-cell bento-cell--secondary">
86
+ <h3>Supporting capability two.</h3>
87
+ </div>
88
+
89
+ <!-- Accent cell: a data point, a decorative element, or a CTA at smaller scale. -->
90
+ <div class="bento-cell bento-cell--accent">
91
+ <span class="bento-stat">48ms</span>
92
+ <span class="bento-stat-label">median response time</span>
93
+ </div>
94
+
95
+ <!-- Wide secondary: spans the full width below the primary cluster. -->
96
+ <div class="bento-cell bento-cell--wide">
97
+ <h3>The wider supporting context.</h3>
98
+ <p>Content that benefits from a wider canvas than the narrow supporting cells.</p>
99
+ </div>
100
+ </div>
101
+ ```
102
+
103
+ ```css
104
+ .bento-grid {
105
+ display: grid;
106
+ grid-template-columns: repeat(var(--bento-columns), 1fr);
107
+ grid-auto-rows: var(--bento-row-height);
108
+ gap: var(--bento-gap);
109
+ padding-inline: var(--space-page-margin, 5vw);
110
+ }
111
+
112
+ /* All cells share the base styling: the radius and background.
113
+ Differentiation comes from content hierarchy, not from different styling. */
114
+ .bento-cell {
115
+ border-radius: var(--bento-radius);
116
+ background-color: var(--surface-subtle, #f5f5f3);
117
+ padding: var(--space-6, 1.5rem);
118
+ overflow: hidden;
119
+ /* Cells do not have individual box-shadows — the gap performs the separation. */
120
+ }
121
+
122
+ /* Primary cell: the largest. The hierarchy is in the span, not in extra decoration. */
123
+ .bento-cell--primary {
124
+ grid-column: span 2;
125
+ grid-row: span 2;
126
+ /* The primary cell may have a distinct background only if it is the section's
127
+ anchor — not as generic differentiation. */
128
+ background-color: var(--surface-emphasis, #0f0f0f);
129
+ color: var(--on-surface-emphasis, #f5f5f3);
130
+ }
131
+
132
+ .bento-cell--secondary {
133
+ grid-column: span 1;
134
+ grid-row: span 1;
135
+ }
136
+
137
+ .bento-cell--accent {
138
+ grid-column: span 1;
139
+ grid-row: span 1;
140
+ display: flex;
141
+ flex-direction: column;
142
+ align-items: flex-start;
143
+ justify-content: flex-end;
144
+ }
145
+
146
+ .bento-cell--wide {
147
+ grid-column: span var(--bento-columns); /* full width */
148
+ grid-row: span 1;
149
+ }
150
+
151
+ .bento-stat {
152
+ font-size: clamp(2rem, 6vw, 4rem);
153
+ font-weight: var(--weight-display, 700);
154
+ letter-spacing: -0.04em;
155
+ line-height: 1;
156
+ }
157
+
158
+ .bento-stat-label {
159
+ font-size: var(--text-xs, 0.75rem);
160
+ color: var(--text-secondary);
161
+ margin-top: var(--space-1, 0.25rem);
162
+ }
163
+ ```
164
+
165
+ ## Responsive behavior
166
+
167
+ **375px (mobile):** The multi-column mosaic collapses to a single column. The hierarchy
168
+ must still read — the primary cell appears first in DOM order, retaining its importance
169
+ through position even when size parity is lost.
170
+
171
+ ```css
172
+ @media (max-width: 600px) {
173
+ .bento-grid {
174
+ grid-template-columns: 1fr;
175
+ grid-auto-rows: auto; /* height driven by content on mobile */
176
+ }
177
+
178
+ .bento-cell--primary,
179
+ .bento-cell--secondary,
180
+ .bento-cell--accent,
181
+ .bento-cell--wide {
182
+ grid-column: 1 / -1;
183
+ grid-row: auto;
184
+ min-height: var(--bento-row-height); /* preserve breathing room */
185
+ }
186
+ }
187
+ ```
188
+
189
+ **768px (tablet):** A two-column variant works well at tablet width. Reduce
190
+ `--bento-columns` to 2 and verify that the primary cell's two-column span still reads
191
+ as dominant — at 768px with two columns, a `span 2` cell occupies the full row, which
192
+ may remove the mosaic effect. Consider a `span 1` tall primary at tablet.
193
+
194
+ ```css
195
+ @media (min-width: 601px) and (max-width: 1024px) {
196
+ .bento-grid {
197
+ --bento-columns: 2;
198
+ }
199
+
200
+ .bento-cell--primary {
201
+ grid-column: span 2;
202
+ grid-row: span 1; /* adjust: span 2 at 2-col is full-width */
203
+ }
204
+ }
205
+ ```
206
+
207
+ **1280px (desktop):** Full 4-column mosaic. The `--bento-row-height` ceiling of 280px
208
+ keeps cells within a readable range; taller cells are appropriate only when they hold
209
+ content that genuinely requires height (an image, a chart, a demonstration). An empty
210
+ tall cell is wasted space, not visual breathing room.
211
+
212
+ ## Do not combine with
213
+
214
+ **asymmetric-diagonal-grid.md** — both techniques restructure the spatial grid at the
215
+ same level. Applying both in a single section produces competing spatial logics.
216
+
217
+ **section-inversion.md** inside individual bento cells — switching background colour per
218
+ cell introduces a fourth surface level (page → section → cell → inverted cell) that
219
+ obscures the hierarchy the bento grid was designed to communicate.
220
+
221
+ **A bento grid where every cell has the same span** — this collapses back into
222
+ `SLOP-TRIPLE-CARD` territory. The anti-slop clause above applies: if the spans are
223
+ uniform, use a standard grid or card layout and state the reason.
@@ -0,0 +1,173 @@
1
+ # Editorial index labels (01, 02, 03)
2
+
3
+ > Candidate hypothesis only. Revalidate its condition, values, and responsive transition
4
+ > against the current composition contract; do not transfer this page recipe unchanged.
5
+
6
+ A large ordinal number — set at display scale, typically 10–15% opacity — anchors each
7
+ section to its position in a sequence. The number reads as background field before it
8
+ reads as a numeral; it reinforces order and creates depth without competing with the
9
+ section's primary content.
10
+
11
+ `expressive.md` on this technique: "The large number (set at display scale, often 10–15%
12
+ opacity as a background element) reinforces the ordered reading without becoming the
13
+ dominant visual."
14
+
15
+ ## When it earns its place / When it does not
16
+
17
+ Condition: the content has a genuine sequence — steps in a process, chapters in a
18
+ narrative, items in an ordered argument. The numbering must correspond to a sequence the
19
+ user will perceive as sequential; the label confirms what the user already suspects. A
20
+ case study with four distinct phases, a feature walkthrough with a natural order, a
21
+ methodology page with numbered stages — each earns the label because the sequence is real.
22
+
23
+ `core/theory/layout.md` — von Restorff and the one memorable thing: the large number
24
+ differentiates each section from the others through the isolation effect. Used across all
25
+ sections, each number is equally differentiated, which means none of them is — the effect
26
+ cancels itself. Applied to a genuine sequence, the number adds the memory hook von Restorff
27
+ describes; applied arbitrarily, it is visual noise.
28
+
29
+ Condition against: fake sequences — content enumerated to look ordered when the items have
30
+ no natural sequence. A features section with three independent capabilities numbered 01–03
31
+ implies the user should attend to them in order; if they should not, the numbering lied.
32
+ `expressive.md`: "Numerical labels that carry no sequential meaning are decoration, and
33
+ decoration at display scale is noise." Also: navigation lists, card grids, or any context
34
+ where the user is scanning rather than reading in order.
35
+
36
+ This specific tell — large low-opacity ordinals on unordered sections — is a documented
37
+ signature of generated pages: giant faint numerals that imply sequence where none exists. The counter-condition is this recipe: ordinals on
38
+ sections whose content has a genuine sequence (methodology phases, walkthrough steps,
39
+ ordered chapters). When the label earns its place the ordinal confirms what the user
40
+ already suspects; when it does not, it is decoration at display scale and should be removed.
41
+ The SLOP-TRIPLE-CARD rule fires on the underlying grid structure that often accompanies
42
+ unearned numbering; adding ordinals to a triple-card grid resolves neither finding.
43
+
44
+ ## Parameters
45
+
46
+ ```css
47
+ :root {
48
+ /* Font size for the index number.
49
+ Large enough to read as a background field, not a foreground label.
50
+ Typical range: clamp(5rem, 15vw, 12rem). */
51
+ --index-font-size: clamp(5rem, 15vw, 12rem);
52
+
53
+ /* Opacity: low enough to read as depth, not text.
54
+ 10–15% is the operative range; above 20% the number competes with the heading. */
55
+ --index-opacity: 0.1;
56
+
57
+ /* Position within the section grid.
58
+ Typically anchored to the left or top-left, offset behind the heading. */
59
+ --index-offset-top: -0.15em; /* nudge behind the heading's cap-height */
60
+ --index-offset-left: -0.05em; /* slight bleed into the margin */
61
+
62
+ /* Font weight.
63
+ Heavy weight at low opacity reads as texture; light weight at low opacity disappears.
64
+ 700 or 800 is the operative range. */
65
+ --index-font-weight: 700;
66
+ }
67
+ ```
68
+
69
+ ## Implementation
70
+
71
+ ```html
72
+ <!-- Each section carries its own index number.
73
+ aria-hidden: the number is decorative; screen readers skip it.
74
+ The section heading carries the actual label for assistive technology. -->
75
+ <section class="indexed-section" data-index="01">
76
+ <span class="indexed-section__number" aria-hidden="true">01</span>
77
+ <div class="indexed-section__content">
78
+ <h2 class="indexed-section__heading">Discovery</h2>
79
+ <p>The phase where assumptions are tested against the real problem.</p>
80
+ </div>
81
+ </section>
82
+
83
+ <section class="indexed-section" data-index="02">
84
+ <span class="indexed-section__number" aria-hidden="true">02</span>
85
+ <div class="indexed-section__content">
86
+ <h2 class="indexed-section__heading">Definition</h2>
87
+ <p>Where the real problem is named, with evidence.</p>
88
+ </div>
89
+ </section>
90
+ ```
91
+
92
+ ```css
93
+ .indexed-section {
94
+ position: relative;
95
+ padding-block: var(--space-section, 6rem);
96
+ padding-inline: var(--space-page-margin, 5vw);
97
+ /* Overflow hidden: the large number must not create horizontal scroll */
98
+ overflow: hidden;
99
+ }
100
+
101
+ .indexed-section__number {
102
+ position: absolute;
103
+ top: var(--index-offset-top);
104
+ left: var(--index-offset-left);
105
+ font-size: var(--index-font-size);
106
+ font-weight: var(--index-font-weight);
107
+ font-family: var(--font-display);
108
+ line-height: 1;
109
+ letter-spacing: -0.04em;
110
+ opacity: var(--index-opacity);
111
+ /* Prevent the number from intercepting pointer events on content above */
112
+ pointer-events: none;
113
+ user-select: none;
114
+ /* z-index: below the content, above the background */
115
+ z-index: 0;
116
+ }
117
+
118
+ .indexed-section__content {
119
+ position: relative;
120
+ z-index: 1; /* above the index number */
121
+ /* The content column stays within the readable measure — max 72ch for body,
122
+ narrower for the heading. */
123
+ max-width: 60ch;
124
+ }
125
+
126
+ .indexed-section__heading {
127
+ font-size: var(--text-2xl, 2rem);
128
+ font-weight: var(--weight-heading, 600);
129
+ margin-bottom: var(--space-4, 1rem);
130
+ }
131
+ ```
132
+
133
+ ## Responsive behavior
134
+
135
+ **375px (mobile):** The large number at `clamp(5rem, 15vw, 12rem)` computes to
136
+ `max(5rem, 56px) = 80px` at 375px — small enough to not dominate the viewport. The
137
+ absolute-positioned number may overlap the heading at this size; verify the heading remains
138
+ legible. If contrast is compromised, increase the `--index-offset-top` negative value to
139
+ push the number further behind or reduce `--index-opacity` to 0.06 at narrow viewports.
140
+
141
+ ```css
142
+ @media (max-width: 600px) {
143
+ .indexed-section {
144
+ --index-opacity: 0.07; /* reduce further at narrow viewports to preserve legibility */
145
+ padding-block: var(--space-section-mobile, 3rem);
146
+ }
147
+ }
148
+ ```
149
+
150
+ **768px (tablet):** The number at `15vw` computes to ~115px. The absolute positioning works
151
+ well; the heading and body copy are clearly in the foreground. No structural change needed;
152
+ adjust offset if the number's baseline aligns uncomfortably with the heading's cap height.
153
+
154
+ **1280px (desktop):** The ceiling `12rem` (192px at 16px root) governs. At this size the
155
+ number reads as a large background field. The content column at `max-width: 60ch` leaves
156
+ significant space on the right at full viewport — the index number fills that zone
157
+ naturally. The composition reads as editorial depth, not as an oversized ordinal.
158
+
159
+ ## Do not combine with
160
+
161
+ **sidebar-margin-annotation.md** in the same section — both create competing secondary
162
+ reading tracks in the peripheral field (left side for the index, right margin for
163
+ annotations). The eye cannot establish which secondary track is the intended one.
164
+
165
+ **section-inversion.md** per section — when every section inverts, the index number's
166
+ opacity calibration breaks across light and dark surfaces. Calibrate separately for each
167
+ surface or avoid the combination.
168
+
169
+ **editorial-index-labels with fake sequences** — this bears repeating because the temptation
170
+ is real: do not apply index labels to a grid of independent features to make the page look
171
+ more structured. `expressive.md` names this directly as decoration. The slop linter's
172
+ `SLOP-TRIPLE-CARD` rule fires on the underlying grid; adding index numbers to that grid
173
+ does not resolve the finding.
@@ -0,0 +1,132 @@
1
+ # Form wizard stepper
2
+
3
+ > Candidate hypothesis only. Revalidate its condition, values, and responsive transition
4
+ > against the current composition contract; do not transfer this page recipe unchanged.
5
+
6
+ A focused multi-step flow — onboarding, signup, checkout, complex creation — composed as
7
+ one narrow column with a visible progress model, one decision cluster per step, and an
8
+ exit from every state. The composition suppresses everything that is not the flow: no
9
+ global navigation pulling the user mid-task, no marketing panel repeating the pitch they
10
+ already accepted. `core/theory/ux.md` §Forms owns the step-count evidence (Baymard: a
11
+ visible progress indicator reduces perceived effort; only the steps the user's task
12
+ requires); `core/theory/layout.md` §Forms owns field-row grammar.
13
+
14
+ ## When it earns its place / When it does not
15
+
16
+ Condition: the task has 8+ fields or genuinely dependent decisions (later fields depend
17
+ on earlier answers), or the user's mental load per screen must stay low (first-run
18
+ onboarding). Each step is one decision cluster the user can name ("계정", "팀 초대",
19
+ "결제") — a step that exists because an engineer needed the data, not because the user
20
+ makes a decision there, is merged away.
21
+
22
+ Condition against: under ~8 independent fields — a single-page form shows the full cost
23
+ upfront and is cheaper (`theory/ux.md` §Forms). Also against: pull-based product
24
+ onboarding where the product is usable immediately — prefer contextual empty-state
25
+ guidance over a forced wizard (§First-run experience: forced tutorials retain worse).
26
+
27
+ **Anti-trap clause.** Every step has an escape: back to the previous step without data
28
+ loss, save-and-exit or an explicit cancel with consequence named. A wizard whose only
29
+ exits are "다음" and the browser's back button is a dead end factory. Entered values
30
+ survive navigation in both directions and validation failure (value preservation is the
31
+ UX acceptance contract, not polish).
32
+
33
+ ## Parameters
34
+
35
+ ```css
36
+ :root {
37
+ /* One-column measure. Forms read and complete fastest in a single column
38
+ (theory/layout.md §Forms); 44-52ch keeps labels+inputs scannable. */
39
+ --wizard-measure: 48rem;
40
+
41
+ /* Progress model: steps are few, named, and visible. */
42
+ --wizard-step-gap: 8px;
43
+ --wizard-accent: var(--accent, #1f6f5c);
44
+
45
+ /* Primary action anchors the step end; back is quiet, never competing. */
46
+ --wizard-cta-h: 48px;
47
+ }
48
+ ```
49
+
50
+ ## Implementation
51
+
52
+ ```html
53
+ <main class="wizard">
54
+ <!-- Progress: named steps, current marked, completed steps revisitable. -->
55
+ <nav class="wizard-progress" aria-label="진행 단계">
56
+ <ol>
57
+ <li><a href="#step-account" class="done">계정</a></li>
58
+ <li aria-current="step">팀 초대</li>
59
+ <li>결제</li>
60
+ </ol>
61
+ </nav>
62
+
63
+ <form class="wizard-step" aria-labelledby="step-title">
64
+ <h1 id="step-title">팀원을 초대해요</h1>
65
+ <p class="wizard-why">건너뛰어도 돼요 — 나중에 설정에서 초대할 수 있어요.</p>
66
+
67
+ <!-- One decision cluster: fields in one column, labels above,
68
+ errors adjacent to their field, values preserved on failure. -->
69
+
70
+ <div class="wizard-actions">
71
+ <button type="button" class="wizard-back">이전</button>
72
+ <button type="submit" class="wizard-next">다음: 결제</button>
73
+ <button type="button" class="wizard-skip">건너뛰기</button>
74
+ </div>
75
+ </form>
76
+ </main>
77
+ ```
78
+
79
+ ```css
80
+ .wizard {
81
+ max-width: var(--wizard-measure);
82
+ margin-inline: auto;
83
+ padding-inline: 16px;
84
+ }
85
+
86
+ .wizard-progress li[aria-current="step"] { color: var(--wizard-accent); font-weight: 600; }
87
+
88
+ .wizard-step { display: grid; row-gap: 16px; }
89
+
90
+ .wizard-actions {
91
+ display: flex;
92
+ align-items: center;
93
+ gap: 12px;
94
+ /* One primary per view: "다음" is filled; back/skip are quiet. */
95
+ }
96
+
97
+ .wizard-next { min-height: var(--wizard-cta-h); }
98
+ ```
99
+
100
+ The primary button predicts the next step by name ("다음: 결제" — a CTA predicts what
101
+ happens after activation, per the copy-deck protocol). The final step ends on a designed
102
+ confirmation that restates what was created and names the first real action in the
103
+ product (§Peak-end rule: the end is a deliberate moment, not a redirect).
104
+
105
+ ## Responsive behavior
106
+
107
+ **1280px (desktop):** the column centers at `--wizard-measure`; surrounding space stays
108
+ empty — a wizard earns air (density follows task frequency; this is a low-frequency,
109
+ high-attention surface).
110
+
111
+ **768px (tablet):** identical structure; the progress list may compress to
112
+ "2/3 — 팀 초대" numeric form when horizontal room runs out, but the current step stays
113
+ named.
114
+
115
+ **375px (mobile):** actions dock to the bottom within thumb reach; the keyboard must not
116
+ cover the focused field or the primary action (test with the on-screen keyboard open —
117
+ the bottom-CTA-under-keyboard failure is the classic mobile wizard defect). Back remains
118
+ visible without scrolling. At 320px nothing two-dimensional remains.
119
+
120
+ ## Do not combine with
121
+
122
+ **app-shell-workbench.md global navigation** — a focused flow suppresses global nav;
123
+ offering the whole product map mid-checkout invites abandonment and half-created state.
124
+ Show at most a logo-exit with a named consequence.
125
+
126
+ **split-screen-hero.md as a signup decoration panel** — the persuasion already happened;
127
+ a marketing half-panel steals half the viewport from the form on every step and doubles
128
+ the reflow work at 768px for nothing the task needs.
129
+
130
+ **Progress bars without step names** — a bare percentage tells the user neither where
131
+ they are nor what remains; the anti-pattern version of the visible-progress evidence this
132
+ recipe cites.
@@ -0,0 +1,129 @@
1
+ # Master-detail flow
2
+
3
+ > Candidate hypothesis only. Revalidate its condition, values, and responsive transition
4
+ > against the current composition contract; do not transfer this page recipe unchanged.
5
+
6
+ A list (master) and the selected object's workspace (detail) presented as one continuous
7
+ loop: select → inspect → act → return. The composition decision is where the detail
8
+ lives — beside the list, over it, or as a separate screen — and that decision is made
9
+ from loop frequency and context needs, not from viewport fashion. `core/theory/ux.md`
10
+ §Flows: every step that exists only because the layout forced a round-trip is an obstacle.
11
+ `core/theory/layout.md` §Density governs the master's row height and scan columns.
12
+
13
+ ## When it earns its place / When it does not
14
+
15
+ Condition: the primary task is processing or inspecting items from a collection —
16
+ tickets, orders, documents, contacts, messages — and the user alternates between
17
+ scanning the collection and working on one item many times per session.
18
+
19
+ Choose the detail presentation by two questions:
20
+
21
+ 1. **Does acting on the item require seeing the list?** (triage, comparison, queue
22
+ awareness) → side-by-side split. The list keeps its scan columns visible; the detail
23
+ never fully covers them.
24
+ 2. **Is the item work immersive and self-contained?** (long editing, composition) →
25
+ full-screen detail with an explicit return path, or an overlay panel when the visit
26
+ is brief (peek, quick edit).
27
+
28
+ Condition against: collections the user visits rarely or only reads linearly (an archive,
29
+ a blog index) — a plain list with navigation is cheaper. Also against: splitting the
30
+ viewport when the detail needs the full width to do its job (a canvas editor) — that is
31
+ an app-shell question, not master-detail.
32
+
33
+ **Anti-overlay clause.** An overlay detail that hides the list's status/assignee columns
34
+ while the loop still needs them breaks queue awareness — the failure seen whenever a
35
+ "slick" slide-over covers exactly the columns the user was triaging by. If the loop needs
36
+ the list, the split is side-by-side and the list keeps `minmax(0, …)` room for its scan
37
+ columns.
38
+
39
+ ## Parameters
40
+
41
+ ```css
42
+ :root {
43
+ /* Master:detail ratio. 7:5 favors scanning; 5:7 favors item work.
44
+ Choose from the frame's frequent action, then verify at 1280. */
45
+ --md-master-fr: 7fr;
46
+ --md-detail-fr: 5fr;
47
+
48
+ /* Minimum master width before the split collapses to stacked navigation.
49
+ Below this the scan columns compress into unreadability. */
50
+ --md-master-min: 480px;
51
+
52
+ /* Selected-row marker: structural (inset bar + tint), never tint alone. */
53
+ --md-selected-bg: color-mix(in srgb, var(--accent, #1f6f5c) 8%, transparent);
54
+ }
55
+ ```
56
+
57
+ ## Implementation
58
+
59
+ ```html
60
+ <div class="master-detail">
61
+ <section class="md-master" aria-label="목록">
62
+ <!-- Toolbar: search + filters + primary action. Then the collection at
63
+ working density. Selection state is visible and persistent. -->
64
+ <table class="md-table">
65
+ <!-- Scan columns: the nouns the user triages by (status, owner, time).
66
+ The row's open control is a real button/link for keyboard reach. -->
67
+ </table>
68
+ </section>
69
+
70
+ <section class="md-detail" aria-label="상세">
71
+ <!-- Item header: title + status control + explicit return path ("목록으로").
72
+ Then the item workspace: content, then the action surface (reply box,
73
+ form) at the end of the loop, sticky on mobile. -->
74
+ </section>
75
+ </div>
76
+ ```
77
+
78
+ ```css
79
+ .master-detail {
80
+ display: grid;
81
+ grid-template-columns: minmax(var(--md-master-min), var(--md-master-fr)) minmax(0, var(--md-detail-fr));
82
+ min-height: 0;
83
+ }
84
+
85
+ .md-master { border-right: 1px solid var(--line, #e5e7e5); min-width: 0; }
86
+
87
+ .md-table tr[aria-selected="true"] {
88
+ background: var(--md-selected-bg);
89
+ box-shadow: inset 3px 0 0 var(--accent, #1f6f5c);
90
+ }
91
+
92
+ .md-detail { min-width: 0; display: flex; flex-direction: column; }
93
+ ```
94
+
95
+ The empty-detail state is designed, not blank: when nothing is selected, the detail
96
+ region says what selecting does ("왼쪽 목록에서 티켓을 열면 여기에 대화가 보여요") — a
97
+ dead pane teaches nothing. Acting in the detail updates the master in place (status
98
+ badge, ordering) so the loop's feedback is visible where the next scan happens.
99
+
100
+ ## Responsive behavior
101
+
102
+ **1280px (desktop):** side-by-side split at the declared ratio. Both regions scroll
103
+ independently; the detail's action surface stays reachable without scrolling past the
104
+ full conversation when the loop is reply-heavy.
105
+
106
+ **768px (tablet):** the split usually no longer fits `--md-master-min`; collapse to
107
+ stacked navigation — list screen ↔ detail screen — with the selection remembered on
108
+ return (scroll position and selected row preserved). An overlay panel is acceptable here
109
+ only when the loop does not need the list's columns while acting.
110
+
111
+ **375px (mobile):** always stacked navigation: full-screen list → full-screen detail
112
+ with a visible "목록으로" return in the top zone and the action surface sticky at the
113
+ bottom within thumb reach. DOM order stays list-then-detail so focus order and back
114
+ navigation match the visual model. At 320px the list rows recompose (title line +
115
+ meta line + status mark) instead of compressing table columns.
116
+
117
+ ## Do not combine with
118
+
119
+ **app-shell-workbench.md side nav at tablet width** — an expanded side nav plus a
120
+ side-by-side split leaves the master below its minimum; collapse the nav to the icon
121
+ rail before collapsing the split.
122
+
123
+ **sticky-sidebar-scroll.md** — a sticky editorial sidebar and a master pane are competing
124
+ persistent regions with different scroll models; a surface gets one persistent
125
+ navigation-like region beside the work, not two.
126
+
127
+ **Modal dialogs as the default detail** — a modal steals the list, traps scroll, and
128
+ forgets position; it is for confirmations and small focused inputs, not for the loop's
129
+ main workspace.