@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,37 @@
|
|
|
1
|
+
import type { Invariants } from '../types.ts';
|
|
2
|
+
|
|
3
|
+
export interface DesignSignal { score: number; missing: string[] }
|
|
4
|
+
|
|
5
|
+
/** Below this, a page has made almost no visual decisions — a warning, not a gate. */
|
|
6
|
+
export const LOW_SIGNAL = 0.4;
|
|
7
|
+
|
|
8
|
+
const round = (value: number, dp: number): number => {
|
|
9
|
+
const factor = 10 ** dp;
|
|
10
|
+
return Math.round(value * factor) / factor;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Nine binary-ish component signals over an already-measured page. danluu.com has almost
|
|
15
|
+
* no design — no radii, no shadows, no motion, no tokens — so as a *visual* reference it
|
|
16
|
+
* teaches nothing; this catches that deterministically instead of relying on a model to notice.
|
|
17
|
+
*/
|
|
18
|
+
export function designSignal(inv: Invariants): DesignSignal {
|
|
19
|
+
const checks: [string, boolean][] = [
|
|
20
|
+
['radius', inv.radiusLadder.length >= 2], // at least two materials
|
|
21
|
+
['elevation', inv.elevationLevels >= 1],
|
|
22
|
+
['type', inv.typeScale.length >= 3], // a deliberate scale, not browser default
|
|
23
|
+
['weights', inv.weightLadder.length >= 2], // weight used as hierarchy
|
|
24
|
+
['motion', inv.motionDurations.length >= 1],
|
|
25
|
+
['tokens', inv.tokenCoverage >= 0.2],
|
|
26
|
+
['spacing', inv.spacingLadder.length >= 4], // a scale, not incidental values
|
|
27
|
+
['padding', inv.paddingWeight >= 4], // someone spaced things on purpose
|
|
28
|
+
// A dead-on-hover button is the classic tell of generated work; a reference that
|
|
29
|
+
// never varies on hover has nothing to teach about interaction states.
|
|
30
|
+
['interaction', inv.hoverCoverage >= 0.3],
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const missing = checks.filter(([, pass]) => !pass).map(([name]) => name);
|
|
34
|
+
const score = round(checks.reduce((sum, [, pass]) => sum + (pass ? 1 : 0), 0) / checks.length, 2);
|
|
35
|
+
|
|
36
|
+
return { score, missing };
|
|
37
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync, readdirSync, readFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { join, basename, resolve } from 'node:path';
|
|
3
|
+
import type { Invariants, Reference } from '../types.ts';
|
|
4
|
+
|
|
5
|
+
/** Backfills invariants written before typography/motion/interaction measurement existed. */
|
|
6
|
+
function withInvariantDefaults(invariants: Invariants | null | undefined): Invariants | null {
|
|
7
|
+
if (invariants == null) return null;
|
|
8
|
+
return {
|
|
9
|
+
...invariants,
|
|
10
|
+
typeScale: invariants.typeScale ?? [],
|
|
11
|
+
fontFamilies: invariants.fontFamilies ?? [],
|
|
12
|
+
weightLadder: invariants.weightLadder ?? [],
|
|
13
|
+
motionDurations: invariants.motionDurations ?? [],
|
|
14
|
+
easingVocab: invariants.easingVocab ?? [],
|
|
15
|
+
animatedShare: invariants.animatedShare ?? 0,
|
|
16
|
+
hoverCoverage: invariants.hoverCoverage ?? 0,
|
|
17
|
+
focusCoverage: invariants.focusCoverage ?? 0,
|
|
18
|
+
// Motion probe fields — absent on references captured before the live probe was added.
|
|
19
|
+
animatedProperties: invariants.animatedProperties ?? [],
|
|
20
|
+
hasReducedMotion: invariants.hasReducedMotion ?? false,
|
|
21
|
+
scrollChoreography: invariants.scrollChoreography ?? [],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const refsDir = (cwd: string): string => join(cwd, '.omd', 'refs');
|
|
26
|
+
|
|
27
|
+
/** Hostname of a URL, or the basename (without extension) of a file path. */
|
|
28
|
+
function hostPart(source: string): string {
|
|
29
|
+
try {
|
|
30
|
+
const url = new URL(source);
|
|
31
|
+
if (url.protocol === 'file:') return basename(url.pathname).replace(/\.[^.]+$/, '');
|
|
32
|
+
return url.hostname.replace(/^www\./, '');
|
|
33
|
+
} catch {
|
|
34
|
+
return basename(source).replace(/\.[^.]+$/, '');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function slugFor(ref: Pick<Reference, 'source' | 'component'>): string {
|
|
39
|
+
return `${hostPart(ref.source)}.${ref.component}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function saveRef(cwd: string, ref: Reference): string {
|
|
43
|
+
const dir = refsDir(cwd);
|
|
44
|
+
mkdirSync(dir, { recursive: true });
|
|
45
|
+
const path = join(dir, `${slugFor(ref)}.json`);
|
|
46
|
+
writeFileSync(path, `${JSON.stringify(ref, null, 2)}\n`);
|
|
47
|
+
return resolve(path);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Path of the scoped component screenshot for a reference (`omd ref add … --shot`). */
|
|
51
|
+
export function refImagePath(cwd: string, ref: Pick<Reference, 'source' | 'component'>): string {
|
|
52
|
+
return join(refsDir(cwd), `${slugFor(ref)}.png`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isReference(value: unknown): value is Partial<Reference> & Pick<Reference, 'source' | 'component'> {
|
|
56
|
+
return (
|
|
57
|
+
typeof value === 'object'
|
|
58
|
+
&& value !== null
|
|
59
|
+
&& typeof (value as Record<string, unknown>)['source'] === 'string'
|
|
60
|
+
&& typeof (value as Record<string, unknown>)['component'] === 'string'
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function loadRefs(cwd: string): Reference[] {
|
|
65
|
+
const dir = refsDir(cwd);
|
|
66
|
+
let files: string[];
|
|
67
|
+
try {
|
|
68
|
+
files = readdirSync(dir).filter((f) => f.endsWith('.json'));
|
|
69
|
+
} catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const refs: Reference[] = [];
|
|
74
|
+
for (const file of files) {
|
|
75
|
+
try {
|
|
76
|
+
const parsed: unknown = JSON.parse(readFileSync(join(dir, file), 'utf8'));
|
|
77
|
+
if (isReference(parsed)) {
|
|
78
|
+
refs.push({
|
|
79
|
+
source: parsed.source,
|
|
80
|
+
component: parsed.component,
|
|
81
|
+
kind: parsed.kind ?? 'page',
|
|
82
|
+
capturedAt: parsed.capturedAt ?? '',
|
|
83
|
+
...(parsed.selector !== undefined ? { selector: parsed.selector } : {}),
|
|
84
|
+
invariants: withInvariantDefaults(parsed.invariants),
|
|
85
|
+
principles: parsed.principles ?? [],
|
|
86
|
+
...(parsed.slopCount !== undefined ? { slopCount: parsed.slopCount } : {}),
|
|
87
|
+
...(parsed.origin !== undefined ? { origin: parsed.origin } : {}),
|
|
88
|
+
// energyCurve is absent on references captured before energy measurement was
|
|
89
|
+
// added — omit the key so downstream code can use `?? null` to detect absence.
|
|
90
|
+
...(parsed.energyCurve !== undefined ? { energyCurve: parsed.energyCurve } : {}),
|
|
91
|
+
// blueprint is absent on references captured before --blueprint was introduced.
|
|
92
|
+
// Omit the key so downstream code can check `ref.blueprint !== undefined`.
|
|
93
|
+
...(parsed.blueprint !== undefined ? { blueprint: parsed.blueprint } : {}),
|
|
94
|
+
// imagePath is absent on references captured before --shot was introduced.
|
|
95
|
+
...(parsed.imagePath !== undefined ? { imagePath: parsed.imagePath } : {}),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
// corrupt file: skip it, don't fail the run
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return refs.sort((a, b) => a.source.localeCompare(b.source) || a.component.localeCompare(b.component));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Appends principles to an existing reference. Throws if none exists: a principle without
|
|
108
|
+
* measurements is an opinion, not a record.
|
|
109
|
+
*/
|
|
110
|
+
export function addPrinciples(cwd: string, source: string, component: string, principles: string[]): void {
|
|
111
|
+
const path = join(refsDir(cwd), `${slugFor({ source, component })}.json`);
|
|
112
|
+
if (!existsSync(path)) {
|
|
113
|
+
throw new Error(`no reference found for ${source} (${component})`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const ref = JSON.parse(readFileSync(path, 'utf8')) as Reference;
|
|
117
|
+
for (const principle of principles) {
|
|
118
|
+
if (!ref.principles.includes(principle)) ref.principles.push(principle);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
writeFileSync(path, `${JSON.stringify(ref, null, 2)}\n`);
|
|
122
|
+
}
|