@glissade/lottie 0.50.0-pre.0 → 0.51.0-pre.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.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as _glissade_scene0 from "@glissade/scene";
2
2
  import { Node, SceneModule } from "@glissade/scene";
3
- import { PathContour, PathValue, Timeline, Vec2 } from "@glissade/core";
3
+ import { Paint, PathContour, PathValue, Timeline, Vec2 } from "@glissade/core";
4
4
 
5
5
  //#region src/spec.d.ts
6
6
 
@@ -19,7 +19,8 @@ interface GroupSpec extends BaseSpec {
19
19
  interface PathSpec extends BaseSpec {
20
20
  kind: 'path';
21
21
  data: PathValue;
22
- fill?: string;
22
+ /** A CSS color string, or a linear/radial gradient `Paint` (imported from `gf`). */
23
+ fill?: string | Paint;
23
24
  stroke?: string;
24
25
  strokeWidth?: number;
25
26
  }
@@ -102,6 +103,16 @@ interface LottieShapePathData {
102
103
  o: number[][];
103
104
  c?: boolean;
104
105
  }
106
+ /**
107
+ * A gradient color ramp (`gf`/`gs` `.g`): `p` = the number of COLOR stops, `k`
108
+ * the animatable flattened array. Static `k` is `[offset,r,g,b, …]` (p color
109
+ * stops, 0–1 floats) optionally followed by `[offset,a, …]` alpha stops when any
110
+ * stop is translucent; the split point is `p*4`.
111
+ */
112
+ interface LottieGradient {
113
+ p: number;
114
+ k: LottieProp;
115
+ }
105
116
  interface LottieShapeItem {
106
117
  /** shape direction: 3 = reversed winding (el/rc). */
107
118
  d?: number | {
@@ -115,11 +126,11 @@ interface LottieShapeItem {
115
126
  /** sh */
116
127
  ks?: LottieProp;
117
128
  closed?: boolean;
118
- /** el / rc / tr */
129
+ /** el / rc / tr — also gf/gs gradient START point (s) + highlight angle (a). */
119
130
  p?: LottieProp;
120
131
  s?: LottieProp;
121
132
  a?: LottieProp;
122
- /** rc corner radius / fl-st opacity-adjacent fields */
133
+ /** rc corner radius / fl-st opacity-adjacent fields / gf-gs fill rule. */
123
134
  r?: LottieProp | number;
124
135
  /** fl / st */
125
136
  c?: LottieProp;
@@ -127,6 +138,11 @@ interface LottieShapeItem {
127
138
  w?: LottieProp;
128
139
  /** mm */
129
140
  mm?: number;
141
+ /** gf / gs gradient: type (1 linear, 2 radial), END point, highlight length, color ramp. */
142
+ t?: number;
143
+ e?: LottieProp;
144
+ h?: LottieProp;
145
+ g?: LottieGradient;
130
146
  }
131
147
  interface LottieTransform {
132
148
  a?: LottieProp;
package/dist/index.js CHANGED
@@ -289,7 +289,6 @@ const UNSUPPORTED_SHAPE_ITEMS = {
289
289
  rp: "repeater",
290
290
  rd: "round corners",
291
291
  sr: "polystar",
292
- gf: "gradient fill",
293
292
  gs: "gradient stroke",
294
293
  zz: "zig-zag",
295
294
  op: "offset path",
@@ -337,6 +336,20 @@ function auditShapeItems(ctx, items, where) {
337
336
  checkExpression(ctx, item.o, `${here}.o`);
338
337
  checkExpression(ctx, item.w, `${here}.w`);
339
338
  break;
339
+ case "gf": {
340
+ if (sawGroup) reject(ctx, "unsupported-shape-structure", `style inheriting into a preceding group at ${here}`);
341
+ if (typeof item.r === "number" && item.r === 2) reject(ctx, "unsupported-fill-rule", `even-odd gradient fill at ${here}`);
342
+ if (item.t !== 1 && item.t !== 2) reject(ctx, "unsupported-gradient", `gradient type ${String(item.t)} (only linear=1 / radial=2) at ${here}`);
343
+ if (!isProp(item.s) || !isProp(item.e)) reject(ctx, "invalid-gradient", `${here}: gradient fill missing start/end point (s/e)`);
344
+ const g = item.g;
345
+ if (!g || typeof g.p !== "number" || g.p < 1 || !isProp(g.k)) reject(ctx, "invalid-gradient", `${here}: gradient fill missing color stops (g.p/g.k)`);
346
+ for (const [name, prop] of [["h", item.h], ["a", item.a]]) if (isProp(prop) && !isKeyframed(prop) && typeof prop.k === "number" && Math.abs(prop.k) > 1e-9) reject(ctx, "unsupported-gradient", `radial highlight (${name}) at ${here}`);
347
+ checkExpression(ctx, item.s, `${here}.s`);
348
+ checkExpression(ctx, item.e, `${here}.e`);
349
+ checkExpression(ctx, item.o, `${here}.o`);
350
+ checkExpression(ctx, g?.k, `${here}.g`);
351
+ break;
352
+ }
340
353
  case "mm": {
341
354
  const mode = item.mm ?? 1;
342
355
  if (mode !== 1) degrade(ctx, "unsupported-shape-modifier", `merge paths mode ${mode} at ${here}`);
@@ -884,6 +897,112 @@ function mergeGeomSources(ctx, sources, tm, where) {
884
897
  keys: enforceMonotonic(keys)
885
898
  };
886
899
  }
900
+ /** Lottie gf color ramp (`g.k`) → glissade ColorStop[]. The first `p` groups of
901
+ * `[offset,r,g,b]` are colors (0–1 floats); trailing `[offset,a]` groups are alpha
902
+ * stops, merged onto each color offset (interpolated across the alpha ramp). */
903
+ function gradientStops(k, p) {
904
+ const alphas = [];
905
+ for (let i = p * 4; i + 1 < k.length; i += 2) alphas.push({
906
+ offset: k[i],
907
+ a: k[i + 1]
908
+ });
909
+ const alphaAt = (offset) => {
910
+ if (alphas.length === 0) return 1;
911
+ let lo = alphas[0];
912
+ if (offset <= lo.offset) return lo.a;
913
+ for (let j = 1; j < alphas.length; j++) {
914
+ const hi = alphas[j];
915
+ if (offset <= hi.offset) {
916
+ const span = hi.offset - lo.offset;
917
+ return span > 0 ? lo.a + (hi.a - lo.a) * (offset - lo.offset) / span : hi.a;
918
+ }
919
+ lo = hi;
920
+ }
921
+ return lo.a;
922
+ };
923
+ const stops = [];
924
+ for (let i = 0; i < p; i++) {
925
+ const o = i * 4;
926
+ const offset = k[o] ?? 0;
927
+ stops.push({
928
+ offset,
929
+ color: formatColor({
930
+ r: (k[o + 1] ?? 0) * 255,
931
+ g: (k[o + 2] ?? 0) * 255,
932
+ b: (k[o + 3] ?? 0) * 255,
933
+ a: alphaAt(offset)
934
+ })
935
+ });
936
+ }
937
+ return stops;
938
+ }
939
+ /** Reconstruct a static Paint from a gf's start/end points + ramp — the inverse of
940
+ * export.ts's gradientStart/gradientEnd (radial: centre = s, radius = |s→e|). */
941
+ function buildStaticPaint(t, s, e, k, p) {
942
+ const stops = gradientStops(k, p);
943
+ if (t === 2) return {
944
+ kind: "radial",
945
+ stops,
946
+ center: [s[0], s[1]],
947
+ radius: Math.hypot(e[0] - s[0], e[1] - s[1])
948
+ };
949
+ return {
950
+ kind: "linear",
951
+ stops,
952
+ from: [s[0], s[1]],
953
+ to: [e[0], e[1]]
954
+ };
955
+ }
956
+ /** Set `spec.fill` to a linear/radial Paint (static) or push a `paint` track (animated). */
957
+ function applyGradientFill(ctx, spec, style, tm) {
958
+ const t = style.t;
959
+ const p = style.g?.p ?? 0;
960
+ const sNorm = norms(style.s);
961
+ const eNorm = norms(style.e);
962
+ const gNorm = norms(style.g?.k);
963
+ const staticS = () => vec2Of(style.s?.k ?? [0, 0]);
964
+ const staticE = () => vec2Of(style.e?.k ?? [0, 0]);
965
+ const staticG = () => Array.isArray(style.g?.k.k) ? style.g.k.k : [];
966
+ if (!sNorm && !eNorm && !gNorm) {
967
+ spec.fill = buildStaticPaint(t, staticS(), staticE(), staticG(), p);
968
+ return;
969
+ }
970
+ const channels = [
971
+ sNorm,
972
+ eNorm,
973
+ gNorm
974
+ ].filter((n) => n !== void 0);
975
+ const first = channels[0];
976
+ const aligned = channels.every((n) => n.length === first.length && n.every((kk, j) => kk.t === first[j].t));
977
+ let keys;
978
+ if (aligned) {
979
+ keys = convertKeys(gNorm ?? sNorm ?? eNorm, tm, () => ({
980
+ kind: "linear",
981
+ stops: []
982
+ }));
983
+ for (let j = 0; j < keys.length; j++) {
984
+ const s = sNorm ? vec2Of(sNorm[j].value) : staticS();
985
+ const e = eNorm ? vec2Of(eNorm[j].value) : staticE();
986
+ const g = gNorm ? gNorm[j].value : staticG();
987
+ keys[j].value = buildStaticPaint(t, s, e, g, p);
988
+ }
989
+ } else {
990
+ const times = [...new Set(channels.flatMap((n) => n.map((kk) => kk.t)))].sort((a, b) => a - b);
991
+ const at = (norm, ft, fallback, read) => {
992
+ if (!norm) return fallback();
993
+ let v = norm[0].value;
994
+ for (const kk of norm) if (kk.t <= ft) v = kk.value;
995
+ else break;
996
+ return read(v);
997
+ };
998
+ keys = enforceMonotonic(times.map((ft) => ({
999
+ t: toSeconds(tm, ft),
1000
+ value: buildStaticPaint(t, at(sNorm, ft, staticS, vec2Of), at(eNorm, ft, staticE, vec2Of), at(gNorm, ft, staticG, (v) => v), p)
1001
+ })));
1002
+ }
1003
+ spec.fill = keys[0].value;
1004
+ pushTrack(ctx, `${spec.id}/fill`, "paint", keys);
1005
+ }
887
1006
  function pathSpecFor(ctx, style, geom, idBase, tm) {
888
1007
  const spec = {
889
1008
  kind: "path",
@@ -891,6 +1010,11 @@ function pathSpecFor(ctx, style, geom, idBase, tm) {
891
1010
  data: geom.kind === "static" ? geom.value : geom.keys[0].value
892
1011
  };
893
1012
  if (geom.kind === "animated") pushTrack(ctx, `${spec.id}/d`, "path", geom.keys.map((k) => ({ ...k })));
1013
+ if (style.ty === "gf") {
1014
+ applyGradientFill(ctx, spec, style, tm);
1015
+ applyOpacity(ctx, spec, style.o, tm);
1016
+ return spec;
1017
+ }
894
1018
  const colorTarget = style.ty === "fl" ? "fill" : "stroke";
895
1019
  const cNorm = norms(style.c);
896
1020
  if (cNorm) {
@@ -948,7 +1072,7 @@ function denormItems(ctx, items, idBase, tm, where) {
948
1072
  source,
949
1073
  name: item.nm ?? `geo${geomCounter++}`
950
1074
  });
951
- } else if (item.ty === "fl" || item.ty === "st") {
1075
+ } else if (item.ty === "fl" || item.ty === "st" || item.ty === "gf") {
952
1076
  const preceding = geoms.filter((g) => g.index < i);
953
1077
  if (preceding.length === 0) continue;
954
1078
  const styleNodes = (hasMerge ? [{
@@ -1543,9 +1667,11 @@ function decimateLinearKeys(keys, relEps = .002) {
1543
1667
  * same discipline the importer uses for misaligned parametric geometry.
1544
1668
  *
1545
1669
  * SCOPE (MVP — mirror the importer's audit discipline: warn + drop, never
1546
- * silent). IN: Group hierarchy, Rect/Circle/Path with a SOLID fill (+ optional
1547
- * stroke), transform channels (position / position.x/.y split, opacity, scale,
1548
- * rotation identity degrees), animated `fill` color, animated `d` path
1670
+ * silent). IN: Group hierarchy, Rect/Circle/Path with a SOLID fill or a
1671
+ * LINEAR/RADIAL gradient `fill: paint` ( Lottie `gf`; +optional stroke),
1672
+ * transform channels (position / position.x/.y split, opacity, scale,
1673
+ * rotation → identity degrees), animated `fill` color OR animated `fill: paint`
1674
+ * gradient (keyed g/s/e when directly invertible, else sampled), animated `d` path
1549
1675
  * (constant topology), and TEXT (ty:5): a Text node → a text layer with a font
1550
1676
  * reference (fonts.list) + a text-document keyframe stream (static = one doc;
1551
1677
  * animated text/fill/fontSize → doc keyframes sampled on the frame grid, held).
@@ -1564,8 +1690,9 @@ function decimateLinearKeys(keys, relEps = .002) {
1564
1690
  * 0.75, not the correct single 0.5). Same limitation the importer documents at
1565
1691
  * convert.ts:470-475. A correct-for-overlap precomp (ty:0 + assets) is a later phase.
1566
1692
  *
1567
- * OUT (warned + dropped): Image/Video, gradient/mesh paint (solid only),
1568
- * non-center anchors, text typewriter `reveal`/`revealFraction`, variable-font axes
1693
+ * OUT (warned + dropped): Image/Video, MESH paint (`gf` covers linear/radial only
1694
+ * mesh has no Lottie ramp), gradient STROKE (`gs` Path.stroke is a color string,
1695
+ * not a Paint), non-center anchors, text typewriter `reveal`/`revealFraction`, variable-font axes
1569
1696
  * (`fontAxes`/`fontVariationSettings` — no Lottie doc field), `box` valign
1570
1697
  * (baseline-approximated) and wrap `width` (the player self-reflows), TokenHighlight.
1571
1698
  * Animated primitive geometry (width/radius tracks) is SAMPLED, not channel-mapped.
@@ -2101,11 +2228,7 @@ function colorKeys(ctx, tr) {
2101
2228
  function buildFill(ctx, node, tracks) {
2102
2229
  const tr = tracks.get("fill");
2103
2230
  if (tr) {
2104
- if (tr.type !== "color") {
2105
- ctx.warn(`${describe(node)}: animated '${tr.type}' fill (gradient/mesh) is not exported — dropped`);
2106
- return;
2107
- }
2108
- return {
2231
+ if (tr.type === "color") return {
2109
2232
  ty: "fl",
2110
2233
  c: {
2111
2234
  a: 1,
@@ -2116,19 +2239,267 @@ function buildFill(ctx, node, tracks) {
2116
2239
  k: 100
2117
2240
  }
2118
2241
  };
2242
+ if (tr.type === "paint") return buildAnimatedGradientFill(ctx, node, tr);
2243
+ ctx.warn(`${describe(node)}: animated '${tr.type}' fill is not exported — dropped`);
2244
+ return;
2119
2245
  }
2120
2246
  const fill = node.fill();
2121
- if (typeof fill !== "string") {
2122
- ctx.warn(`${describe(node)}: a gradient/mesh fill is not exported (MVP: solid color) — dropped`);
2123
- return;
2247
+ if (typeof fill === "string") {
2248
+ if (fill === "") return void 0;
2249
+ return {
2250
+ ty: "fl",
2251
+ c: {
2252
+ a: 0,
2253
+ k: colorToLottie(fill)
2254
+ },
2255
+ o: {
2256
+ a: 0,
2257
+ k: 100
2258
+ }
2259
+ };
2124
2260
  }
2125
- if (fill === "") return void 0;
2126
- return {
2261
+ if (fill.kind === "color") return {
2127
2262
  ty: "fl",
2128
2263
  c: {
2129
2264
  a: 0,
2130
- k: colorToLottie(fill)
2265
+ k: colorToLottie(fill.color)
2266
+ },
2267
+ o: {
2268
+ a: 0,
2269
+ k: 100
2270
+ }
2271
+ };
2272
+ if (fill.kind === "mesh") {
2273
+ ctx.warn(`${describe(node)}: a mesh fill has no Lottie gradient ramp (MVP: solid / linear / radial) — dropped`);
2274
+ return;
2275
+ }
2276
+ warnGradientInterpolation(ctx, node, fill);
2277
+ return gradientFillItem(fill, localBounds(node));
2278
+ }
2279
+ /** Local-space bounds of a shape's fill path — the gradient-geometry default source
2280
+ * (matches raster2d.resolveFill: linear ⇒ vertical bounds sweep, radial ⇒ centre +
2281
+ * half-diagonal). Rect/Circle draw centred at the origin; a Path bounds its anchors. */
2282
+ function localBounds(node) {
2283
+ if (node instanceof Rect) {
2284
+ const w = node.width() / 2;
2285
+ const h = node.height() / 2;
2286
+ return {
2287
+ minX: -w,
2288
+ minY: -h,
2289
+ maxX: w,
2290
+ maxY: h
2291
+ };
2292
+ }
2293
+ if (node instanceof Circle) {
2294
+ const r = node.radius();
2295
+ return {
2296
+ minX: -r,
2297
+ minY: -r,
2298
+ maxX: r,
2299
+ maxY: r
2300
+ };
2301
+ }
2302
+ let minX = Infinity;
2303
+ let minY = Infinity;
2304
+ let maxX = -Infinity;
2305
+ let maxY = -Infinity;
2306
+ for (const contour of node.data()) for (const [x, y] of contour.v) {
2307
+ if (x < minX) minX = x;
2308
+ if (y < minY) minY = y;
2309
+ if (x > maxX) maxX = x;
2310
+ if (y > maxY) maxY = y;
2311
+ }
2312
+ if (!Number.isFinite(minX)) return {
2313
+ minX: 0,
2314
+ minY: 0,
2315
+ maxX: 0,
2316
+ maxY: 0
2317
+ };
2318
+ return {
2319
+ minX,
2320
+ minY,
2321
+ maxX,
2322
+ maxY
2323
+ };
2324
+ }
2325
+ /** gf start point (Lottie `s`) from a gradient's geometry, defaulting to bounds
2326
+ * exactly as raster2d.resolveFill does (linear: [centre-x, minY]; radial: centre). */
2327
+ function gradientStart(g, b) {
2328
+ const cx = (b.minX + b.maxX) / 2;
2329
+ if (g.kind === "linear") return g.from ? [g.from[0], g.from[1]] : [cx, b.minY];
2330
+ const cy = (b.minY + b.maxY) / 2;
2331
+ return g.center ? [g.center[0], g.center[1]] : [cx, cy];
2332
+ }
2333
+ /** gf end point (Lottie `e`): linear ⇒ `to`; radial ⇒ centre + [radius, 0], so the
2334
+ * Lottie radial extent (|s→e|) equals the glissade radius (half-diagonal default). */
2335
+ function gradientEnd(g, b) {
2336
+ if (g.kind === "linear") {
2337
+ const cx = (b.minX + b.maxX) / 2;
2338
+ return g.to ? [g.to[0], g.to[1]] : [cx, b.maxY];
2339
+ }
2340
+ const [cx, cy] = gradientStart(g, b);
2341
+ return [cx + (g.radius !== void 0 ? g.radius : Math.hypot(b.maxX - b.minX, b.maxY - b.minY) / 2), cy];
2342
+ }
2343
+ /** Flatten stops into the Lottie `g.k` array: `[offset,r,g,b, …]` (0–1 floats),
2344
+ * then `[offset,a, …]` alpha stops appended iff any stop is translucent. */
2345
+ function gradientStopArray(stops) {
2346
+ const colors = [];
2347
+ const alphas = [];
2348
+ let anyAlpha = false;
2349
+ for (const s of stops) {
2350
+ const { r, g, b, a } = parseColor(s.color);
2351
+ colors.push(s.offset, r / 255, g / 255, b / 255);
2352
+ alphas.push(s.offset, a);
2353
+ if (a < 1) anyAlpha = true;
2354
+ }
2355
+ return anyAlpha ? [...colors, ...alphas] : colors;
2356
+ }
2357
+ function warnGradientInterpolation(ctx, node, g) {
2358
+ if (g.interpolation !== void 0 && g.interpolation !== "linear") ctx.warn(`${describe(node)}: '${g.interpolation}' gradient interpolation is emitted as a linear Lottie ramp (gf has no smooth/gaussian mode) — mid-stop banding may differ`);
2359
+ }
2360
+ /** A static linear/radial gradient → a `gf` shape item (geometry from `bounds`). */
2361
+ function gradientFillItem(g, bounds) {
2362
+ const gk = {
2363
+ p: g.stops.length,
2364
+ k: {
2365
+ a: 0,
2366
+ k: gradientStopArray(g.stops)
2367
+ }
2368
+ };
2369
+ const s = gradientStart(g, bounds);
2370
+ const e = gradientEnd(g, bounds);
2371
+ if (g.kind === "radial") return {
2372
+ ty: "gf",
2373
+ t: 2,
2374
+ s: {
2375
+ a: 0,
2376
+ k: s
2377
+ },
2378
+ e: {
2379
+ a: 0,
2380
+ k: e
2381
+ },
2382
+ g: gk,
2383
+ h: {
2384
+ a: 0,
2385
+ k: 0
2386
+ },
2387
+ a: {
2388
+ a: 0,
2389
+ k: 0
2390
+ },
2391
+ o: {
2392
+ a: 0,
2393
+ k: 100
2394
+ }
2395
+ };
2396
+ return {
2397
+ ty: "gf",
2398
+ t: 1,
2399
+ s: {
2400
+ a: 0,
2401
+ k: s
2402
+ },
2403
+ e: {
2404
+ a: 0,
2405
+ k: e
2406
+ },
2407
+ g: gk,
2408
+ o: {
2409
+ a: 0,
2410
+ k: 100
2411
+ }
2412
+ };
2413
+ }
2414
+ /**
2415
+ * An animated `fill: paint` track → a `gf` with keyframed s/e/g channels. The
2416
+ * gradient KIND (linear/radial) is fixed to the first key's kind (Lottie can't
2417
+ * animate `t`); mesh/color first keys warn-drop. Directly-invertible tracks keep
2418
+ * their eases via emitKeys; anything else (named ease / spring / expr) samples on
2419
+ * the frame grid. A key whose kind/stop-count differs from the first would snap
2420
+ * under paintType anyway, so its geometry/ramp is read as best-effort.
2421
+ */
2422
+ function buildAnimatedGradientFill(ctx, node, tr) {
2423
+ const first = tr.keys[0].value;
2424
+ if (first.kind === "mesh") {
2425
+ ctx.warn(`${describe(node)}: an animated mesh fill has no Lottie gradient ramp — dropped`);
2426
+ return;
2427
+ }
2428
+ if (first.kind === "color") {
2429
+ ctx.warn(`${describe(node)}: an animated color-only paint fill is not exported (use a 'color' track) — dropped`);
2430
+ return;
2431
+ }
2432
+ const kind = first.kind;
2433
+ const bounds = localBounds(node);
2434
+ const asGradient = (p) => p.kind === "linear" || p.kind === "radial" ? p : {
2435
+ kind,
2436
+ stops: [{
2437
+ offset: 0,
2438
+ color: "#000000"
2439
+ }]
2440
+ };
2441
+ warnGradientInterpolation(ctx, node, first);
2442
+ const p = first.stops.length;
2443
+ const sMap = (v) => gradientStart(asGradient(v), bounds);
2444
+ const eMap = (v) => gradientEnd(asGradient(v), bounds);
2445
+ const gMap = (v) => gradientStopArray(asGradient(v).stops);
2446
+ let sK;
2447
+ let eK;
2448
+ let gK;
2449
+ if (isDirectlyInvertible(tr.keys, tr.expr)) {
2450
+ sK = emitKeys(tr.keys, ctx.fr, sMap);
2451
+ eK = emitKeys(tr.keys, ctx.fr, eMap);
2452
+ gK = emitKeys(tr.keys, ctx.fr, gMap);
2453
+ } else {
2454
+ ctx.warn(`${describe(node)}: animated gradient fill is sampled at ${ctx.fr} fps (non-invertible ease)`);
2455
+ sK = sampleToLottieKeys(tr, ctx.fr, ctx.ip, ctx.op, sMap);
2456
+ eK = sampleToLottieKeys(tr, ctx.fr, ctx.ip, ctx.op, eMap);
2457
+ gK = sampleToLottieKeys(tr, ctx.fr, ctx.ip, ctx.op, gMap);
2458
+ }
2459
+ const g = {
2460
+ p,
2461
+ k: {
2462
+ a: 1,
2463
+ k: gK
2464
+ }
2465
+ };
2466
+ if (kind === "radial") return {
2467
+ ty: "gf",
2468
+ t: 2,
2469
+ s: {
2470
+ a: 1,
2471
+ k: sK
2472
+ },
2473
+ e: {
2474
+ a: 1,
2475
+ k: eK
2476
+ },
2477
+ g,
2478
+ h: {
2479
+ a: 0,
2480
+ k: 0
2481
+ },
2482
+ a: {
2483
+ a: 0,
2484
+ k: 0
2485
+ },
2486
+ o: {
2487
+ a: 0,
2488
+ k: 100
2489
+ }
2490
+ };
2491
+ return {
2492
+ ty: "gf",
2493
+ t: 1,
2494
+ s: {
2495
+ a: 1,
2496
+ k: sK
2497
+ },
2498
+ e: {
2499
+ a: 1,
2500
+ k: eK
2131
2501
  },
2502
+ g,
2132
2503
  o: {
2133
2504
  a: 0,
2134
2505
  k: 100
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glissade/lottie",
3
- "version": "0.50.0-pre.0",
3
+ "version": "0.51.0-pre.0",
4
4
  "description": "glissade Lottie import (S1 MVP): pure .json (Lottie/bodymovin) → node specs + a v1 Timeline. Fail-fast feature audit; no DOM/Node dependencies.",
5
5
  "license": "Apache-2.0",
6
6
  "engines": {
@@ -18,11 +18,11 @@
18
18
  "dist"
19
19
  ],
20
20
  "dependencies": {
21
- "@glissade/core": "0.50.0-pre.0",
22
- "@glissade/scene": "0.50.0-pre.0"
21
+ "@glissade/core": "0.51.0-pre.0",
22
+ "@glissade/scene": "0.51.0-pre.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@glissade/backend-skia": "0.50.0-pre.0"
25
+ "@glissade/backend-skia": "0.51.0-pre.0"
26
26
  },
27
27
  "repository": {
28
28
  "type": "git",