chaincss 2.1.39 → 2.2.0

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.
@@ -1,5 +1,7 @@
1
1
  // src/compiler/intent-engine.ts
2
+
2
3
  import type { CorrectionResult, HealMode, HealResult, IntentContext } from '../core/types.js';
4
+ import { detectIfPatterns, emitCSSIf } from './css-if-transpiler.js';
3
5
  export type { CorrectionResult, HealMode, HealResult, IntentContext };
4
6
 
5
7
  interface ValueCorrection { wrong: string; correct: string; confidence: number; }
@@ -13,6 +15,262 @@ const SEMANTIC_INTENTS: Array<{pattern: RegExp; handler: Function; description:
13
15
  { pattern: /^(rounded|round)$/i, handler: (v: string, ctx: any) => ({ original: v, property: ctx.property||'border-radius', corrected: '9999px', defaults: { borderRadius: '9999px' }, confidence: 0.8, intent: 'rounded-pill', explanation: '"rounded" -> border-radius: 9999px (pill)' }), description: 'rounded -> pill' },
14
16
  ];
15
17
 
18
+
19
+ // ============================================================================
20
+ // Layout Macros — High-level semantic intents
21
+ // These compile complex multi-property layouts from simple intent names
22
+ // ============================================================================
23
+
24
+ interface LayoutMacro {
25
+ name: string;
26
+ description: string;
27
+ properties: Record<string, string | number>;
28
+ defaults?: Record<string, string | number>;
29
+ mediaQueries?: Record<string, Record<string, any>>;
30
+ }
31
+
32
+ const LAYOUT_MACROS: Record<string, LayoutMacro> = {
33
+ stickyHeader: {
34
+ name: 'stickyHeader',
35
+ description: 'Sticky header with scroll shadow and entrance animation',
36
+ properties: {
37
+ position: 'sticky',
38
+ top: '0',
39
+ zIndex: '50',
40
+ backgroundColor: 'var(--header-bg, white)',
41
+ backdropFilter: 'blur(8px)',
42
+ borderBottom: '1px solid transparent',
43
+ },
44
+ defaults: {
45
+ '--header-bg': 'white',
46
+ '--header-shadow': '0 4px 12px rgba(0,0,0,0.1)',
47
+ },
48
+ mediaQueries: {
49
+ '(max-width: 768px)': {
50
+ padding: '12px 16px',
51
+ },
52
+ '(min-width: 769px)': {
53
+ padding: '16px 32px',
54
+ },
55
+ },
56
+ },
57
+
58
+ card: {
59
+ name: 'card',
60
+ description: 'Standard card container with hover lift effect',
61
+ properties: {
62
+ display: 'flex',
63
+ flexDirection: 'column',
64
+ borderRadius: '12px',
65
+ backgroundColor: 'var(--card-bg, white)',
66
+ boxShadow: '0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.08)',
67
+ transition: 'box-shadow 0.2s ease, transform 0.2s ease',
68
+ overflow: 'hidden',
69
+ },
70
+ defaults: {
71
+ '--card-bg': 'white',
72
+ '--card-hover-shadow': '0 10px 30px rgba(0,0,0,0.15)',
73
+ },
74
+ mediaQueries: {
75
+ '(hover: hover)': {
76
+ '&:hover': {
77
+ boxShadow: 'var(--card-hover-shadow)',
78
+ transform: 'translateY(-2px)',
79
+ },
80
+ },
81
+ },
82
+ },
83
+
84
+ hero: {
85
+ name: 'hero',
86
+ description: 'Full-width hero section with centered content',
87
+ properties: {
88
+ display: 'flex',
89
+ flexDirection: 'column',
90
+ justifyContent: 'center',
91
+ alignItems: 'center',
92
+ width: '100%',
93
+ minHeight: '60vh',
94
+ padding: '48px 24px',
95
+ textAlign: 'center',
96
+ },
97
+ defaults: {},
98
+ mediaQueries: {
99
+ '(max-width: 768px)': {
100
+ minHeight: '40vh',
101
+ padding: '32px 16px',
102
+ },
103
+ },
104
+ },
105
+
106
+ container: {
107
+ name: 'container',
108
+ description: 'Responsive centered container with max-width',
109
+ properties: {
110
+ width: '100%',
111
+ maxWidth: '1200px',
112
+ marginLeft: 'auto',
113
+ marginRight: 'auto',
114
+ paddingLeft: '16px',
115
+ paddingRight: '16px',
116
+ },
117
+ defaults: {},
118
+ mediaQueries: {
119
+ '(min-width: 768px)': {
120
+ paddingLeft: '24px',
121
+ paddingRight: '24px',
122
+ },
123
+ '(min-width: 1024px)': {
124
+ paddingLeft: '32px',
125
+ paddingRight: '32px',
126
+ },
127
+ },
128
+ },
129
+
130
+ center: {
131
+ name: 'center',
132
+ description: 'Absolute centering using flexbox',
133
+ properties: {
134
+ display: 'flex',
135
+ justifyContent: 'center',
136
+ alignItems: 'center',
137
+ },
138
+ defaults: {},
139
+ },
140
+
141
+ gridList: {
142
+ name: 'gridList',
143
+ description: 'Responsive grid list with auto-fit columns',
144
+ properties: {
145
+ display: 'grid',
146
+ gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))',
147
+ gap: '24px',
148
+ },
149
+ defaults: {},
150
+ mediaQueries: {
151
+ '(max-width: 640px)': {
152
+ gridTemplateColumns: '1fr',
153
+ gap: '16px',
154
+ },
155
+ },
156
+ },
157
+
158
+ sidebar: {
159
+ name: 'sidebar',
160
+ description: 'Two-column layout: sidebar + main content',
161
+ properties: {
162
+ display: 'grid',
163
+ gridTemplateColumns: '280px 1fr',
164
+ gap: '32px',
165
+ minHeight: '100vh',
166
+ },
167
+ defaults: {},
168
+ mediaQueries: {
169
+ '(max-width: 1024px)': {
170
+ gridTemplateColumns: '1fr',
171
+ gap: '24px',
172
+ },
173
+ },
174
+ },
175
+
176
+ pill: {
177
+ name: 'pill',
178
+ description: 'Pill-shaped element (fully rounded)',
179
+ properties: {
180
+ borderRadius: '9999px',
181
+ padding: '8px 20px',
182
+ display: 'inline-flex',
183
+ alignItems: 'center',
184
+ justifyContent: 'center',
185
+ },
186
+ defaults: {},
187
+ },
188
+
189
+ glass: {
190
+ name: 'glass',
191
+ description: 'Frosted glass morphism effect',
192
+ properties: {
193
+ backgroundColor: 'rgba(255, 255, 255, 0.1)',
194
+ backdropFilter: 'blur(16px)',
195
+ border: '1px solid rgba(255, 255, 255, 0.2)',
196
+ borderRadius: '16px',
197
+ },
198
+ defaults: {},
199
+ },
200
+
201
+ truncate: {
202
+ name: 'truncate',
203
+ description: 'Single-line text truncation with ellipsis',
204
+ properties: {
205
+ overflow: 'hidden',
206
+ textOverflow: 'ellipsis',
207
+ whiteSpace: 'nowrap',
208
+ },
209
+ defaults: {},
210
+ },
211
+
212
+ srOnly: {
213
+ name: 'srOnly',
214
+ description: 'Screen-reader only (visually hidden but accessible)',
215
+ properties: {
216
+ position: 'absolute',
217
+ width: '1px',
218
+ height: '1px',
219
+ padding: '0',
220
+ margin: '-1px',
221
+ overflow: 'hidden',
222
+ clip: 'rect(0, 0, 0, 0)',
223
+ whiteSpace: 'nowrap',
224
+ borderWidth: '0',
225
+ },
226
+ defaults: {},
227
+ },
228
+ };
229
+
230
+ // ============================================================================
231
+ // Layout Macro Resolver
232
+ // ============================================================================
233
+
234
+ function resolveLayoutMacro(name: string): LayoutMacro | null {
235
+ return LAYOUT_MACROS[name] || null;
236
+ }
237
+
238
+ function expandLayoutMacro(name: string): Record<string, any> | null {
239
+ const macro = resolveLayoutMacro(name);
240
+ if (!macro) return null;
241
+
242
+ // Start with properties
243
+ const result: Record<string, any> = { ...macro.properties };
244
+
245
+ // Merge defaults (CSS custom properties) into the result
246
+ if (macro.defaults) {
247
+ Object.assign(result, macro.defaults);
248
+ }
249
+
250
+ // Apply media queries as nested atRules
251
+ if (macro.mediaQueries) {
252
+ result.atRules = result.atRules || [];
253
+ for (const [query, props] of Object.entries(macro.mediaQueries)) {
254
+ result.atRules.push({
255
+ type: 'media',
256
+ query,
257
+ styles: props,
258
+ });
259
+ }
260
+ }
261
+
262
+ return result;
263
+ }
264
+
265
+ function getAvailableMacros(): string[] {
266
+ return Object.keys(LAYOUT_MACROS);
267
+ }
268
+
269
+ function getMacroDescription(name: string): string | null {
270
+ const macro = resolveLayoutMacro(name);
271
+ return macro?.description || null;
272
+ }
273
+
16
274
  const VALUE_CORRECTIONS: Record<string, ValueCorrection[]> = {
17
275
  'display': [{wrong:'flexbox',correct:'flex',confidence:0.95},{wrong:'inline-flexbox',correct:'inline-flex',confidence:0.95}],
18
276
  'position': [{wrong:'abs',correct:'absolute',confidence:0.9},{wrong:'rel',correct:'relative',confidence:0.9}],
@@ -101,12 +359,44 @@ export const intent = {
101
359
  },
102
360
  getCorrections(property: string): ValueCorrection[] { return VALUE_CORRECTIONS[property] || []; },
103
361
  explain(correction: CorrectionResult): string { return correction.explanation; },
104
- getKnownProperties(): string[] { return [...KNOWN_PROPERTIES]; },
362
+ cssIf: { detect: detectIfPatterns, emit: emitCSSIf },
105
363
  getIntents() { return SEMANTIC_INTENTS.map(r => ({ pattern: r.pattern.toString(), description: r.description })); },
364
+ getKnownProperties(): string[] { return [...KNOWN_PROPERTIES]; },
365
+
366
+ // Layout Macros
367
+ macro(name: string): Record<string, any> | null { return expandLayoutMacro(name); },
368
+ getMacros(): string[] { return getAvailableMacros(); },
369
+ getMacroDescription(name: string): string | null { return getMacroDescription(name); },
370
+ hasMacro(name: string): boolean { return name in LAYOUT_MACROS; },
371
+ /**
372
+ * Apply a layout macro to an existing styles object.
373
+ * Merges macro properties with user overrides.
374
+ */
375
+ applyMacro(name: string, overrides?: Record<string, any>): Record<string, any> | null {
376
+ const macro = expandLayoutMacro(name);
377
+ if (!macro) return null;
378
+ if (!overrides) return macro;
379
+ // Deep merge: user overrides win
380
+ const merged = { ...macro };
381
+ for (const [key, value] of Object.entries(overrides)) {
382
+ if (key === 'atRules' && Array.isArray(value) && Array.isArray(merged.atRules)) {
383
+ merged.atRules = [...merged.atRules, ...value];
384
+ } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
385
+ merged[key] = { ...(merged[key] || {}), ...value };
386
+ } else {
387
+ merged[key] = value;
388
+ }
389
+ }
390
+ return merged;
391
+ },
106
392
  };
107
393
 
108
394
  export const correct = intent.correct.bind(intent);
109
395
  export const heal = intent.heal.bind(intent);
110
396
  export const validate = intent.validate.bind(intent);
111
397
  export const getIntent = intent.getIntent.bind(intent);
398
+ export const macro = intent.macro.bind(intent);
399
+ export const applyMacro = intent.applyMacro.bind(intent);
400
+ export const getMacros = intent.getMacros.bind(intent);
401
+ export const hasMacro = intent.hasMacro.bind(intent);
112
402
  export default intent;
@@ -0,0 +1,284 @@
1
+ // src/compiler/scroll-timeline.ts
2
+ /**
3
+ * Scroll-Driven Animations Engine
4
+ *
5
+ * Compiles timeline-based animation descriptions into:
6
+ * 1. Native CSS scroll-timeline / view-timeline (Chromium 115+)
7
+ * 2. @supports fallback with JavaScript polyfill hint
8
+ *
9
+ * API inspiration: GSAP's ScrollTrigger, but compiles to 0kb JS.
10
+ */
11
+
12
+ // ============================================================================
13
+ // Types
14
+ // ============================================================================
15
+
16
+ export interface ScrollTimelineConfig {
17
+ /** Name of the timeline */
18
+ name: string;
19
+ /** What drives the timeline */
20
+ source: 'scroll' | 'view';
21
+ /** The scrollable element (default: nearest scrollable ancestor) */
22
+ scroller?: 'nearest' | 'root' | 'self' | string;
23
+ /** Scroll axis */
24
+ axis?: 'block' | 'inline' | 'x' | 'y';
25
+ /** For view timelines: when does the element enter/exit */
26
+ inset?: string | { start: string; end: string };
27
+ /** Timeline range (for view timelines) */
28
+ range?: 'cover' | 'contain' | 'entry' | 'exit' | 'entry-crossing' | 'exit-crossing' | string;
29
+ }
30
+
31
+ export interface KeyframeStep {
32
+ /** Percentage or keyword (e.g., '0%', 'from', 'to') */
33
+ offset: string;
34
+ /** CSS properties at this keyframe */
35
+ properties: Record<string, string | number>;
36
+ /** Easing for this segment */
37
+ easing?: string;
38
+ }
39
+
40
+ export interface ScrollAnimation {
41
+ /** Selector to animate */
42
+ selector: string;
43
+ /** Timeline configuration */
44
+ timeline: ScrollTimelineConfig;
45
+ /** Keyframes */
46
+ keyframes: KeyframeStep[];
47
+ /** Animation duration (maps to timeline range) */
48
+ duration?: string;
49
+ /** Fill mode */
50
+ fill?: 'none' | 'forwards' | 'backwards' | 'both';
51
+ /** Iteration count */
52
+ iterations?: number | 'infinite';
53
+ /** Delay before starting */
54
+ delay?: string;
55
+ }
56
+
57
+ export interface ScrollTimelineResult {
58
+ /** Native CSS for scroll-driven animation */
59
+ css: string;
60
+ /** The @keyframes name generated */
61
+ animationName: string;
62
+ /** The timeline name generated */
63
+ timelineName: string;
64
+ /** Fallback CSS for browsers without scroll-timeline */
65
+ fallback: string;
66
+ }
67
+
68
+ // ============================================================================
69
+ // Presets — common scroll animations
70
+ // ============================================================================
71
+
72
+ export const SCROLL_PRESETS: Record<string, ScrollAnimation> = {
73
+ fadeIn: {
74
+ selector: '',
75
+ timeline: { name: 'fade-in', source: 'view', range: 'entry' },
76
+ keyframes: [
77
+ { offset: '0%', properties: { opacity: '0', transform: 'translateY(20px)' } },
78
+ { offset: '100%', properties: { opacity: '1', transform: 'translateY(0)' } },
79
+ ],
80
+ },
81
+ fadeOut: {
82
+ selector: '',
83
+ timeline: { name: 'fade-out', source: 'view', range: 'exit' },
84
+ keyframes: [
85
+ { offset: '0%', properties: { opacity: '1' } },
86
+ { offset: '100%', properties: { opacity: '0' } },
87
+ ],
88
+ },
89
+ scaleIn: {
90
+ selector: '',
91
+ timeline: { name: 'scale-in', source: 'view', range: 'entry' },
92
+ keyframes: [
93
+ { offset: '0%', properties: { opacity: '0', transform: 'scale(0.8)' } },
94
+ { offset: '100%', properties: { opacity: '1', transform: 'scale(1)' } },
95
+ ],
96
+ },
97
+ slideLeft: {
98
+ selector: '',
99
+ timeline: { name: 'slide-left', source: 'view', range: 'entry' },
100
+ keyframes: [
101
+ { offset: '0%', properties: { opacity: '0', transform: 'translateX(-40px)' } },
102
+ { offset: '100%', properties: { opacity: '1', transform: 'translateX(0)' } },
103
+ ],
104
+ },
105
+ slideRight: {
106
+ selector: '',
107
+ timeline: { name: 'slide-right', source: 'view', range: 'entry' },
108
+ keyframes: [
109
+ { offset: '0%', properties: { opacity: '0', transform: 'translateX(40px)' } },
110
+ { offset: '100%', properties: { opacity: '1', transform: 'translateX(0)' } },
111
+ ],
112
+ },
113
+ parallax: {
114
+ selector: '',
115
+ timeline: { name: 'parallax', source: 'scroll', scroller: 'root' },
116
+ keyframes: [
117
+ { offset: '0%', properties: { transform: 'translateY(0)' } },
118
+ { offset: '100%', properties: { transform: 'translateY(-20%)' } },
119
+ ],
120
+ },
121
+ stickyReveal: {
122
+ selector: '',
123
+ timeline: { name: 'sticky-reveal', source: 'view', range: 'contain' },
124
+ keyframes: [
125
+ { offset: '0%', properties: { opacity: '0', clipPath: 'inset(0 0 100% 0)' } },
126
+ { offset: '50%', properties: { opacity: '1', clipPath: 'inset(0 0 0% 0)' } },
127
+ { offset: '100%', properties: { opacity: '1', clipPath: 'inset(0 0 0% 0)' } },
128
+ ],
129
+ },
130
+ };
131
+
132
+ // ============================================================================
133
+ // Core Compiler
134
+ // ============================================================================
135
+
136
+ let animCounter = 0;
137
+
138
+ function generateName(prefix: string): string {
139
+ return prefix + '-' + (animCounter++).toString(36);
140
+ }
141
+
142
+ /**
143
+ * Compile a scroll animation into CSS.
144
+ *
145
+ * Output includes:
146
+ * - @keyframes definition
147
+ * - animation-timeline property
148
+ * - animation-range (for view timelines)
149
+ * - @supports fallback for older browsers
150
+ */
151
+ export function compileScrollAnimation(animation: ScrollAnimation): ScrollTimelineResult {
152
+ const animName = animation.timeline.name || generateName('scroll-anim');
153
+ const timelineName = '--' + animName + '-tl';
154
+
155
+ let css = '';
156
+
157
+ // 1. Define the scroll timeline
158
+ css += '/* Scroll Timeline: ' + animName + ' */\n';
159
+
160
+ if (animation.timeline.source === 'view') {
161
+ // view-timeline
162
+ const range = animation.timeline.range || 'entry';
163
+ css += animation.selector + ' {\n';
164
+ css += ' view-timeline-name: ' + timelineName + ';\n';
165
+ css += ' view-timeline-axis: ' + (animation.timeline.axis || 'block') + ';\n';
166
+ if (animation.timeline.inset) {
167
+ const inset = typeof animation.timeline.inset === 'string'
168
+ ? animation.timeline.inset
169
+ : animation.timeline.inset.start + ' ' + animation.timeline.inset.end;
170
+ css += ' view-timeline-inset: ' + inset + ';\n';
171
+ }
172
+ css += '}\n\n';
173
+ } else {
174
+ // scroll-timeline
175
+ const scroller = animation.timeline.scroller === 'root' ? 'root'
176
+ : animation.timeline.scroller === 'self' ? 'self'
177
+ : animation.timeline.scroller || 'nearest';
178
+ css += animation.selector + ' {\n';
179
+ if (scroller === 'root' || scroller === 'self' || scroller === 'nearest') {
180
+ css += ' scroll-timeline-name: ' + timelineName + ';\n';
181
+ css += ' scroll-timeline-axis: ' + (animation.timeline.axis || 'block') + ';\n';
182
+ }
183
+ css += '}\n\n';
184
+ }
185
+
186
+ // 2. Define @keyframes
187
+ css += '@keyframes ' + animName + ' {\n';
188
+ for (const step of animation.keyframes) {
189
+ css += ' ' + step.offset + ' {\n';
190
+ for (const [prop, value] of Object.entries(step.properties)) {
191
+ const kebabProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
192
+ css += ' ' + kebabProp + ': ' + value + ';\n';
193
+ }
194
+ css += ' }\n';
195
+ }
196
+ css += '}\n\n';
197
+
198
+ // 3. Apply animation to target
199
+ const targetSelector = animation.selector + ' > *' || animation.selector;
200
+ css += '/* Apply animation to children */\n';
201
+ css += targetSelector + ' {\n';
202
+ css += ' animation: ' + animName + ' linear both;\n';
203
+ css += ' animation-timeline: ' + timelineName + ';\n';
204
+ if (animation.timeline.source === 'view') {
205
+ const range = animation.timeline.range || 'entry';
206
+ css += ' animation-range: ' + range + ';\n';
207
+ }
208
+ if (animation.delay) css += ' animation-delay: ' + animation.delay + ';\n';
209
+ css += '}\n\n';
210
+
211
+ // 4. @supports fallback
212
+ css += '/* Fallback for browsers without scroll-timeline */\n';
213
+ css += '@supports not (animation-timeline: scroll()) {\n';
214
+ css += ' ' + targetSelector + ' {\n';
215
+ css += ' /* Use JS polyfill: https://github.com/flackr/scroll-timeline */\n';
216
+ css += ' animation: ' + animName + ' 1s ease both;\n';
217
+ css += ' }\n';
218
+ css += '}\n';
219
+
220
+ return {
221
+ css,
222
+ animationName: animName,
223
+ timelineName,
224
+ fallback: '',
225
+ };
226
+ }
227
+
228
+ /**
229
+ * Compile multiple scroll animations for a page.
230
+ */
231
+ export function compileScrollAnimations(animations: ScrollAnimation[]): string {
232
+ let css = '/* ============================================================\n';
233
+ css += ' ChainCSS Scroll-Driven Animations\n';
234
+ css += ' Generated: ' + new Date().toISOString() + '\n';
235
+ css += ' ============================================================ */\n\n';
236
+
237
+ for (const animation of animations) {
238
+ const result = compileScrollAnimation(animation);
239
+ css += result.css;
240
+ }
241
+
242
+ return css;
243
+ }
244
+
245
+ /**
246
+ * Create a scroll animation from a preset.
247
+ */
248
+ export function createScrollAnimation(
249
+ preset: keyof typeof SCROLL_PRESETS,
250
+ selector: string,
251
+ overrides?: Partial<ScrollAnimation>
252
+ ): ScrollAnimation {
253
+ const base = SCROLL_PRESETS[preset];
254
+ if (!base) throw new Error('Unknown scroll preset: ' + preset);
255
+
256
+ return {
257
+ ...base,
258
+ selector,
259
+ timeline: { ...base.timeline, ...overrides?.timeline },
260
+ keyframes: overrides?.keyframes || base.keyframes,
261
+ ...overrides,
262
+ };
263
+ }
264
+
265
+ /**
266
+ * Get available scroll animation presets.
267
+ */
268
+ export function getScrollPresets(): string[] {
269
+ return Object.keys(SCROLL_PRESETS);
270
+ }
271
+
272
+ // ============================================================================
273
+ // Exports
274
+ // ============================================================================
275
+
276
+ export const scrollTimeline = {
277
+ compile: compileScrollAnimation,
278
+ compileAll: compileScrollAnimations,
279
+ create: createScrollAnimation,
280
+ presets: SCROLL_PRESETS,
281
+ getPresets: getScrollPresets,
282
+ };
283
+
284
+ export default scrollTimeline;
package/src/index.ts CHANGED
@@ -162,6 +162,40 @@ export type { BreakpointsMap, ResponsiveStyle } from './compiler/breakpoints.js'
162
162
  // ============================================================================
163
163
  export const VERSION = '3.0.0';
164
164
 
165
+ // ============================================================================
166
+ // Default Export - Keep original chain for backward compatibility
167
+ // ============================================================================
168
+ // ============================================================================
169
+ // 🆕 Design System Orchestrator (v2.3)
170
+ // ============================================================================
171
+ export {
172
+ orchestrator,
173
+ contrastRatio,
174
+ checkContrast,
175
+ auditContrast,
176
+ createContextualToken,
177
+ resolveContextual,
178
+ generateContextualCSS,
179
+ validateTokenRelationships,
180
+ } from './compiler/design-orchestrator.js';
181
+ export type { ContrastResult, ContrastReport, ContextualToken, TokenContext } from './compiler/design-orchestrator.js';
182
+
183
+ // ============================================================================
184
+ // Default Export - Keep original chain for backward compatibility
185
+ // ============================================================================
186
+ // ============================================================================
187
+ // 🆕 Scroll Timeline Engine (v2.3)
188
+ // ============================================================================
189
+ export {
190
+ scrollTimeline,
191
+ compileScrollAnimation,
192
+ compileScrollAnimations,
193
+ createScrollAnimation,
194
+ getScrollPresets,
195
+ SCROLL_PRESETS,
196
+ } from './compiler/scroll-timeline.js';
197
+ export type { ScrollTimelineConfig, ScrollAnimation, ScrollTimelineResult, KeyframeStep } from './compiler/scroll-timeline.js';
198
+
165
199
  // ============================================================================
166
200
  // Default Export - Keep original chain for backward compatibility
167
201
  // ============================================================================