@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.
- package/LICENSE +21 -0
- package/README.ko.md +282 -0
- package/README.md +282 -0
- package/adapters/build.ts +184 -0
- package/adapters/claude.ts +111 -0
- package/adapters/codex.ts +68 -0
- package/adapters/tokens.ts +21 -0
- package/adapters/tool-map.json +7 -0
- package/bin/omd-install.ts +76 -0
- package/bin/omd.ts +1774 -0
- package/core/asset-sourcing/index.ts +187 -0
- package/core/coach/index.ts +98 -0
- package/core/composition/app-shell-workbench.md +168 -0
- package/core/composition/asymmetric-diagonal-grid.md +178 -0
- package/core/composition/bento-grid.md +223 -0
- package/core/composition/editorial-index-labels.md +173 -0
- package/core/composition/form-wizard-stepper.md +132 -0
- package/core/composition/master-detail-flow.md +129 -0
- package/core/composition/section-inversion.md +182 -0
- package/core/composition/sidebar-margin-annotation.md +184 -0
- package/core/composition/split-screen-hero.md +224 -0
- package/core/composition/sticky-sidebar-scroll.md +219 -0
- package/core/composition/typographic-hero.md +100 -0
- package/core/composition-contract/index.ts +233 -0
- package/core/composition-contract/visual-richness.ts +101 -0
- package/core/config/index.ts +27 -0
- package/core/copy/index.ts +296 -0
- package/core/craft/finish-pass.md +356 -0
- package/core/craft/index.ts +33 -0
- package/core/design/index.ts +581 -0
- package/core/design/interaction-states.ts +189 -0
- package/core/eval-harness/index.ts +225 -0
- package/core/evidence/final.ts +444 -0
- package/core/evidence/task.ts +459 -0
- package/core/figma/client.ts +312 -0
- package/core/figma/diff.ts +261 -0
- package/core/figma/responsive.ts +310 -0
- package/core/figma/system.ts +466 -0
- package/core/figma/types.ts +185 -0
- package/core/frame/check-ux.ts +180 -0
- package/core/frame/index.ts +39 -0
- package/core/frame/write.ts +168 -0
- package/core/graphics/css-illustration-primitives.md +189 -0
- package/core/graphics/duotone-image-presets.md +173 -0
- package/core/graphics/gradient-mesh.md +178 -0
- package/core/graphics/noise-grain-texture.md +150 -0
- package/core/graphics/placeholder-policy.md +185 -0
- package/core/graphics/svg-geometric-patterns.md +145 -0
- package/core/history/index.ts +39 -0
- package/core/install/detect.ts +32 -0
- package/core/install/install.ts +321 -0
- package/core/install/patch-claude.ts +109 -0
- package/core/install/patch-codex.ts +82 -0
- package/core/interaction/index.ts +101 -0
- package/core/interaction/recipes/signature-lighting.md +203 -0
- package/core/ir/dom.ts +352 -0
- package/core/ir/normalize.ts +177 -0
- package/core/motion/easing.md +151 -0
- package/core/motion/energy.ts +188 -0
- package/core/motion/recipes/image-hover-distortion.md +198 -0
- package/core/motion/recipes/magnetic-hover.md +229 -0
- package/core/motion/recipes/marquee.md +202 -0
- package/core/motion/recipes/number-counter.md +242 -0
- package/core/motion/recipes/page-loader.md +240 -0
- package/core/motion/recipes/parallax.md +233 -0
- package/core/motion/recipes/scroll-reveal.md +210 -0
- package/core/motion/recipes/section-color-inversion.md +217 -0
- package/core/motion/recipes/split-text-entrance.md +222 -0
- package/core/motion/recipes/stagger-orchestrator.md +227 -0
- package/core/motion/recipes/sticky-scene-transition.md +246 -0
- package/core/motion/recipes/view-transitions.md +239 -0
- package/core/probe/index.ts +186 -0
- package/core/probe/schema.json +46 -0
- package/core/protocol/composition-contract.md +192 -0
- package/core/protocol/copy-deck.md +96 -0
- package/core/protocol/human-design-loop.md +447 -0
- package/core/protocol/slop-review.md +71 -0
- package/core/ref/batch.ts +94 -0
- package/core/ref/blueprint.ts +199 -0
- package/core/ref/distance.ts +225 -0
- package/core/ref/invariants.ts +162 -0
- package/core/ref/signal.ts +37 -0
- package/core/ref/store.ts +122 -0
- package/core/render/index.ts +607 -0
- package/core/rules/attribution.ts +159 -0
- package/core/rules/builtin/contrast.yaml +8 -0
- package/core/rules/builtin/focus.yaml +8 -0
- package/core/rules/builtin/hit-area.yaml +8 -0
- package/core/rules/builtin/ko.yaml +6 -0
- package/core/rules/builtin/motion.yaml +80 -0
- package/core/rules/builtin/slop.yaml +365 -0
- package/core/rules/builtin/spacing.yaml +9 -0
- package/core/rules/builtin/system.yaml +42 -0
- package/core/rules/builtin/token.yaml +8 -0
- package/core/rules/builtin/ux.yaml +125 -0
- package/core/rules/engine.ts +88 -0
- package/core/rules/leakage.ts +72 -0
- package/core/rules/motion-spec.ts +204 -0
- package/core/site/index.ts +100 -0
- package/core/slop/index.ts +427 -0
- package/core/slop/text-slop.ts +220 -0
- package/core/source-seal/index.ts +174 -0
- package/core/stack/index.ts +53 -0
- package/core/target/index.ts +183 -0
- package/core/theory/color.md +285 -0
- package/core/theory/components.md +272 -0
- package/core/theory/craft.md +270 -0
- package/core/theory/expressive.md +213 -0
- package/core/theory/imagegen.md +105 -0
- package/core/theory/layout.md +159 -0
- package/core/theory/motion.md +283 -0
- package/core/theory/typography.md +134 -0
- package/core/theory/ux.md +580 -0
- package/core/theory/voice.md +595 -0
- package/core/types.ts +684 -0
- package/package.json +40 -0
- package/src/agents/composer.agent.yaml +174 -0
- package/src/agents/eye.agent.yaml +208 -0
- package/src/agents/framer.agent.yaml +52 -0
- package/src/agents/glance.agent.yaml +19 -0
- package/src/agents/hand.agent.yaml +200 -0
- package/src/agents/scout.agent.yaml +71 -0
- package/src/agents/sketch.agent.yaml +64 -0
- package/src/agents/typesetter.agent.yaml +52 -0
- package/src/agents/writer.agent.yaml +63 -0
- package/src/skills/omd-coach/SKILL.md +40 -0
- package/src/skills/omd-critique/SKILL.md +77 -0
- package/src/skills/omd-figma/SKILL.md +256 -0
- package/src/skills/omd-humanize/SKILL.md +160 -0
- package/src/skills/omd-scout/SKILL.md +77 -0
- 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.
|