dreative 0.5.2 → 0.5.4

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.
@@ -33,6 +33,20 @@ function basePlan(tier = "award") {
33
33
  mechanisms: ["variable-type morph", "depth-stack", "drag-inertia", "mask-dissolve"],
34
34
  drivers: ["scroll", "pointer", "time"],
35
35
  },
36
+ motionComplexityBudget: {
37
+ heroMoments: [{ sectionId: "hero", reason: "The archival specimen becomes the navigation system and establishes the living-catalog concept." }],
38
+ calmSectionIds: [],
39
+ sharedLanguage: "Archival sheets fold, align, and become structural boundaries across the journey.",
40
+ deviceLimits: "Desktop uses the full pinned chapter while mobile shortens travel and removes continuous rendering.",
41
+ progressiveEnhancement: "Semantic catalog content renders first; masks and synchronized motion enhance it when supported.",
42
+ antiDefaultReview: {
43
+ basicMotionAssessment: "The key specimen changes role and geometry rather than only fading, sliding, or scaling.",
44
+ compositionHandoff: "The specimen edge becomes the next chapter's indexing rail instead of disappearing.",
45
+ visualStateChange: "Loose sheets become navigation, a research surface, and then the closing mark.",
46
+ conceptSpecificity: "The sheet behavior is derived from archival handling and would not fit an unrelated product.",
47
+ memorableMoment: "Dragging the specimen reorganizes the complete catalog into the next chapter.",
48
+ },
49
+ },
36
50
  pages: [
37
51
  {
38
52
  id: "home",
@@ -45,6 +59,19 @@ function basePlan(tier = "award") {
45
59
  layoutFamily: "archival-index",
46
60
  skills: ["ux", "mobile", "motion", "media"],
47
61
  interactions: ["drag specimen"],
62
+ motionTreatment: {
63
+ class: "transformational",
64
+ staticComposition: "A loose archival specimen sits beside the catalog index.",
65
+ startState: "The specimen is centered and isolated as the page's primary subject.",
66
+ endState: "The specimen unfolds into the structural rail for the next catalog chapter.",
67
+ changes: ["The specimen mask expands and its edge becomes the next chapter rail."],
68
+ pinnedElements: [],
69
+ handoff: "The specimen edge persists as the indexing rail in the following composition.",
70
+ purpose: "Turn passive archival viewing into active handling and establish continuity.",
71
+ mechanism: "SVG mask migration synchronized with a GSAP scroll timeline.",
72
+ mobile: "A shorter clip-path handoff preserves the specimen-to-rail transformation.",
73
+ reducedMotion: "Render the specimen already aligned with the rail and use a direct section cut.",
74
+ },
48
75
  mobile: "stacked specimen",
49
76
  fallback: "static catalog",
50
77
  verification: ["Signature responds to input"],
@@ -59,41 +86,59 @@ function basePlan(tier = "award") {
59
86
  };
60
87
  }
61
88
  function verification(...ids) {
89
+ const timelineStates = ["initial", "early", "mid-transition", "final", "handoff", "mobile", "reduced-motion"];
90
+ const timeline = timelineStates.map((timelineState) => ({
91
+ id: `timeline-${timelineState}`,
92
+ criterion: `Captured ${timelineState} visual state`,
93
+ status: "pass",
94
+ evidence: "Captured the production build at the planned visual state.",
95
+ timelineState,
96
+ proof: { timestamp: started, testedUrl: "http://localhost:3000", consoleErrorCount: 0 },
97
+ }));
62
98
  return {
63
99
  version: 1,
64
100
  generatedAt: started,
65
- evidence: ids.map((id) => ({
66
- id,
67
- criterion: id,
68
- status: "pass",
69
- evidence: "Captured runtime behavior in the production build.",
70
- proof: { timestamp: started, testedUrl: "http://localhost:3000", consoleErrorCount: 0 },
71
- })),
101
+ evidence: [
102
+ ...timeline,
103
+ ...ids.map((id) => ({
104
+ id,
105
+ criterion: id,
106
+ status: "pass",
107
+ evidence: "Captured runtime behavior in the production build.",
108
+ proof: { timestamp: started, testedUrl: "http://localhost:3000", consoleErrorCount: 0 },
109
+ })),
110
+ ],
111
+ refinement: {
112
+ inspectedAt: started,
113
+ findings: ["The mid-transition index was too compressed to read."],
114
+ changes: ["Extended the mask handoff and delayed the rail compression."],
115
+ evidenceIds: ["timeline-mid-transition"],
116
+ },
72
117
  };
73
118
  }
74
119
  test("valid default award plan", () => {
75
120
  assert.deepEqual(validateRuleControls(basePlan(), registry, reflex, verification()), []);
76
121
  });
77
- test("valid 3D substitution using spatial typography", () => {
122
+ test("valid key-asset substitution for a typography-only experience", () => {
78
123
  const plan = basePlan();
79
124
  plan.ruleExceptions = [
80
125
  {
81
- ruleId: "award.spatialSignature",
126
+ ruleId: "media.keyAssetTreatment",
82
127
  decision: "substituted",
83
128
  declaredAt: planned,
84
- reason: "The archival concept has no physical subject, so a product-like prop would misrepresent the material.",
85
- alternative: "A persistent variable-type specimen becomes index, spatial architecture, and interactive control across four chapters.",
86
- successCriteria: ["The specimen appears in three materially different roles", "Pointer and scroll input visibly reshape its depth"],
87
- evidenceIds: ["spatial-type-desktop", "spatial-type-mobile"],
129
+ reason: "The archive contains no raster key imagery; inventing photography would misrepresent the supplied typographic material.",
130
+ alternative: "A persistent variable-type specimen becomes index, spatial architecture, and interactive control across the complete journey.",
131
+ successCriteria: ["The specimen appears in materially different structural roles", "Pointer and scroll input visibly reshape the specimen"],
132
+ evidenceIds: ["type-system-desktop", "type-system-mobile"],
88
133
  },
89
134
  ];
90
- assert.deepEqual(validateRuleControls(plan, registry, reflex, verification("spatial-type-desktop", "spatial-type-mobile")), []);
135
+ assert.deepEqual(validateRuleControls(plan, registry, reflex, verification("type-system-desktop", "type-system-mobile")), []);
91
136
  });
92
- test("invalid vague 3D exception", () => {
137
+ test("invalid vague creative-rule exception", () => {
93
138
  const plan = basePlan();
94
139
  plan.ruleExceptions = [
95
140
  {
96
- ruleId: "award.spatialSignature",
141
+ ruleId: "media.keyAssetTreatment",
97
142
  decision: "substituted",
98
143
  declaredAt: planned,
99
144
  reason: "It did not fit",
@@ -135,11 +180,41 @@ test("valid development plan", () => {
135
180
  };
136
181
  assert.deepEqual(validateRuleControls(plan, registry, reflex, verification()), []);
137
182
  });
138
- test("invalid repeated-mechanism plan", () => {
183
+ test("invalid decorative-only expressive plan", () => {
139
184
  const plan = basePlan();
140
- plan.creativeStrategy = { path: "diversity", mechanisms: ["fade", "fade", "fade", "fade"], drivers: ["scroll", "scroll", "scroll"] };
185
+ plan.pages[0].sections[0].motionTreatment = {
186
+ ...plan.pages[0].sections[0].motionTreatment,
187
+ class: "decorative",
188
+ changes: ["The image fades upward and scales into view."],
189
+ mechanism: "CSS opacity, translateY, and scale transitions on scroll reveal.",
190
+ };
141
191
  const errors = validateRuleControls(plan, registry, reflex, verification());
142
- assert.ok(errors.some((error) => error.includes("distinct mechanisms")));
192
+ assert.ok(errors.some((error) => error.includes("decorative-only")));
193
+ });
194
+ test("expressive plan requires contextual motion budget", () => {
195
+ const plan = basePlan("expressive");
196
+ delete plan.motionComplexityBudget;
197
+ assert.ok(validateRuleControls(plan, registry, reflex, verification()).some((error) => error.includes("motionComplexityBudget")));
198
+ });
199
+ test("award verification requires timeline states and a refinement pass", () => {
200
+ const plan = basePlan();
201
+ const report = {
202
+ version: 1,
203
+ generatedAt: started,
204
+ evidence: [
205
+ {
206
+ id: "initial-only",
207
+ criterion: "Initial state",
208
+ status: "pass",
209
+ evidence: "Captured only the initial frame.",
210
+ timelineState: "initial",
211
+ proof: { timestamp: started, testedUrl: "http://localhost:3000", consoleErrorCount: 0 },
212
+ },
213
+ ],
214
+ };
215
+ const errors = validateRuleControls(plan, registry, reflex, report);
216
+ assert.ok(errors.some((error) => error.includes("mid-transition")));
217
+ assert.ok(errors.some((error) => error.includes("refinement pass")));
143
218
  });
144
219
  test("valid reflex-font justification", () => {
145
220
  const plan = basePlan("premium");