@3xhaust/oh-my-design 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.ko.md +282 -0
  3. package/README.md +282 -0
  4. package/adapters/build.ts +184 -0
  5. package/adapters/claude.ts +111 -0
  6. package/adapters/codex.ts +68 -0
  7. package/adapters/tokens.ts +21 -0
  8. package/adapters/tool-map.json +7 -0
  9. package/bin/omd-install.ts +76 -0
  10. package/bin/omd.ts +1774 -0
  11. package/core/asset-sourcing/index.ts +187 -0
  12. package/core/coach/index.ts +98 -0
  13. package/core/composition/app-shell-workbench.md +168 -0
  14. package/core/composition/asymmetric-diagonal-grid.md +178 -0
  15. package/core/composition/bento-grid.md +223 -0
  16. package/core/composition/editorial-index-labels.md +173 -0
  17. package/core/composition/form-wizard-stepper.md +132 -0
  18. package/core/composition/master-detail-flow.md +129 -0
  19. package/core/composition/section-inversion.md +182 -0
  20. package/core/composition/sidebar-margin-annotation.md +184 -0
  21. package/core/composition/split-screen-hero.md +224 -0
  22. package/core/composition/sticky-sidebar-scroll.md +219 -0
  23. package/core/composition/typographic-hero.md +100 -0
  24. package/core/composition-contract/index.ts +233 -0
  25. package/core/composition-contract/visual-richness.ts +101 -0
  26. package/core/config/index.ts +27 -0
  27. package/core/copy/index.ts +296 -0
  28. package/core/craft/finish-pass.md +356 -0
  29. package/core/craft/index.ts +33 -0
  30. package/core/design/index.ts +581 -0
  31. package/core/design/interaction-states.ts +189 -0
  32. package/core/eval-harness/index.ts +225 -0
  33. package/core/evidence/final.ts +444 -0
  34. package/core/evidence/task.ts +459 -0
  35. package/core/figma/client.ts +312 -0
  36. package/core/figma/diff.ts +261 -0
  37. package/core/figma/responsive.ts +310 -0
  38. package/core/figma/system.ts +466 -0
  39. package/core/figma/types.ts +185 -0
  40. package/core/frame/check-ux.ts +180 -0
  41. package/core/frame/index.ts +39 -0
  42. package/core/frame/write.ts +168 -0
  43. package/core/graphics/css-illustration-primitives.md +189 -0
  44. package/core/graphics/duotone-image-presets.md +173 -0
  45. package/core/graphics/gradient-mesh.md +178 -0
  46. package/core/graphics/noise-grain-texture.md +150 -0
  47. package/core/graphics/placeholder-policy.md +185 -0
  48. package/core/graphics/svg-geometric-patterns.md +145 -0
  49. package/core/history/index.ts +39 -0
  50. package/core/install/detect.ts +32 -0
  51. package/core/install/install.ts +321 -0
  52. package/core/install/patch-claude.ts +109 -0
  53. package/core/install/patch-codex.ts +82 -0
  54. package/core/interaction/index.ts +101 -0
  55. package/core/interaction/recipes/signature-lighting.md +203 -0
  56. package/core/ir/dom.ts +352 -0
  57. package/core/ir/normalize.ts +177 -0
  58. package/core/motion/easing.md +151 -0
  59. package/core/motion/energy.ts +188 -0
  60. package/core/motion/recipes/image-hover-distortion.md +198 -0
  61. package/core/motion/recipes/magnetic-hover.md +229 -0
  62. package/core/motion/recipes/marquee.md +202 -0
  63. package/core/motion/recipes/number-counter.md +242 -0
  64. package/core/motion/recipes/page-loader.md +240 -0
  65. package/core/motion/recipes/parallax.md +233 -0
  66. package/core/motion/recipes/scroll-reveal.md +210 -0
  67. package/core/motion/recipes/section-color-inversion.md +217 -0
  68. package/core/motion/recipes/split-text-entrance.md +222 -0
  69. package/core/motion/recipes/stagger-orchestrator.md +227 -0
  70. package/core/motion/recipes/sticky-scene-transition.md +246 -0
  71. package/core/motion/recipes/view-transitions.md +239 -0
  72. package/core/probe/index.ts +186 -0
  73. package/core/probe/schema.json +46 -0
  74. package/core/protocol/composition-contract.md +192 -0
  75. package/core/protocol/copy-deck.md +96 -0
  76. package/core/protocol/human-design-loop.md +447 -0
  77. package/core/protocol/slop-review.md +71 -0
  78. package/core/ref/batch.ts +94 -0
  79. package/core/ref/blueprint.ts +199 -0
  80. package/core/ref/distance.ts +225 -0
  81. package/core/ref/invariants.ts +162 -0
  82. package/core/ref/signal.ts +37 -0
  83. package/core/ref/store.ts +122 -0
  84. package/core/render/index.ts +607 -0
  85. package/core/rules/attribution.ts +159 -0
  86. package/core/rules/builtin/contrast.yaml +8 -0
  87. package/core/rules/builtin/focus.yaml +8 -0
  88. package/core/rules/builtin/hit-area.yaml +8 -0
  89. package/core/rules/builtin/ko.yaml +6 -0
  90. package/core/rules/builtin/motion.yaml +80 -0
  91. package/core/rules/builtin/slop.yaml +365 -0
  92. package/core/rules/builtin/spacing.yaml +9 -0
  93. package/core/rules/builtin/system.yaml +42 -0
  94. package/core/rules/builtin/token.yaml +8 -0
  95. package/core/rules/builtin/ux.yaml +125 -0
  96. package/core/rules/engine.ts +88 -0
  97. package/core/rules/leakage.ts +72 -0
  98. package/core/rules/motion-spec.ts +204 -0
  99. package/core/site/index.ts +100 -0
  100. package/core/slop/index.ts +427 -0
  101. package/core/slop/text-slop.ts +220 -0
  102. package/core/source-seal/index.ts +174 -0
  103. package/core/stack/index.ts +53 -0
  104. package/core/target/index.ts +183 -0
  105. package/core/theory/color.md +285 -0
  106. package/core/theory/components.md +272 -0
  107. package/core/theory/craft.md +270 -0
  108. package/core/theory/expressive.md +213 -0
  109. package/core/theory/imagegen.md +105 -0
  110. package/core/theory/layout.md +159 -0
  111. package/core/theory/motion.md +283 -0
  112. package/core/theory/typography.md +134 -0
  113. package/core/theory/ux.md +580 -0
  114. package/core/theory/voice.md +595 -0
  115. package/core/types.ts +684 -0
  116. package/package.json +40 -0
  117. package/src/agents/composer.agent.yaml +174 -0
  118. package/src/agents/eye.agent.yaml +208 -0
  119. package/src/agents/framer.agent.yaml +52 -0
  120. package/src/agents/glance.agent.yaml +19 -0
  121. package/src/agents/hand.agent.yaml +200 -0
  122. package/src/agents/scout.agent.yaml +71 -0
  123. package/src/agents/sketch.agent.yaml +64 -0
  124. package/src/agents/typesetter.agent.yaml +52 -0
  125. package/src/agents/writer.agent.yaml +63 -0
  126. package/src/skills/omd-coach/SKILL.md +40 -0
  127. package/src/skills/omd-critique/SKILL.md +77 -0
  128. package/src/skills/omd-figma/SKILL.md +256 -0
  129. package/src/skills/omd-humanize/SKILL.md +160 -0
  130. package/src/skills/omd-scout/SKILL.md +77 -0
  131. package/src/skills/omd-ultradesign/SKILL.md +521 -0
@@ -0,0 +1,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
+ }