@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,173 @@
|
|
|
1
|
+
# Duotone / treated image presets
|
|
2
|
+
|
|
3
|
+
A duotone treatment replaces an image's tonal range with two colours — one for the
|
|
4
|
+
shadows, one for the highlights — using a CSS `filter` chain. The result reads as
|
|
5
|
+
intentional art direction rather than stock photography: the same face or landscape
|
|
6
|
+
in two hues aligned to the brand palette becomes a designed asset, not a found one.
|
|
7
|
+
The technique unifies images from different sources under a single visual language.
|
|
8
|
+
|
|
9
|
+
The CSS implementation uses `filter: grayscale(1) contrast() brightness() sepia() hue-rotate()` chained in a specific order to achieve the duotone effect without SVG
|
|
10
|
+
feColorMatrix or Canvas manipulation. A secondary blend-mode approach using
|
|
11
|
+
`mix-blend-mode: multiply` or `color` can achieve richer duotones when browser support
|
|
12
|
+
allows for the stacking context requirements.
|
|
13
|
+
|
|
14
|
+
## When it earns its place / When it does not
|
|
15
|
+
|
|
16
|
+
Condition: when real images exist but come from varied sources — team photography at
|
|
17
|
+
different times, stock imagery from multiple libraries, user-generated content — and
|
|
18
|
+
visual unification is needed without re-photographing everything. A duotone treatment
|
|
19
|
+
makes diverse source material read as a designed set. Also: when the brand palette is
|
|
20
|
+
strong and the concept benefits from photographs that are unmistakably on-brand; the
|
|
21
|
+
treatment removes the image's original colour story and replaces it with the brand's.
|
|
22
|
+
|
|
23
|
+
`core/theory/layout.md` on similarity: "elements that look the same are read as
|
|
24
|
+
belonging to the same category." A grid of duotone-treated images reads as a unified
|
|
25
|
+
collection; the same grid with untreated photographs reads as a collection of separate
|
|
26
|
+
images that happen to share space.
|
|
27
|
+
|
|
28
|
+
Condition against: product photography where accurate colour matters — food, fashion,
|
|
29
|
+
product hardware. Medical or scientific imagery where colour carries meaning. Any context
|
|
30
|
+
where the user needs to see the actual colours of the subject. Documentation images where
|
|
31
|
+
clarity outweighs aesthetic treatment.
|
|
32
|
+
|
|
33
|
+
## Parameters
|
|
34
|
+
|
|
35
|
+
```css
|
|
36
|
+
:root {
|
|
37
|
+
/* The CSS filter duotone uses a grayscale base plus hue rotation and saturation.
|
|
38
|
+
Exact values depend on the target shadow and highlight colours.
|
|
39
|
+
The values below produce a warm amber shadow / cool blue highlight duotone.
|
|
40
|
+
Calibrate these against the brand palette — these are starting points. */
|
|
41
|
+
|
|
42
|
+
/* Step 1: desaturate completely. */
|
|
43
|
+
--dt-grayscale: grayscale(1);
|
|
44
|
+
|
|
45
|
+
/* Step 2: boost contrast so shadows and highlights are distinct zones. */
|
|
46
|
+
--dt-contrast: contrast(1.2);
|
|
47
|
+
|
|
48
|
+
/* Step 3: add the shadow tone via sepia (shifts greys to warm amber). */
|
|
49
|
+
--dt-sepia: sepia(0.8);
|
|
50
|
+
|
|
51
|
+
/* Step 4: rotate hue to place the image in the brand palette zone.
|
|
52
|
+
0deg: warm amber (sepia base).
|
|
53
|
+
60deg: warm yellow-green.
|
|
54
|
+
180deg: cyan-teal.
|
|
55
|
+
240deg: blue.
|
|
56
|
+
300deg: purple-pink.
|
|
57
|
+
Combine with saturation adjustment for exact colour targeting. */
|
|
58
|
+
--dt-hue: hue-rotate(200deg);
|
|
59
|
+
|
|
60
|
+
/* Step 5: saturation of the resulting toned image.
|
|
61
|
+
0.6–0.8: subtle colour wash — reads as toned, not fully coloured.
|
|
62
|
+
1.0–1.4: strong colour — reads as deliberately duotoned. */
|
|
63
|
+
--dt-saturate: saturate(1.2);
|
|
64
|
+
|
|
65
|
+
/* Final brightness correction: duotone often darkens; compensate here. */
|
|
66
|
+
--dt-brightness: brightness(1.05);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Implementation
|
|
71
|
+
|
|
72
|
+
**CSS filter chain (single-colour tint approach)**
|
|
73
|
+
|
|
74
|
+
```css
|
|
75
|
+
/* The filter chain order matters. Changing the order changes the output. */
|
|
76
|
+
.duotone {
|
|
77
|
+
filter:
|
|
78
|
+
var(--dt-grayscale)
|
|
79
|
+
var(--dt-contrast)
|
|
80
|
+
var(--dt-sepia)
|
|
81
|
+
var(--dt-hue)
|
|
82
|
+
var(--dt-saturate)
|
|
83
|
+
var(--dt-brightness);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* Preset: brand blue duotone */
|
|
87
|
+
.duotone--blue {
|
|
88
|
+
filter: grayscale(1) contrast(1.2) sepia(0.9) hue-rotate(180deg) saturate(1.3) brightness(1.0);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Preset: brand amber duotone */
|
|
92
|
+
.duotone--amber {
|
|
93
|
+
filter: grayscale(1) contrast(1.15) sepia(1) hue-rotate(20deg) saturate(1.1) brightness(1.08);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Preset: monochrome (single tone, minimal saturation) */
|
|
97
|
+
.duotone--mono {
|
|
98
|
+
filter: grayscale(1) contrast(1.1) brightness(1.05);
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Mix-blend-mode approach (richer control, requires stacking context)**
|
|
103
|
+
|
|
104
|
+
```html
|
|
105
|
+
<!-- The image sits in a container. A pseudo-element with the brand colour overlays
|
|
106
|
+
it via mix-blend-mode: color, which replaces the image's hue while preserving
|
|
107
|
+
luminosity. This produces a true two-stop duotone. -->
|
|
108
|
+
<div class="duotone-blend">
|
|
109
|
+
<img src="image.jpg" alt="[description]" class="duotone-blend__image">
|
|
110
|
+
</div>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```css
|
|
114
|
+
.duotone-blend {
|
|
115
|
+
position: relative;
|
|
116
|
+
isolation: isolate; /* required for mix-blend-mode to work correctly */
|
|
117
|
+
overflow: hidden;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.duotone-blend__image {
|
|
121
|
+
display: block;
|
|
122
|
+
width: 100%;
|
|
123
|
+
height: 100%;
|
|
124
|
+
object-fit: cover;
|
|
125
|
+
filter: grayscale(1) contrast(1.1); /* desaturate first */
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* The colour overlay uses mix-blend-mode: color to apply the brand hue
|
|
129
|
+
to the greyscale image below. */
|
|
130
|
+
.duotone-blend::after {
|
|
131
|
+
content: '';
|
|
132
|
+
position: absolute;
|
|
133
|
+
inset: 0;
|
|
134
|
+
background-color: var(--color-brand-primary, #1a4d8f);
|
|
135
|
+
opacity: 0.6; /* lower = more greyscale visible; higher = more brand colour */
|
|
136
|
+
mix-blend-mode: color;
|
|
137
|
+
pointer-events: none;
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Linter notes
|
|
142
|
+
|
|
143
|
+
- Duotone treatments applied in the flagged SLOP-GRADIENT hue bands (hue-rotate values
|
|
144
|
+
that produce indigo/violet or purple/pink tones) carry the same `SLOP-GRADIENT` risk as
|
|
145
|
+
mesh gradients in those bands. The treatment is applied to an image rather than a
|
|
146
|
+
background, so the `SLOP-GRADIENT` rule does not fire directly — but the aesthetic
|
|
147
|
+
outcome (a purple-tinted image on a purple gradient background) reads as the same AI-
|
|
148
|
+
default visual language. Record the colour choice if it lands in those bands.
|
|
149
|
+
|
|
150
|
+
- The `mix-blend-mode: color` approach requires the parent to have `isolation: isolate` set.
|
|
151
|
+
Without the stacking context, the blend mode may affect elements outside the intended
|
|
152
|
+
container. Always verify the containment before shipping.
|
|
153
|
+
|
|
154
|
+
- Duotone treatments reduce effective contrast for users with colour vision deficiencies.
|
|
155
|
+
After applying a treatment, run `omd check` to verify that any text overlaid on the
|
|
156
|
+
duotone image still meets 4.5:1 contrast. The treatment changes the image's luminance
|
|
157
|
+
distribution; a dark duotone image may produce lower contrast than the untreated version.
|
|
158
|
+
|
|
159
|
+
## Do not combine with
|
|
160
|
+
|
|
161
|
+
**noise-grain-texture.md at opacity above 8%** — grain over a duotone image creates
|
|
162
|
+
triple-layered processing (image → duotone filter → grain overlay) that produces an
|
|
163
|
+
image that reads as heavily processed rather than treated. Light grain (3–5%) over a
|
|
164
|
+
duotone can add material quality; heavy grain obscures the treatment.
|
|
165
|
+
|
|
166
|
+
**css-illustration-primitives.md on the same visual zone** — a duotone image and a CSS
|
|
167
|
+
blob in the same section compete for the decorative register. The treated image is itself
|
|
168
|
+
the art-directed visual; a blob behind or over it dilutes its effect.
|
|
169
|
+
|
|
170
|
+
**Accurate-colour product contexts** — if the image is a product photograph and the
|
|
171
|
+
user needs to see the actual colour of the product, do not apply a duotone treatment.
|
|
172
|
+
The treatment overrides the image's colour story with the brand's, which is correct
|
|
173
|
+
for editorial and brand contexts and incorrect for product accuracy contexts.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Gradient mesh backgrounds
|
|
2
|
+
|
|
3
|
+
A gradient mesh layers multiple `radial-gradient()` declarations on a single element,
|
|
4
|
+
producing a soft, multi-source light field that reads as dimensional rather than flat.
|
|
5
|
+
Unlike a simple two-stop linear gradient, a mesh gradient has no visible direction — it
|
|
6
|
+
pools and diffuses, the way light fills a room through multiple windows. The technique
|
|
7
|
+
is CSS-only: no SVG, no image file, no canvas.
|
|
8
|
+
|
|
9
|
+
## When it earns its place / When it does not
|
|
10
|
+
|
|
11
|
+
Condition: the page has large flat sections that read as too digital — blank backgrounds
|
|
12
|
+
that feel like developer defaults rather than design decisions. A mesh gradient makes
|
|
13
|
+
the surface a presence without adding visual weight that competes with the content.
|
|
14
|
+
Showpiece and confident registers both benefit: the mesh adds depth to the hero or a
|
|
15
|
+
full-bleed section background without introducing imagery or illustration overhead.
|
|
16
|
+
`expressive.md` on grain texture (the natural companion to mesh): "Flat backgrounds or
|
|
17
|
+
smooth gradients that read as too digital — noise adds the tactile quality that solid
|
|
18
|
+
fills lack." A mesh gradient addresses the same problem at the colour level before grain
|
|
19
|
+
addresses it at the texture level.
|
|
20
|
+
|
|
21
|
+
Condition against: pages where the brand palette is already active and saturated — adding
|
|
22
|
+
a mesh to a high-saturation layout creates a chromatic argument between the mesh and the
|
|
23
|
+
content. Any dark section where the mesh's colour pools compete with text legibility;
|
|
24
|
+
verify 4.5:1 contrast between every text element and the mesh behind it at every mesh
|
|
25
|
+
position, not just the average. And for AI-generated-looking aesthetics: see the linter
|
|
26
|
+
notes below — specific hue combinations trigger `SLOP-GRADIENT` because they are the
|
|
27
|
+
statistical mean of AI design output, not a brand decision.
|
|
28
|
+
|
|
29
|
+
## Parameters
|
|
30
|
+
|
|
31
|
+
```css
|
|
32
|
+
:root {
|
|
33
|
+
/* Each radial gradient defines one light source in the mesh.
|
|
34
|
+
Three to five sources produce a convincing mesh; more than six
|
|
35
|
+
tends toward muddiness rather than depth.
|
|
36
|
+
Position each source at a different corner or edge — distribute
|
|
37
|
+
them to avoid clustering. */
|
|
38
|
+
|
|
39
|
+
/* Source 1: warm anchor — primary brand hue */
|
|
40
|
+
--mesh-source-1-color: hsl(220deg 60% 70% / 0.8);
|
|
41
|
+
--mesh-source-1-pos: 0% 0%;
|
|
42
|
+
--mesh-source-1-size: 60% 60%;
|
|
43
|
+
|
|
44
|
+
/* Source 2: cool contrast — secondary or complementary hue */
|
|
45
|
+
--mesh-source-2-color: hsl(280deg 50% 60% / 0.6);
|
|
46
|
+
--mesh-source-2-pos: 100% 0%;
|
|
47
|
+
--mesh-source-2-size: 50% 50%;
|
|
48
|
+
|
|
49
|
+
/* Source 3: neutral or analogous midground */
|
|
50
|
+
--mesh-source-3-color: hsl(200deg 40% 80% / 0.5);
|
|
51
|
+
--mesh-source-3-pos: 50% 100%;
|
|
52
|
+
--mesh-source-3-size: 70% 50%;
|
|
53
|
+
|
|
54
|
+
/* Base fill colour: what shows where the gradients do not reach.
|
|
55
|
+
Should be the lightest or darkest value in the palette, never a mid-tone.
|
|
56
|
+
Mid-tone bases with mid-tone gradients produce uniform murk. */
|
|
57
|
+
--mesh-base: #f0f0ee;
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Implementation
|
|
62
|
+
|
|
63
|
+
```css
|
|
64
|
+
/* ── CSS-only mesh gradient ──────────────────────────────────────────────── */
|
|
65
|
+
.mesh-bg {
|
|
66
|
+
background-color: var(--mesh-base);
|
|
67
|
+
background-image:
|
|
68
|
+
radial-gradient(
|
|
69
|
+
ellipse var(--mesh-source-1-size) at var(--mesh-source-1-pos),
|
|
70
|
+
var(--mesh-source-1-color),
|
|
71
|
+
transparent
|
|
72
|
+
),
|
|
73
|
+
radial-gradient(
|
|
74
|
+
ellipse var(--mesh-source-2-size) at var(--mesh-source-2-pos),
|
|
75
|
+
var(--mesh-source-2-color),
|
|
76
|
+
transparent
|
|
77
|
+
),
|
|
78
|
+
radial-gradient(
|
|
79
|
+
ellipse var(--mesh-source-3-size) at var(--mesh-source-3-pos),
|
|
80
|
+
var(--mesh-source-3-color),
|
|
81
|
+
transparent
|
|
82
|
+
);
|
|
83
|
+
/* No background-blend-mode needed for basic mesh — the stacking of transparent
|
|
84
|
+
gradients handles the blending. background-blend-mode: multiply or screen can
|
|
85
|
+
intensify the effect but is harder to calibrate for legibility. */
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* ── Full-section mesh hero ──────────────────────────────────────────────── */
|
|
89
|
+
.mesh-hero {
|
|
90
|
+
min-height: 100svh;
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: flex-end;
|
|
93
|
+
padding: var(--space-page-margin, 5vw);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* ── Mesh as a positional decorative element (not full-bleed) ─────────────── */
|
|
97
|
+
/* Place the mesh behind specific components without covering the entire background. */
|
|
98
|
+
.mesh-spot {
|
|
99
|
+
position: relative;
|
|
100
|
+
isolation: isolate; /* creates stacking context for the pseudo-element mesh */
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.mesh-spot::before {
|
|
104
|
+
content: '';
|
|
105
|
+
position: absolute;
|
|
106
|
+
inset: -20%;
|
|
107
|
+
z-index: -1;
|
|
108
|
+
border-radius: 50%; /* circular mesh pool reads as ambient light source */
|
|
109
|
+
background-image:
|
|
110
|
+
radial-gradient(
|
|
111
|
+
ellipse 80% 80% at 30% 40%,
|
|
112
|
+
var(--mesh-source-1-color),
|
|
113
|
+
transparent
|
|
114
|
+
),
|
|
115
|
+
radial-gradient(
|
|
116
|
+
ellipse 60% 60% at 70% 60%,
|
|
117
|
+
var(--mesh-source-2-color),
|
|
118
|
+
transparent
|
|
119
|
+
);
|
|
120
|
+
filter: blur(40px); /* blur softens the ellipse edges for a true mesh feel */
|
|
121
|
+
pointer-events: none;
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```html
|
|
126
|
+
<!-- Full-section mesh: -->
|
|
127
|
+
<section class="mesh-bg mesh-hero">
|
|
128
|
+
<h1>Content over the mesh.</h1>
|
|
129
|
+
</section>
|
|
130
|
+
|
|
131
|
+
<!-- Spot mesh behind a card or feature: -->
|
|
132
|
+
<div class="mesh-spot">
|
|
133
|
+
<div class="card">Content gains an ambient glow.</div>
|
|
134
|
+
</div>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Linter notes
|
|
138
|
+
|
|
139
|
+
The `SLOP-GRADIENT` rule fires on the following hue combinations because they are the
|
|
140
|
+
statistical signature of AI-generated design output — the default gradient every model
|
|
141
|
+
reaches for when asked to "make it look good":
|
|
142
|
+
|
|
143
|
+
- **Indigo → violet** (hue range 240–290deg): the single most overused gradient in AI
|
|
144
|
+
design. Any `background: linear-gradient` or `radial-gradient` whose two dominant
|
|
145
|
+
stops land in this band fires `SLOP-GRADIENT`. If the brand genuinely uses indigo and
|
|
146
|
+
violet, the decision must be recorded in `omd decision` with the brand's hex values
|
|
147
|
+
and the reference that supports the choice.
|
|
148
|
+
|
|
149
|
+
- **Purple → pink** (hue range 280–330deg): a secondary AI-default combination.
|
|
150
|
+
Frequently appears in "premium" and "AI product" contexts. Same rule applies.
|
|
151
|
+
|
|
152
|
+
- **Electric blue → teal** (hue range 190–240deg): common in tech startup contexts.
|
|
153
|
+
Fires `SLOP-GRADIENT` in these ranges at saturation above 70%.
|
|
154
|
+
|
|
155
|
+
The mesh gradient technique is not itself flagged — layered radial gradients at lower
|
|
156
|
+
saturation using the brand's actual hue values are expected and correct. The linter
|
|
157
|
+
fires on the specific hue bands above, not on mesh gradients per se. A mesh using
|
|
158
|
+
`hsl(30deg 40% 80%)` (warm sand) and `hsl(170deg 30% 75%)` (cool sage) is clean.
|
|
159
|
+
A mesh using `hsl(260deg 80% 60%)` and `hsl(310deg 70% 60%)` fires the rule.
|
|
160
|
+
|
|
161
|
+
**The test**: before committing a mesh gradient, read the HSL values of the dominant
|
|
162
|
+
stops. If both stops land in the flagged hue bands, record the brand reason or choose
|
|
163
|
+
a different hue combination. The mesh technique is sound; the hue selection is the risk.
|
|
164
|
+
|
|
165
|
+
## Do not combine with
|
|
166
|
+
|
|
167
|
+
**noise-grain-texture.md at opacity above 10%** — both techniques add depth signals to
|
|
168
|
+
the background. Grain over a mesh is appropriate at 3–6% opacity; above that, the
|
|
169
|
+
combined texture reads as a rendering artifact, not a design decision.
|
|
170
|
+
|
|
171
|
+
**svg-geometric-patterns.md behind the same element** — a pattern and a mesh applied
|
|
172
|
+
to the same background produce competing spatial readings (structured geometry and
|
|
173
|
+
ambient light) that cancel each other's signal.
|
|
174
|
+
|
|
175
|
+
**A mesh where text contrast cannot be verified at all mesh positions** — mesh gradients
|
|
176
|
+
are not uniform; the lightest point and the darkest point may differ enough that text
|
|
177
|
+
legible over one is illegible over the other. Always verify contrast at the gradient's
|
|
178
|
+
lightest zone for dark text and its darkest zone for light text.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Noise / grain texture
|
|
2
|
+
|
|
3
|
+
A grain texture overlays a fine noise pattern — generated by an SVG `feTurbulence` filter
|
|
4
|
+
embedded as a data URI — on a background at low opacity. The effect adds tactile quality
|
|
5
|
+
that flat fills and smooth gradients lack: the surface reads as material rather than
|
|
6
|
+
rendered, as though light is interacting with a slightly imperfect field. At the correct
|
|
7
|
+
opacity (3–8%), the grain reads as depth. Above 15% it reads as a compression artifact
|
|
8
|
+
or a low-resolution encoding error.
|
|
9
|
+
|
|
10
|
+
The technique was documented by Lea Verou and CSS-Tricks: "Grainy Gradients" —
|
|
11
|
+
`feTurbulence` as a composited texture layer, reducing gradient banding on smooth surfaces.
|
|
12
|
+
`expressive.md` cites this directly: "The CSS-Tricks SVG filter technique (an feTurbulence
|
|
13
|
+
filter composited at low opacity over a background) adds texture without weight."
|
|
14
|
+
|
|
15
|
+
## When it earns its place / When it does not
|
|
16
|
+
|
|
17
|
+
Condition: flat backgrounds or smooth mesh gradients that read as too digital. Grain most
|
|
18
|
+
visibly earns its place over mesh gradients, where it reduces the banding that appears on
|
|
19
|
+
smooth colour transitions on LCD displays. Single-colour sections that feel like developer
|
|
20
|
+
backgrounds — white, off-white, near-black — also benefit: the grain makes the surface a
|
|
21
|
+
material decision rather than a default. Showpiece and confident registers. Hero sections.
|
|
22
|
+
Feature backgrounds. Sections where the surface itself should read as considered.
|
|
23
|
+
|
|
24
|
+
Condition against: over photography or video — grain is redundant over natural image
|
|
25
|
+
texture and reduces legibility. On text at any size — grain under type at small sizes
|
|
26
|
+
degrades the contrast relationship and makes text harder to read. At high opacity on any
|
|
27
|
+
element — above 15%, grain reads as an encoding problem. Any product context where
|
|
28
|
+
surface cleanliness is a trust signal (healthcare, finance, enterprise tooling) — grain
|
|
29
|
+
introduces noise that reads as imprecision.
|
|
30
|
+
|
|
31
|
+
## Parameters
|
|
32
|
+
|
|
33
|
+
```css
|
|
34
|
+
:root {
|
|
35
|
+
/* Grain opacity.
|
|
36
|
+
3–5%: subtle, reads as material quality on close inspection.
|
|
37
|
+
6–8%: visible grain that reads as deliberate texture.
|
|
38
|
+
9–15%: strong texture, suitable for decorative elements only.
|
|
39
|
+
Above 15%: renders as compression artifact. Do not use. */
|
|
40
|
+
--grain-opacity: 0.05;
|
|
41
|
+
|
|
42
|
+
/* Turbulence frequency.
|
|
43
|
+
Low values (0.4–0.6): coarse grain, reads as film grain.
|
|
44
|
+
High values (0.7–0.9): fine grain, reads as paper texture.
|
|
45
|
+
Values above 1.0: noise too fine to read as texture — invisible. */
|
|
46
|
+
--grain-frequency: 0.65;
|
|
47
|
+
|
|
48
|
+
/* Number of octaves: detail layers in the noise function.
|
|
49
|
+
2: smooth, gentle grain.
|
|
50
|
+
4: more complex, film-like texture.
|
|
51
|
+
Values above 6: significant render cost; diminishing visual returns. */
|
|
52
|
+
--grain-octaves: 4;
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Implementation
|
|
57
|
+
|
|
58
|
+
The grain is an SVG filter rendered as a data URI and applied as a `::after` pseudo-element.
|
|
59
|
+
This approach requires no external file, no JavaScript, and works in all modern browsers.
|
|
60
|
+
|
|
61
|
+
```css
|
|
62
|
+
/* ── Grain texture as a pseudo-element overlay ────────────────────────────── */
|
|
63
|
+
.grain {
|
|
64
|
+
position: relative;
|
|
65
|
+
isolation: isolate; /* ensures the pseudo-element stacks correctly */
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.grain::after {
|
|
69
|
+
content: '';
|
|
70
|
+
position: absolute;
|
|
71
|
+
inset: 0;
|
|
72
|
+
z-index: 1; /* above the background, below the content */
|
|
73
|
+
pointer-events: none;
|
|
74
|
+
user-select: none;
|
|
75
|
+
opacity: var(--grain-opacity);
|
|
76
|
+
|
|
77
|
+
/* The SVG feTurbulence filter as a data URI.
|
|
78
|
+
fractalNoise: produces organic grain (vs turbulence which produces cloud-like noise).
|
|
79
|
+
seed: change this value to produce a different grain pattern on different sections. */
|
|
80
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23noise)'/%3E%3C/svg%3E");
|
|
81
|
+
background-size: 300px 300px; /* tile size: smaller = finer visual grain */
|
|
82
|
+
background-repeat: repeat;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* ── Content must sit above the grain pseudo-element ─────────────────────── */
|
|
86
|
+
/* Any direct children of .grain that hold readable content need z-index: 2
|
|
87
|
+
to layer above the ::after grain overlay. */
|
|
88
|
+
.grain > * {
|
|
89
|
+
position: relative;
|
|
90
|
+
z-index: 2;
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```html
|
|
95
|
+
<!-- Apply .grain to a section to add texture to its background. -->
|
|
96
|
+
<section class="mesh-bg grain">
|
|
97
|
+
<div class="section-content">
|
|
98
|
+
<h2>The surface is now material.</h2>
|
|
99
|
+
</div>
|
|
100
|
+
</section>
|
|
101
|
+
|
|
102
|
+
<!-- Or to a specific card: -->
|
|
103
|
+
<div class="card grain">
|
|
104
|
+
<h3>Card with grain texture.</h3>
|
|
105
|
+
</div>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Adjusting the grain:** To change the frequency or octave count without editing the data
|
|
109
|
+
URI, modify the SVG string in the background-image. The key parameters are `baseFrequency`
|
|
110
|
+
(mapped to `--grain-frequency`) and `numOctaves` (mapped to `--grain-octaves`). The data
|
|
111
|
+
URI must be regenerated when these values change — they are baked into the string, not
|
|
112
|
+
CSS variables. Consider a small build step or a Sass function if multiple grain densities
|
|
113
|
+
are needed.
|
|
114
|
+
|
|
115
|
+
**Performance:** The `feTurbulence` filter renders in the browser at paint time. On modern
|
|
116
|
+
hardware this is imperceptible. On older mobile hardware with large grain tiles, the
|
|
117
|
+
initial render may take 10–20ms. Tile the grain at 300×300px rather than applying it as
|
|
118
|
+
a full-viewport element to keep the painted area bounded. The `stitchTiles: stitch`
|
|
119
|
+
parameter ensures the tile seams are invisible at any scale.
|
|
120
|
+
|
|
121
|
+
## Linter notes
|
|
122
|
+
|
|
123
|
+
- Grain opacity above 15% (`--grain-opacity > 0.15`) is a defect. At this level the
|
|
124
|
+
texture reads as a rendering error rather than a design decision. The `omd check`
|
|
125
|
+
system cannot detect opacity set inside a CSS variable directly, but a visual check
|
|
126
|
+
of any rendered surface should reject opacity this high.
|
|
127
|
+
|
|
128
|
+
- Grain applied directly to text containers degrades legibility. The grain pseudo-element
|
|
129
|
+
must be layered below content (`z-index` of the pseudo-element below content children).
|
|
130
|
+
Verify by running `omd check` — the contrast checker measures effective contrast
|
|
131
|
+
between text and the painted background, including overlaid grain.
|
|
132
|
+
|
|
133
|
+
- A grain-only texture on a white background with no other design decisions is not a
|
|
134
|
+
texture choice — it is an avoidance of a background decision. Grain earns its place
|
|
135
|
+
as a layer over a considered colour, not as a substitute for one.
|
|
136
|
+
|
|
137
|
+
## Do not combine with
|
|
138
|
+
|
|
139
|
+
**gradient-mesh.md at grain opacity above 8%** — grain at 3–6% over a mesh gradient is
|
|
140
|
+
the canonical combination; it reduces banding and adds material quality. Above 8%, the
|
|
141
|
+
two textures begin to fight: the mesh's smooth colour transition and the grain's random
|
|
142
|
+
noise produce a muddy middle ground that reads as neither.
|
|
143
|
+
|
|
144
|
+
**drop-shadow on the same element** — grain texture and heavy box shadows are competing
|
|
145
|
+
depth signals. Both communicate material weight; using both says the element is both
|
|
146
|
+
thick with grain texture and elevated with shadow, which is contradictory. Choose one.
|
|
147
|
+
|
|
148
|
+
**photography or video backgrounds** — grain is a substitute for natural surface texture,
|
|
149
|
+
not a complement. Real photographic grain and synthetic `feTurbulence` grain at the same
|
|
150
|
+
opacity produce visual interference, not depth.
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Placeholder policy
|
|
2
|
+
|
|
3
|
+
A grey box is a design defect: it defers a decision the build must make before shipping,
|
|
4
|
+
and it ships as an admission that nobody decided what belongs here. A placeholder that
|
|
5
|
+
reaches production — as a grey rectangle, a "Coming soon" block, or a `background: #ccc`
|
|
6
|
+
div — is not a placeholder anymore. It is content that says the design is unfinished.
|
|
7
|
+
|
|
8
|
+
This recipe defines what to do instead. For every situation where a grey box would appear,
|
|
9
|
+
there is a deliberate alternative: a typographic block, a pattern fill, or a generated
|
|
10
|
+
gradient. Each carries more information about the intended content than a grey rectangle
|
|
11
|
+
and requires the designer to make a decision — which is the point.
|
|
12
|
+
|
|
13
|
+
## When it earns its place / When it does not
|
|
14
|
+
|
|
15
|
+
Condition: any section where final imagery is not available at build time. Team photos
|
|
16
|
+
not yet shot. Product screenshots not yet captured. User-generated content zones in an
|
|
17
|
+
empty state. Hero sections awaiting final photography. The placeholder policy applies
|
|
18
|
+
to all of these: each one must ship as a deliberate, designed alternative to the missing
|
|
19
|
+
image, not as a grey box.
|
|
20
|
+
|
|
21
|
+
The placeholder policy is a forcing function. Deciding what to use instead of the grey
|
|
22
|
+
box requires answering: "What does this space communicate when the real content is
|
|
23
|
+
absent?" A typographic block communicates the category and scale of the expected content.
|
|
24
|
+
A pattern fill communicates that the space is structural, not content-bearing. A gradient
|
|
25
|
+
communicates the colour register of the expected content.
|
|
26
|
+
|
|
27
|
+
Condition against: this policy does not apply to actual content loading states (skeleton
|
|
28
|
+
screens during data fetch are correct — see `core/theory/layout.md` on loading states).
|
|
29
|
+
The policy targets design-time decisions, not runtime states.
|
|
30
|
+
|
|
31
|
+
## Parameters
|
|
32
|
+
|
|
33
|
+
```css
|
|
34
|
+
:root {
|
|
35
|
+
/* Typography placeholder: the font size should approximate the heading or body
|
|
36
|
+
size of the expected content, so the layout holds its rhythm even without
|
|
37
|
+
the real content. */
|
|
38
|
+
--placeholder-type-size: var(--text-xl, 1.25rem);
|
|
39
|
+
--placeholder-type-color: var(--text-secondary, rgba(0,0,0,0.35));
|
|
40
|
+
|
|
41
|
+
/* Pattern placeholder: same parameters as svg-geometric-patterns.md.
|
|
42
|
+
Use the same pattern token so the placeholder and a deliberate pattern
|
|
43
|
+
application are visually consistent. */
|
|
44
|
+
--placeholder-pattern-opacity: 0.06;
|
|
45
|
+
|
|
46
|
+
/* Gradient placeholder: a single-hue gradient using the brand palette.
|
|
47
|
+
Not a generic grey → white; the brand colour in a light wash is more
|
|
48
|
+
informative and more finished than any grey value. */
|
|
49
|
+
--placeholder-gradient-start: hsl(var(--hue-brand, 220deg) 20% 95%);
|
|
50
|
+
--placeholder-gradient-end: hsl(var(--hue-brand, 220deg) 10% 88%);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Implementation
|
|
55
|
+
|
|
56
|
+
**Option 1: Typographic block** — for content zones that will hold a heading, a body
|
|
57
|
+
paragraph, or a feature statement. The typographic placeholder communicates scale and
|
|
58
|
+
category without pretending to be real content.
|
|
59
|
+
|
|
60
|
+
```html
|
|
61
|
+
<!-- Instead of: <div class="hero-image" style="background:#ccc; height:400px;"></div> -->
|
|
62
|
+
|
|
63
|
+
<!-- Use: a typographic placeholder that holds the layout and signals intent. -->
|
|
64
|
+
<div class="placeholder placeholder--type" role="img" aria-label="Hero image: [brief description of intended content]">
|
|
65
|
+
<span class="placeholder__label">Hero image</span>
|
|
66
|
+
<span class="placeholder__detail">Photography: [subject or concept]</span>
|
|
67
|
+
</div>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```css
|
|
71
|
+
.placeholder--type {
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-direction: column;
|
|
74
|
+
align-items: center;
|
|
75
|
+
justify-content: center;
|
|
76
|
+
/* Match the dimensions of the expected content exactly.
|
|
77
|
+
If the hero image is 100vw × 60vh, this placeholder is too. */
|
|
78
|
+
min-height: 60vh;
|
|
79
|
+
background-color: var(--placeholder-gradient-start);
|
|
80
|
+
background-image: linear-gradient(
|
|
81
|
+
135deg,
|
|
82
|
+
var(--placeholder-gradient-start),
|
|
83
|
+
var(--placeholder-gradient-end)
|
|
84
|
+
);
|
|
85
|
+
border-radius: var(--radius-section, 0);
|
|
86
|
+
gap: var(--space-2, 0.5rem);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.placeholder__label {
|
|
90
|
+
font-size: var(--placeholder-type-size);
|
|
91
|
+
font-weight: var(--weight-medium, 500);
|
|
92
|
+
color: var(--placeholder-type-color);
|
|
93
|
+
letter-spacing: 0.04em;
|
|
94
|
+
text-transform: uppercase;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.placeholder__detail {
|
|
98
|
+
font-size: var(--text-sm, 0.875rem);
|
|
99
|
+
color: var(--placeholder-type-color);
|
|
100
|
+
opacity: 0.7;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Option 2: Pattern fill** — for content zones that are structural (a card image slot,
|
|
105
|
+
a sidebar graphic zone) where the space needs visual weight but no textual label.
|
|
106
|
+
|
|
107
|
+
```html
|
|
108
|
+
<div class="placeholder placeholder--pattern" role="img" aria-label="[describe intended content]"></div>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```css
|
|
112
|
+
.placeholder--pattern {
|
|
113
|
+
/* Dimensions match the intended content zone. */
|
|
114
|
+
min-height: 200px;
|
|
115
|
+
background-color: var(--surface-subtle, #f5f5f3);
|
|
116
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Ccircle cx='12' cy='12' r='1' fill='rgba(0%2C0%2C0%2C0.12)'/%3E%3C/svg%3E");
|
|
117
|
+
background-size: 24px 24px;
|
|
118
|
+
border-radius: var(--radius-card, 8px);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Option 3: Generated gradient** — for hero and full-bleed sections where the missing
|
|
123
|
+
image is photographic and the gradient conveys the intended colour register.
|
|
124
|
+
|
|
125
|
+
```html
|
|
126
|
+
<section class="placeholder placeholder--gradient placeholder--hero" role="img" aria-label="[describe intended imagery]">
|
|
127
|
+
<div class="section-content">
|
|
128
|
+
<!-- Section content sits above the gradient placeholder background. -->
|
|
129
|
+
</div>
|
|
130
|
+
</section>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```css
|
|
134
|
+
.placeholder--gradient {
|
|
135
|
+
background-image: linear-gradient(
|
|
136
|
+
160deg,
|
|
137
|
+
var(--placeholder-gradient-start) 0%,
|
|
138
|
+
var(--placeholder-gradient-end) 100%
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.placeholder--hero {
|
|
143
|
+
min-height: 100svh;
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Decision rule:** choose by content type and visibility:
|
|
148
|
+
- The expected content is a heading or copy block → **typographic block**.
|
|
149
|
+
- The expected content is an image in a card, thumbnail, or secondary zone → **pattern fill**.
|
|
150
|
+
- The expected content is a full-bleed hero or section background photo → **generated gradient**.
|
|
151
|
+
- When in doubt between pattern and gradient: gradient, because it communicates the
|
|
152
|
+
colour register of the eventual photography.
|
|
153
|
+
|
|
154
|
+
## Linter notes
|
|
155
|
+
|
|
156
|
+
- A `background: #ccc`, `background: #d9d9d9`, `background: grey`, or any near-neutral
|
|
157
|
+
background on a container that has explicit height but no content is the signal the
|
|
158
|
+
`omd check` system identifies as a potential placeholder. The check looks for elements
|
|
159
|
+
with fixed-height, no semantic content, and a near-neutral background. If a legitimate
|
|
160
|
+
design element fires this check, record the reason with `omd decision`.
|
|
161
|
+
|
|
162
|
+
- Generated gradient placeholders that land in the SLOP-GRADIENT hue bands (indigo–violet,
|
|
163
|
+
purple–pink) should use the brand's actual hue variable (`var(--hue-brand)`) rather than
|
|
164
|
+
a hardcoded hue. The placeholder inherits the brand palette, so if the brand changes,
|
|
165
|
+
the placeholders update automatically.
|
|
166
|
+
|
|
167
|
+
- Every placeholder element must have a meaningful `aria-label` that describes the
|
|
168
|
+
intended content. A placeholder without an accessible description is both a design
|
|
169
|
+
defect and an accessibility failure — screen readers announce it as an unlabelled image.
|
|
170
|
+
|
|
171
|
+
## Do not combine with
|
|
172
|
+
|
|
173
|
+
**Actual content** — when the real image or content is available, use it. The placeholder
|
|
174
|
+
policy does not mean "use a gradient everywhere instead of photography." Photography,
|
|
175
|
+
illustration, or purposeful imagery always supersedes a placeholder when it exists.
|
|
176
|
+
|
|
177
|
+
**Lorem ipsum text** — text placeholders are governed by the same principle: `hand.agent.yaml`
|
|
178
|
+
states "Placeholder copy is a defect. 'Lorem ipsum' and 'Your content here' never ship."
|
|
179
|
+
A typographic block that serves as an image placeholder is acceptable; a paragraph of
|
|
180
|
+
lorem ipsum text is not. If body copy is absent, use structural whitespace or a typographic
|
|
181
|
+
label indicating the content category, never placeholder prose.
|
|
182
|
+
|
|
183
|
+
**More than one placeholder type in the same layout zone** — choosing between typographic,
|
|
184
|
+
pattern, and gradient means choosing one. Stacking a pattern fill and a gradient on the
|
|
185
|
+
same placeholder element produces a background system, not a placeholder decision.
|