@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,182 @@
1
+ # Section inversion (dark ↔ light narrative turn)
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 section inversion switches the page from a light surface to a dark one — or the
7
+ reverse — at a specific point in the scroll narrative. The colour change is not
8
+ decoration; it marks a conceptual pivot, a moment where what was light becomes weighty,
9
+ or where accumulated tension releases. One inversion per page is usually correct. Two
10
+ inversions become a rhythm. Three become a pattern with no meaning.
11
+
12
+ `expressive.md`: "The inversion makes the change visible and felt, not just read. One
13
+ inversion per page is usually correct; more than one is a rhythm, not a pivot."
14
+
15
+ ## When it earns its place / When it does not
16
+
17
+ Condition: the scroll narrative has a genuine turn — a moment where the conceptual
18
+ register shifts. Before the inversion, the user is in the exposition; after it, they are
19
+ in the consequence. A product page that moves from benefit to proof, a case study that
20
+ moves from problem to resolution, a campaign page that moves from question to answer.
21
+ The inversion is structural punctuation.
22
+
23
+ `core/theory/layout.md` — von Restorff isolation effect: a single element that breaks the
24
+ pattern is remembered. A section with a different background is the layout equivalent of
25
+ that differentiated element. Used once, it is the most remembered section on the page.
26
+
27
+ Condition against: arbitrary visual variety — swapping colours because the page "needed
28
+ something different" halfway down. `expressive.md`: "When the colour switches back
29
+ without a corresponding conceptual return, the technique has been used as decoration and
30
+ should be removed." Also: pages without a genuine narrative arc (tool UIs, dashboards,
31
+ documentation) where sections are parallel containers, not sequential scenes.
32
+
33
+ ## Parameters
34
+
35
+ ```css
36
+ :root {
37
+ /* Light surface tokens */
38
+ --surface-light: #ffffff;
39
+ --on-surface-light: #0f0f0f;
40
+ --surface-light-subtle: #f5f5f3;
41
+
42
+ /* Dark surface tokens — the inverted register */
43
+ --surface-dark: #0f0f0f;
44
+ --on-surface-dark: #f5f5f3;
45
+ --surface-dark-subtle: #1a1a1a;
46
+
47
+ /* Transition between sections.
48
+ Instant cut reads as deliberate and editorial.
49
+ A 40–80px overlap blend reads as atmospheric.
50
+ Most showpiece executions use the instant cut. */
51
+ --inversion-transition: none; /* or: use a gradient bleed on the section edge */
52
+ }
53
+ ```
54
+
55
+ ## Implementation
56
+
57
+ ```html
58
+ <!-- The narrative sections before the turn -->
59
+ <section class="narrative-section narrative-section--light">
60
+ <div class="narrative-section__inner">
61
+ <h2>The problem was not the product.</h2>
62
+ <p>Four teams. Four definitions of done. Zero shared vocabulary.</p>
63
+ </div>
64
+ </section>
65
+
66
+ <!-- The inversion: this section marks the turn -->
67
+ <section class="narrative-section narrative-section--dark">
68
+ <div class="narrative-section__inner">
69
+ <h2>The turn.</h2>
70
+ <p>One shared language changed what could ship.</p>
71
+ </div>
72
+ </section>
73
+
74
+ <!-- After the turn, the dark surface continues or returns to light intentionally -->
75
+ <section class="narrative-section narrative-section--dark">
76
+ <div class="narrative-section__inner">
77
+ <h2>What changed.</h2>
78
+ <p>Velocity doubled. Not from more people — from fewer conversations.</p>
79
+ </div>
80
+ </section>
81
+ ```
82
+
83
+ ```css
84
+ /* ── Base section structure ─────────────────────────────────────────────────── */
85
+ .narrative-section {
86
+ padding-block: var(--space-section, 8rem);
87
+ padding-inline: var(--space-page-margin, 5vw);
88
+ }
89
+
90
+ .narrative-section__inner {
91
+ max-width: var(--content-measure, 72ch);
92
+ margin-inline: auto;
93
+ }
94
+
95
+ /* ── Light surface ──────────────────────────────────────────────────────────── */
96
+ .narrative-section--light {
97
+ background-color: var(--surface-light);
98
+ color: var(--on-surface-light);
99
+ }
100
+
101
+ /* ── Dark surface (the inversion) ──────────────────────────────────────────── */
102
+ .narrative-section--dark {
103
+ background-color: var(--surface-dark);
104
+ color: var(--on-surface-dark);
105
+ }
106
+
107
+ /* ── Token inheritance: nested components must read their tokens from context ── */
108
+ /* Buttons, links, cards inside inverted sections inherit from the surface token.
109
+ Do not hardcode light or dark values inside components — they will break here. */
110
+ .narrative-section--dark .card {
111
+ background-color: var(--surface-dark-subtle);
112
+ border-color: rgba(255, 255, 255, 0.08);
113
+ }
114
+
115
+ /* ── Optional: gradient bleed at section boundary for atmospheric transitions ── */
116
+ /* Use only when the concept benefits from ambiguity at the turn rather than clarity. */
117
+ .narrative-section--dark.narrative-section--bleed-top {
118
+ border-top: none;
119
+ /* A subtle gradient overlay creates the bleed effect without JavaScript */
120
+ position: relative;
121
+ }
122
+
123
+ .narrative-section--dark.narrative-section--bleed-top::before {
124
+ content: '';
125
+ position: absolute;
126
+ inset: 0;
127
+ top: 0;
128
+ height: 80px;
129
+ background: linear-gradient(to bottom, var(--surface-light), transparent);
130
+ pointer-events: none;
131
+ }
132
+
133
+ /* ── Reduced-motion: no effect needed here — section inversion is a static
134
+ colour change, not an animation. The technique is inherently motion-free. ── */
135
+ ```
136
+
137
+ ## Responsive behavior
138
+
139
+ **375px (mobile):** Section inversion works at all viewports — it is a colour property, not
140
+ a spatial one. The primary concern at narrow widths is that the transition edge is clean.
141
+ Verify that no component's border or shadow bleeds across the section boundary creating a
142
+ visible seam; use `overflow: hidden` on the section if needed.
143
+
144
+ Padding reduction on mobile: the vertical rhythm of the narrative may be compressed.
145
+ Reduce `--space-section` to 4rem on mobile to maintain narrative pace without excessive
146
+ scrolling between conceptual turns.
147
+
148
+ ```css
149
+ @media (max-width: 600px) {
150
+ .narrative-section {
151
+ padding-block: var(--space-section-mobile, 4rem);
152
+ }
153
+ }
154
+ ```
155
+
156
+ **768px (tablet):** No structural change. Verify the inversion boundary falls at a clean
157
+ scroll position — if a section begins mid-viewport on most tablet scroll positions, the
158
+ turn may never land with impact. `min-height: 60vh` on the inverted section ensures the
159
+ user arrives at the dark surface with the full viewport, not a sliver.
160
+
161
+ **1280px (desktop):** At wide viewports, the full-width colour change reads as a hard
162
+ architectural break. This is the register the technique is designed for — the inversion
163
+ commands the full screen width, which is why it works as punctuation. No additional
164
+ responsive changes needed; verify that imagery or tokens designed for the light surface
165
+ are replaced with dark-surface equivalents inside inverted sections.
166
+
167
+ ## Do not combine with
168
+
169
+ **bento-grid.md** inside an inverted section — the bento grid's cell backgrounds create
170
+ a tertiary surface tier (light section → dark section → bento cell background). Three
171
+ surface levels in sequence confuse the inversion signal; the turn is no longer readable
172
+ as a conceptual pivot because it is preceded by background-switching at smaller scale.
173
+
174
+ **section-inversion applied per-section to every section** — this is the most common
175
+ misuse. If every section has a different background, no single inversion marks a turn.
176
+ The technique requires contrast between the norm and the exception; if all sections are
177
+ exceptions, there is no norm to deviate from.
178
+
179
+ **gradient-mesh.md on the inverted surface** — a mesh gradient on the dark section
180
+ introduces colour variation that competes with the starkness the inversion depends on.
181
+ The dark surface's power is its flatness against the light sections; texture undermines
182
+ the contrast signal.
@@ -0,0 +1,184 @@
1
+ # Sidebar / margin annotation layout
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 Tufte-style margin layout places supplementary detail — a footnote, a source citation, a
7
+ clarifying aside, an illustrative data point — in the margin column beside the text that
8
+ references it. The main column reads without interruption; the margin column deepens for
9
+ readers who want it. The vertical alignment between the annotation and its anchor in the
10
+ body text is the structural contract: the annotation appears beside the sentence it
11
+ qualifies, not at the bottom of the section.
12
+
13
+ ## When it earns its place / When it does not
14
+
15
+ Condition: the content is prose-heavy and carries genuinely supplementary material that
16
+ enriches the main argument without being essential to it. Long-form editorial content,
17
+ research-style pages, case studies that cite sources, documentation that benefits from
18
+ contextual notes without disrupting the reading flow. The technique embodies
19
+ `core/theory/layout.md`'s closure principle: the spatial proximity between body text and
20
+ annotation communicates the relationship without requiring visual connectors.
21
+
22
+ Edward Tufte argues in *Visual Display of Quantitative Information* (1983) that placing
23
+ supporting material beside the text that refers to it reduces the need for citations that
24
+ interrupt reading — the footnote does not require a journey to the bottom of the page.
25
+ The margin annotation is the print typographer's solution to this problem, applied to screen.
26
+
27
+ Condition against: narrow viewports (the margin column disappears below 900px and the
28
+ annotations must be inlined or dropped). Marketing pages where every word must hold
29
+ attention — supplementary text in the margin costs reading focus even when it is not
30
+ read; the eye registers its presence. Tool UIs and dashboards where sidebar content
31
+ should be navigation or action, not annotation. And any layout where the body column is
32
+ already narrow — if the reading measure drops below 50ch to accommodate a margin column
33
+ at 768px, the main text has been sacrificed for the annotation, which inverts the hierarchy.
34
+
35
+ `core/theory/layout.md` on information density: "Whitespace here is not wasted; it is
36
+ the product." The margin annotation layout requires generous right-margin whitespace at
37
+ medium densities; it is incompatible with high-density layouts where every column is
38
+ earning its keep.
39
+
40
+ ## Parameters
41
+
42
+ ```css
43
+ :root {
44
+ /* Main content column width.
45
+ Tufte's canonical measure: 55–65% of the text area.
46
+ Readable body text: 50–75ch is the research-validated range. */
47
+ --main-col-width: 60%;
48
+
49
+ /* Margin annotation column width.
50
+ Typically 25–30% of the text area; the remainder is gutter. */
51
+ --margin-col-width: 28%;
52
+
53
+ /* Gutter between main and margin columns. */
54
+ --margin-gutter: clamp(16px, 2vw, 32px);
55
+
56
+ /* Annotation font size.
57
+ Smaller than body to signal supplementary status. 80–85% of body size.
58
+ At 1rem body: 0.8rem annotation is legible and clearly secondary. */
59
+ --annotation-font-size: 0.8rem;
60
+ --annotation-line-height: 1.5;
61
+ --annotation-color: var(--text-secondary, rgba(0,0,0,0.55));
62
+ }
63
+ ```
64
+
65
+ ## Implementation
66
+
67
+ ```html
68
+ <!-- The layout uses a named CSS grid area: main + margin.
69
+ Each .margin-ref creates a vertical alignment anchor.
70
+ The annotation must be a sibling of the annotated paragraph, not a child —
71
+ this keeps the DOM order correct for reading without CSS. -->
72
+ <article class="margined-prose">
73
+ <div class="margined-prose__row">
74
+ <p class="margined-prose__body">
75
+ The spacing scale is not arbitrary.
76
+ <span class="margined-prose__anchor" id="note-1" aria-describedby="ann-1">¹</span>
77
+ Each step in the scale must have a reason that could not be the adjacent step.
78
+ </p>
79
+ <aside class="margined-prose__annotation" id="ann-1" aria-label="Note 1">
80
+ Müller-Brockmann, <em>Grid Systems in Graphic Design</em> (1981): shared
81
+ spacing units make disparate elements read as one system.
82
+ </aside>
83
+ </div>
84
+
85
+ <div class="margined-prose__row">
86
+ <p class="margined-prose__body">
87
+ The 8pt grid is a mechanical constraint with a perceptual benefit: values on the
88
+ grid land at whole pixel boundaries on most screen densities.
89
+ </p>
90
+ <!-- Row with no annotation: the margin column is empty whitespace here.
91
+ The empty right column is not a failure — it is the breathing room
92
+ that makes the annotated rows legible. -->
93
+ </div>
94
+ </article>
95
+ ```
96
+
97
+ ```css
98
+ /* ── Grid layout ──────────────────────────────────────────────────────────── */
99
+ .margined-prose {
100
+ display: flex;
101
+ flex-direction: column;
102
+ gap: var(--space-4, 1rem);
103
+ padding-inline: var(--space-page-margin, 5vw);
104
+ max-width: 100%;
105
+ }
106
+
107
+ .margined-prose__row {
108
+ display: grid;
109
+ grid-template-columns: var(--main-col-width) var(--margin-gutter) var(--margin-col-width);
110
+ align-items: start; /* top-align: annotation begins beside its anchor sentence */
111
+ }
112
+
113
+ .margined-prose__body {
114
+ grid-column: 1 / 2;
115
+ font-size: var(--text-base, 1rem);
116
+ line-height: var(--leading-body, 1.65);
117
+ max-width: 65ch; /* readable measure cap within the column */
118
+ }
119
+
120
+ .margined-prose__annotation {
121
+ grid-column: 3 / 4; /* skip the gutter column */
122
+ font-size: var(--annotation-font-size);
123
+ line-height: var(--annotation-line-height);
124
+ color: var(--annotation-color);
125
+ /* Vertical alignment: the annotation top-aligns with the row.
126
+ For precise alignment with a specific sentence, margin-top may be added
127
+ equal to the number of leading lines above the anchor word. */
128
+ padding-top: 0.15em; /* optically align with cap-height of body text */
129
+ }
130
+
131
+ /* Annotation number in body: superscript, same color as annotation. */
132
+ .margined-prose__anchor {
133
+ font-size: 0.7em;
134
+ vertical-align: super;
135
+ color: var(--annotation-color);
136
+ text-decoration: none;
137
+ }
138
+ ```
139
+
140
+ ## Responsive behavior
141
+
142
+ **375px (mobile):** The margin column is not viable at this width — the body column would
143
+ fall below readable measure to accommodate it. Stack the annotation below its paragraph,
144
+ visually differentiated by font size and a left border.
145
+
146
+ ```css
147
+ @media (max-width: 900px) {
148
+ .margined-prose__row {
149
+ display: block; /* collapse grid to single column */
150
+ }
151
+
152
+ .margined-prose__annotation {
153
+ display: block;
154
+ margin-top: var(--space-2, 0.5rem);
155
+ padding-left: var(--space-3, 0.75rem);
156
+ border-left: 2px solid var(--border-subtle, rgba(0,0,0,0.12));
157
+ /* Annotation is now inline, below the body paragraph, clearly secondary. */
158
+ }
159
+ }
160
+ ```
161
+
162
+ **768px (tablet):** The 900px breakpoint covers tablet portrait. At landscape tablet
163
+ (1024px), the grid layout works; test that `--main-col-width: 60%` at 1024px produces a
164
+ body column of at least 50ch. If not, widen the main column to 65% and narrow the margin
165
+ column correspondingly.
166
+
167
+ **1280px (desktop):** The full three-column grid. The empty margin column in un-annotated
168
+ rows is intentional negative space — do not fill it with anything. The whitespace signals
169
+ "more is available if you look" without demanding attention.
170
+
171
+ ## Do not combine with
172
+
173
+ **asymmetric-diagonal-grid.md** — the diagonal layout breaks the column contract that the
174
+ margin annotation depends on. The annotation cannot be vertically anchored to its body
175
+ text if the body column is itself off-axis.
176
+
177
+ **editorial-index-labels.md** in the same section — both add secondary visual elements
178
+ in the peripheral field. The index number in the left background and the annotation in the
179
+ right margin create a four-zone reading field (left-decoration, body, gutter, annotation)
180
+ that costs more attention than it returns.
181
+
182
+ **High-density layouts** where the body column is already maximised — the margin annotation
183
+ technique is structural whitespace. It cannot be added to a dense layout without sacrificing
184
+ reading measure.
@@ -0,0 +1,224 @@
1
+ # Split-screen hero
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 split-screen hero divides the viewport vertically into two panels. One panel holds the
7
+ primary statement — headline, copy, and CTA. The other holds a counterpart — an image,
8
+ a graphic, a live demo, or a contrasting visual element. The division is the design
9
+ decision: the two sides are in genuine tension or complementarity, and the user reads
10
+ both as a complete argument.
11
+
12
+ ## When it earns its place / When it does not
13
+
14
+ Condition: the content genuinely has two distinct poles that benefit from juxtaposition.
15
+ A product with a before/after proposition, a dual-audience site, a brand with a visual
16
+ and a verbal identity that are each strong enough to carry half the viewport. The
17
+ split must express a relationship — contrast, comparison, dialogue — not simply divide
18
+ available space in half because the designer had two things to place.
19
+
20
+ `core/theory/layout.md` on scan patterns: the Z-pattern (sparse marketing pages) places
21
+ the diagonal at centre-screen, which is exactly the vertical divider of a split-screen
22
+ layout. The left panel captures the entry, the diagonal crosses to the right panel, and
23
+ the eye completes the Z on whichever side holds the CTA. This makes the split-screen
24
+ structurally aligned with the Z-pattern's natural scan — neither panel is out of the
25
+ eye's path.
26
+
27
+ Condition against: when there is one primary message. A split hero on a page with a
28
+ single value proposition divides the viewport's attention budget without a reason; one
29
+ side will always be weaker, and the weaker side dilutes the stronger. `core/theory/
30
+ layout.md` visual hierarchy rule 1: "If you have one thing that matters most, it must
31
+ be the largest." A split hero makes two things equal — choose this only when equality
32
+ is accurate. Also: mobile viewports where the split collapses to a stack, making the
33
+ panel order a design decision that the responsive logic must handle deliberately.
34
+
35
+ ## Parameters
36
+
37
+ ```css
38
+ :root {
39
+ /* Column split ratio.
40
+ 50/50 reads as pure duality — equal weight.
41
+ 60/40 or 55/45 reads as a primary/supporting relationship.
42
+ Choose based on which side carries the primary message. */
43
+ --split-left: 55%;
44
+ --split-right: 45%;
45
+
46
+ /* Minimum height.
47
+ 100svh ensures both panels fill the viewport on first load.
48
+ svh (small viewport height) accounts for mobile browser chrome. */
49
+ --split-min-height: 100svh;
50
+
51
+ /* The divider between panels.
52
+ Invisible divider: panels differentiated by background only.
53
+ Visible divider: a 1px line, a gap, or a decorative element. */
54
+ --split-gap: 0px; /* or var(--space-2) for a gap-based divider */
55
+
56
+ /* Padding within each panel. Both panels share the same internal padding
57
+ for visual consistency across the divider. */
58
+ --split-panel-padding: clamp(2rem, 5vw, 6rem);
59
+ }
60
+ ```
61
+
62
+ ## Implementation
63
+
64
+ ```html
65
+ <!-- The split hero contains exactly two panels.
66
+ Left panel: text, CTA — the verbal side.
67
+ Right panel: visual, image, graphic — the visual side.
68
+ DOM order: left-then-right mirrors the visual left-to-right reading order.
69
+ On mobile: left panel (text) appears first, right panel (visual) below. -->
70
+ <section class="split-hero">
71
+ <div class="split-hero__panel split-hero__panel--left">
72
+ <div class="split-hero__panel-inner">
73
+ <p class="split-hero__eyebrow">Engineering tools</p>
74
+ <h1 class="split-hero__heading">
75
+ Write once.<br>
76
+ Ship everywhere.
77
+ </h1>
78
+ <p class="split-hero__body">
79
+ The deployment layer that reads your config and disappears.
80
+ </p>
81
+ <a href="#" class="split-hero__cta">Start building</a>
82
+ </div>
83
+ </div>
84
+
85
+ <div class="split-hero__panel split-hero__panel--right">
86
+ <!-- Visual panel: image, illustration, live code demo, gradient mesh, etc.
87
+ This panel is allowed to be full-bleed — no inner padding required
88
+ if the visual extends to all four edges of the panel. -->
89
+ <div class="split-hero__visual" role="img" aria-label="[describe the visual]">
90
+ <!-- Visual content goes here -->
91
+ </div>
92
+ </div>
93
+ </section>
94
+ ```
95
+
96
+ ```css
97
+ .split-hero {
98
+ display: grid;
99
+ grid-template-columns: var(--split-left) var(--split-right);
100
+ gap: var(--split-gap);
101
+ min-height: var(--split-min-height);
102
+ }
103
+
104
+ .split-hero__panel {
105
+ display: flex;
106
+ align-items: center; /* vertical centering within the viewport */
107
+ overflow: hidden;
108
+ }
109
+
110
+ .split-hero__panel--left {
111
+ background-color: var(--surface-primary, #ffffff);
112
+ padding: var(--split-panel-padding);
113
+ }
114
+
115
+ .split-hero__panel--right {
116
+ background-color: var(--surface-accent, #0f0f0f);
117
+ /* Right panel may be full-bleed visual — no padding applied here;
118
+ the visual content manages its own edges. */
119
+ }
120
+
121
+ .split-hero__panel-inner {
122
+ max-width: 40ch; /* constrains the text column within the left panel */
123
+ }
124
+
125
+ .split-hero__eyebrow {
126
+ font-size: var(--text-xs, 0.75rem);
127
+ letter-spacing: 0.1em;
128
+ text-transform: uppercase;
129
+ color: var(--text-secondary);
130
+ margin-bottom: var(--space-4, 1rem);
131
+ }
132
+
133
+ .split-hero__heading {
134
+ font-size: clamp(2rem, 4vw, 3.5rem);
135
+ font-weight: var(--weight-display, 700);
136
+ line-height: 1.05;
137
+ letter-spacing: -0.03em;
138
+ margin-bottom: var(--space-5, 1.25rem);
139
+ }
140
+
141
+ .split-hero__body {
142
+ font-size: var(--text-base, 1rem);
143
+ line-height: var(--leading-body, 1.6);
144
+ color: var(--text-secondary);
145
+ max-width: 38ch;
146
+ margin-bottom: var(--space-8, 2rem);
147
+ }
148
+
149
+ .split-hero__cta {
150
+ display: inline-block;
151
+ padding: var(--space-3, 0.75rem) var(--space-6, 1.5rem);
152
+ background-color: var(--color-accent);
153
+ color: var(--on-accent);
154
+ font-size: var(--text-sm, 0.875rem);
155
+ font-weight: var(--weight-medium, 500);
156
+ border-radius: var(--radius-button, 6px);
157
+ text-decoration: none;
158
+ /* 44×44px minimum touch target — verify the padding achieves this */
159
+ min-height: 44px;
160
+ }
161
+
162
+ .split-hero__visual {
163
+ width: 100%;
164
+ height: 100%;
165
+ object-fit: cover; /* for image content */
166
+ }
167
+ ```
168
+
169
+ ## Responsive behavior
170
+
171
+ **375px (mobile):** The two-column split collapses to a single column. The visual panel
172
+ stacks below the text panel — the text remains first in the reading order and the
173
+ visual follows. If the visual panel has a fixed height, reduce it to a reasonable
174
+ proportion (40–50vh) rather than the full viewport height it occupied on desktop.
175
+
176
+ ```css
177
+ @media (max-width: 768px) {
178
+ .split-hero {
179
+ grid-template-columns: 1fr;
180
+ min-height: auto;
181
+ }
182
+
183
+ .split-hero__panel--left {
184
+ padding: var(--split-panel-padding);
185
+ min-height: auto;
186
+ }
187
+
188
+ .split-hero__panel--right {
189
+ min-height: 45vw; /* generous proportion on mobile, not half the viewport */
190
+ max-height: 60vw;
191
+ }
192
+
193
+ .split-hero__heading {
194
+ /* At narrow width, the heading may grow via clamp without needing adjustment.
195
+ Verify it does not overflow at 375px before removing this. */
196
+ font-size: clamp(1.75rem, 8vw, 2.5rem);
197
+ }
198
+ }
199
+ ```
200
+
201
+ **768px (tablet):** The split works at tablet width. At 768px, `var(--split-left): 55%`
202
+ gives the text panel ~422px — sufficient for the content column at `max-width: 40ch`.
203
+ The visual panel receives ~346px. If the visual requires minimum width to read clearly,
204
+ swap to a 50/50 split at this breakpoint.
205
+
206
+ **1280px (desktop):** Full split. At 1280px the left panel at 55% is ~704px; the `max-
207
+ width: 40ch` constraint means the text column occupies ~640px (at 16px base). Generous
208
+ whitespace within the left panel; the text does not stretch to fill the panel width.
209
+
210
+ ## Do not combine with
211
+
212
+ **bento-grid.md** inside either panel — a split hero establishes a two-panel spatial
213
+ contract; a bento grid inside one panel introduces a third spatial system within a zone
214
+ that already has compositional meaning.
215
+
216
+ **typographic-hero.md** — both recipes make the hero the visual anchor. Using both in
217
+ sequence (typographic hero, then split-screen) redundantly establishes the hero register
218
+ twice; the page does not know which one is the primary statement.
219
+
220
+ **A right panel with text-heavy content** — when the right panel carries comparable text
221
+ volume to the left panel, the split reads as two columns of equal density, which is
222
+ `core/theory/layout.md`'s multi-column form warning applied to a hero: the eye must
223
+ decide where to start, and the Z-pattern scan that makes the split-screen structurally
224
+ sound collapses.