
base
base
base
base
base
basediff --git a/docs/blend-modes.html b/docs/blend-modes.html index 7b4c45d..ca9c294 100644 --- a/docs/blend-modes.html +++ b/docs/blend-modes.html @@ -37,13 +37,13 @@
The unedited photos that every blend mode below is applied to.






image.darken("#e8743b")
min(A, B)

base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
baseimage.lighten("#e8743b")
max(A, B)
- 
base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
baseimage.overlay("#e8743b")
2AB if A <= 0.5, else 1 - 2(1 - A)(1 - B)
- 
base
base
base
base
base
base
base
base
base
base
base
baseimage.hard_light("#e8743b")
2AB if B <= 0.5, else 1 - 2(1 - A)(1 - B)

base
base
base
base
base
baseimage.vivid_light("#e8743b")
Color burn if B <= 0.5, else color dodge

base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
base
baseOpacity mixes the blended result back with the original: overlay("#ff4f8b", opacity=...) on the mountain-lake photo.
Opacity mixes the blended result back with the original: overlay("#ff4f8b", opacity=...) on the palace-pond photo.





The same mode gives very different moods depending on the color: soft_light(color) on the portrait photo.
The same mode gives very different moods depending on the color: soft_light(color) on the hanbok-portrait photo.






Small corrections that make a photo look the way it felt.
+

A hazy, overcast shot has no true blacks or whites. Curve points computed with NumPy emulate a Levels adjustment that stretches the tones to the full range; saturation and a warm overlay bring the colors back.
image = LayerImage.from_file("photos/harbor.jpg")
-# Levels: map input 0.12 to black and 0.88 to white
+ image = LayerImage.from_file("photos/fishing-harbor.jpg")
+# Levels: map input 0.16 to black and 0.82 to white
x = np.linspace(0, 1, 26)
-levels = np.clip((x - 0.12) / 0.76, 0, 1)
+levels = np.clip((x - 0.16) / 0.66, 0, 1)
(image.curve("rgb", levels.tolist())
.saturation(0.35)
@@ -69,7 +69,7 @@ Rescue a Flat Photo
-
+
Before
@@ -81,7 +81,7 @@ Rescue a Flat Photo
Make Food Pop
Food photos benefit from rich color and a touch of warmth. A gentle S-curve adds contrast without crushing the shadows.
Uses
- image = LayerImage.from_file("photos/breakfast.jpg")
+ image = LayerImage.from_file("photos/bibimbap.jpg")
(image.saturation(0.4)
.curve("rgb", [0, 0.2, 0.52, 0.8, 1])
.overlay("#ffc670", opacity=0.3))
@@ -89,7 +89,7 @@ Make Food Pop
-
+
Before
@@ -101,7 +101,7 @@ Make Food Pop
Warm Up Skin Tones
Per-channel curves act as a white-balance slider: raise the red midtones and lower the blue ones to warm up a cool, grey-lit portrait.
Uses
- image = LayerImage.from_file("photos/portrait.jpg")
+ image = LayerImage.from_file("photos/hanbok-portrait.jpg")
(image.curve("r", [0, 0.58, 1])
.curve("b", [0, 0.43, 0.95])
.saturation(0.1))
@@ -113,7 +113,7 @@ Color Grading
Shift the mood of a scene with curves and color layers.
-
+
Before
@@ -125,7 +125,7 @@ Color Grading
Teal & Orange
The blockbuster grade: an S-curve on red and an inverted S-curve on blue push the shadows toward teal and the highlights toward orange.
Uses
- image = LayerImage.from_file("photos/beach.jpg")
+ image = LayerImage.from_file("photos/jeju-coast.jpg")
(image.curve("r", [0, 0.1, 0.5, 0.9, 1])
.curve("b", [0.18, 0.38, 0.5, 0.6, 0.8])
.contrast(1.1)
@@ -134,7 +134,7 @@ Teal & Orange
-
+
Before
@@ -146,7 +146,7 @@ Teal & Orange
Neon Nights
Lift the blue in the shadows, wash the scene with magenta using Soft Light, and turn up saturation for a cyberpunk feel.
Uses
- image = LayerImage.from_file("photos/city-night.jpg")
+ image = LayerImage.from_file("photos/seoul-night.jpg")
(image.curve("b", [0.12, 0.45, 0.75, 1])
.soft_light("#ff2fb3", opacity=0.6)
.saturation(0.3)
@@ -155,7 +155,7 @@ Neon Nights
-
+
Before
@@ -167,7 +167,7 @@ Neon Nights
Winter Blues
A blue Soft Light layer and a curve that lifts the blue shadows cool the whole scene; a small lightness lift keeps it airy.
Uses
- image = LayerImage.from_file("photos/snowy-cabin.jpg")
+ image = LayerImage.from_file("photos/snowy-hanok.jpg")
(image.soft_light("#1f5fd1", opacity=0.8)
.curve("b", [0.1, 0.55, 1])
.lightness(0.05))
@@ -175,7 +175,7 @@ Winter Blues
-
+
Before
@@ -187,7 +187,7 @@ Winter Blues
Moody Forest
Muted greens and a deep teal cast: desaturate, crush the shadows with a curve, then multiply with a dark teal at low opacity.
Uses
- image = LayerImage.from_file("photos/forest.jpg")
+ image = LayerImage.from_file("photos/bamboo-forest.jpg")
(image.saturation(-0.35)
.curve("rgb", [0.03, 0.18, 0.45, 0.78, 0.97])
.multiply("#2f6f6a", opacity=0.35)
@@ -196,7 +196,7 @@ Moody Forest
-
+
Before
@@ -208,7 +208,7 @@ Moody Forest
Golden Glow
Exaggerate the sunrise: Screen a dark orange to lift and tint the shadows, Overlay a brighter one to warm the midtones, then restore contrast with an S-curve.
Uses
- image = LayerImage.from_file("photos/mountain-lake.jpg")
+ image = LayerImage.from_file("photos/palace-pond.jpg")
(image.screen("#3a1800", opacity=0.7)
.overlay("#ff9d3b", opacity=0.25)
.curve("rgb", [0, 0.18, 0.5, 0.82, 1]))
@@ -220,7 +220,7 @@ Film & Vintage
Lifted blacks, warm casts and soft contrast for an analog look.
-
+
Before
@@ -232,7 +232,7 @@ Film & Vintage
Faded Film
Lifted blacks and softened whites give the matte look of expired film; a cream Soft Light layer warms the midtones.
Uses
- image = LayerImage.from_file("photos/cafe.jpg")
+ image = LayerImage.from_file("photos/hanok-tea.jpg")
(image.curve("rgb", [0.15, 0.32, 0.55, 0.76, 0.9])
.saturation(-0.25)
.soft_light("#f0cf9c", opacity=0.7))
@@ -240,7 +240,7 @@ Faded Film
-
+
Before
@@ -252,7 +252,7 @@ Faded Film
Cross-Processed
Developing slide film in negative chemistry gave punchy, yellow-green highlights and blue shadows. Opposing curves on each channel recreate it.
Uses
- image = LayerImage.from_file("photos/vintage-car.jpg")
+ image = LayerImage.from_file("photos/gamcheon-village.jpg")
(image.curve("r", [0, 0.15, 0.55, 0.9, 1])
.curve("g", [0, 0.2, 0.55, 0.85, 1])
.curve("b", [0.22, 0.35, 0.48, 0.6, 0.72]))
@@ -260,7 +260,7 @@ Cross-Processed
-
+
Before
@@ -272,7 +272,7 @@ Cross-Processed
Instant Film
Lighten with a deep navy so no shadow goes below it, warm the image with Overlay and pull back saturation for a Polaroid feel.
Uses
- image = LayerImage.from_file("photos/beach.jpg")
+ image = LayerImage.from_file("photos/jeju-coast.jpg")
(image.lighten("#2b2d4a")
.overlay("#ffb870", opacity=0.5)
.saturation(-0.25)
@@ -281,7 +281,7 @@ Instant Film
-
+
Before
@@ -293,7 +293,7 @@ Instant Film
Sepia Print
Convert to grayscale, then use per-channel curves as a gradient map: black becomes dark brown and white becomes warm cream.
Uses
- image = LayerImage.from_file("photos/portrait.jpg")
+ image = LayerImage.from_file("photos/hanbok-portrait.jpg")
(image.grayscale()
.curve("r", [0.16, 0.62, 1.0])
.curve("g", [0.1, 0.5, 0.92])
@@ -302,7 +302,7 @@ Sepia Print
-
+
Before
@@ -314,7 +314,7 @@ Sepia Print
Lomo
Toy-camera look: high contrast, oversaturated color and dark corners. The vignette is a NumPy array blended with Multiply, where white in the center leaves pixels untouched.
Uses
- image = LayerImage.from_file("photos/flower-market.jpg")
+ image = LayerImage.from_file("photos/lotus-lanterns.jpg")
h, w, _ = image.get_image_as_array().shape
y, x = np.ogrid[-1:1:h * 1j, -1:1:w * 1j]
vignette = np.clip(1.2 - 0.6 * (x**2 + y**2), 0, 1)[..., np.newaxis]
@@ -330,7 +330,7 @@ Black & White
Monochrome conversions, from classic to toned prints.
-
+
Before
@@ -340,16 +340,16 @@ Black & White
Classic Black & White
- Grayscale alone is often flat. An S-curve restores depth and makes the textures of the beard and sweater stand out.
+ Grayscale alone is often flat. An S-curve restores depth and brings out the texture of the beard and the woven gat hat.
Uses
- image = LayerImage.from_file("photos/portrait.jpg")
+ image = LayerImage.from_file("photos/hanbok-portrait.jpg")
(image.grayscale()
.curve("rgb", [0, 0.15, 0.48, 0.85, 1]))
-
+
Before
@@ -361,7 +361,7 @@ Classic Black & White
B&W with a Red Filter
Film photographers used a red lens filter to turn blue skies almost black. Multiplying by red before converting to grayscale does the same thing.
Uses
- image = LayerImage.from_file("photos/architecture.jpg")
+ image = LayerImage.from_file("photos/jeju-coast.jpg")
(image.multiply("#ff5a36")
.grayscale()
.curve("rgb", [0, 0.3, 0.85, 1])
@@ -370,7 +370,7 @@ B&W with a Red Filter
-
+
Before
@@ -382,7 +382,7 @@ B&W with a Red Filter
Noir
Crushed blacks and bright highlights for a film-noir street. The curve's low second point drives most of the frame into shadow.
Uses
- image = LayerImage.from_file("photos/city-night.jpg")
+ image = LayerImage.from_file("photos/seoul-night.jpg")
(image.grayscale()
.curve("rgb", [0, 0.04, 0.3, 0.75, 1])
.contrast(1.2))
@@ -390,7 +390,7 @@ Noir
-
+
Before
@@ -402,7 +402,7 @@ Noir
Cyanotype
The 19th-century blueprint process, recreated with a gradient map from deep Prussian blue to pale cyan.
Uses
- image = LayerImage.from_file("photos/forest.jpg")
+ image = LayerImage.from_file("photos/bamboo-forest.jpg")
(image.grayscale()
.curve("rgb", [0, 0.1, 0.55, 0.9, 1])
.curve("r", [0.02, 0.85])
@@ -412,7 +412,7 @@ Cyanotype
-
+
Before
@@ -424,7 +424,7 @@ Cyanotype
High-Key Monochrome
Bright, airy black and white: lighten toward white, then add contrast so the darkest details still read.
Uses
- image = LayerImage.from_file("photos/snowy-cabin.jpg")
+ image = LayerImage.from_file("photos/snowy-hanok.jpg")
(image.grayscale()
.lightness(0.3)
.contrast(1.25))
@@ -436,7 +436,7 @@ Creative Effects
Gradients, vignettes, double exposures and false color. Most of them use NumPy arrays as blend layers.
-
+
Before
@@ -448,7 +448,7 @@ Creative Effects
Duotone Poster
Map shadows to one color and highlights to another. A two-point curve per channel acts as a gradient map.
Uses
- image = LayerImage.from_file("photos/architecture.jpg")
+ image = LayerImage.from_file("photos/palace-eaves.jpg")
shadow, highlight = (0.12, 0.08, 0.4), (1.0, 0.45, 0.42) # indigo to coral
image.grayscale().contrast(1.2)
@@ -458,7 +458,7 @@ Duotone Poster
Double Exposure
- Blend modes accept another photo's pixels. Screen keeps the white sky white and fills the dark silhouette with the forest; a pale green Multiply tints the result.
+ Blend modes accept another photo's pixels. Screen keeps the white sky white and fills the dark silhouette with the bamboo; a pale green Multiply tints the result.
Uses
- image = LayerImage.from_file("photos/silhouette.jpg")
-forest = LayerImage.from_file("photos/forest.jpg").get_image_as_array()
+ image = LayerImage.from_file("photos/hanbok-silhouette.jpg")
+bamboo = LayerImage.from_file("photos/bamboo-forest.jpg").get_image_as_array()
-(image.screen(forest)
+(image.screen(bamboo)
.grayscale()
.curve("rgb", [0, 0.12, 0.5, 0.88, 1])
.multiply("#cfe3dc"))
@@ -481,7 +481,7 @@ Double Exposure
Graduated Sky Filter
- A vertical gradient from warm tint to white, blended with Multiply, deepens the sky while leaving the lake untouched, like a graduated filter on a lens.
+ A vertical gradient from warm tint to white, blended with Multiply, deepens the sky while leaving the pond untouched, like a graduated filter on a lens.
Uses
- image = LayerImage.from_file("photos/mountain-lake.jpg")
+ image = LayerImage.from_file("photos/palace-pond.jpg")
h, w, _ = image.get_image_as_array().shape
t = np.clip(np.linspace(0, 2.2, h), 0, 1)[:, np.newaxis, np.newaxis]
gradient = (1 - t) * np.array([0.95, 0.55, 0.4]) + t # tint at the top fading to white
@@ -503,7 +503,7 @@ Graduated Sky Filter
Channel Swap
- LayerImage works on plain NumPy arrays, so anything NumPy can do is fair game. Swapping the red and green channels turns the turquoise car violet and the pink house lime.
+ LayerImage works on plain NumPy arrays, so anything NumPy can do is fair game. Swapping the red and green channels turns the turquoise houses violet and the pink ones lime.
Uses
- image = LayerImage.from_file("photos/vintage-car.jpg")
+ image = LayerImage.from_file("photos/gamcheon-village.jpg")
pixels = image.get_image_as_array()
image = LayerImage.from_array(pixels[..., [1, 0, 2]].copy()) # swap R and G
@@ -524,7 +524,7 @@ Channel Swap
Colorize
- hue() sets every pixel to a single hue while keeping saturation and value, so the whole stall becomes shades of magenta.
+ hue() sets every pixel to a single hue while keeping saturation and value, so every lantern becomes a shade of magenta.
Uses
- image = LayerImage.from_file("photos/flower-market.jpg")
+ image = LayerImage.from_file("photos/lotus-lanterns.jpg")
image.hue(0.88).contrast(1.1)
-
+
Before
@@ -554,7 +554,7 @@ Colorize
Pastel Dream
Soft and airy: lighten toward white, mute the colors and Screen a pale pink over everything.
Uses
- image = LayerImage.from_file("photos/vintage-car.jpg")
+ image = LayerImage.from_file("photos/gamcheon-village.jpg")
(image.lightness(0.18)
.saturation(-0.2)
.screen("#ffc4d6", opacity=0.35))
@@ -587,43 +587,43 @@ One Preset, Many Photos
-
-
+
+
+ aria-label="Compare the warm matte preset on palace-pond with the original photo">
- mountain-lake.jpg
+ palace-pond.jpg
-
-
+
+
+ aria-label="Compare the warm matte preset on bibimbap with the original photo">
- breakfast.jpg
+ bibimbap.jpg
-
-
+
+
+ aria-label="Compare the warm matte preset on fishing-harbor with the original photo">
- harbor.jpg
+ fishing-harbor.jpg
-
-
+
+
+ aria-label="Compare the warm matte preset on hanok-tea with the original photo">
- cafe.jpg
+ hanok-tea.jpg
@@ -632,19 +632,19 @@ One Preset, Many Photos
Sample Photos
Download the originals (1536 x 1024) to run the snippets yourself. They were generated for these docs with OpenAI's gpt-image-2.5 model; the prompts are in scripts/generate_photos.py.
-
mountain-lake.jpg
-
portrait.jpg
-
city-night.jpg
-
breakfast.jpg
-
flower-market.jpg
-
architecture.jpg
-
beach.jpg
-
forest.jpg
-
cafe.jpg
-
vintage-car.jpg
-
harbor.jpg
-
silhouette.jpg
-
snowy-cabin.jpg
+
palace-pond.jpg
+
hanbok-portrait.jpg
+
seoul-night.jpg
+
bibimbap.jpg
+
lotus-lanterns.jpg
+
palace-eaves.jpg
+
jeju-coast.jpg
+
bamboo-forest.jpg
+
hanok-tea.jpg
+
gamcheon-village.jpg
+
fishing-harbor.jpg
+
hanbok-silhouette.jpg
+
snowy-hanok.jpg
diff --git a/docs/examples/batch/bibimbap.jpg b/docs/examples/batch/bibimbap.jpg
new file mode 100644
index 0000000..127d493
Binary files /dev/null and b/docs/examples/batch/bibimbap.jpg differ
diff --git a/docs/examples/batch/breakfast.jpg b/docs/examples/batch/breakfast.jpg
deleted file mode 100644
index 9f2ee72..0000000
Binary files a/docs/examples/batch/breakfast.jpg and /dev/null differ
diff --git a/docs/examples/batch/cafe.jpg b/docs/examples/batch/cafe.jpg
deleted file mode 100644
index e2ecc53..0000000
Binary files a/docs/examples/batch/cafe.jpg and /dev/null differ
diff --git a/docs/examples/batch/fishing-harbor.jpg b/docs/examples/batch/fishing-harbor.jpg
new file mode 100644
index 0000000..f2cdc34
Binary files /dev/null and b/docs/examples/batch/fishing-harbor.jpg differ
diff --git a/docs/examples/batch/hanok-tea.jpg b/docs/examples/batch/hanok-tea.jpg
new file mode 100644
index 0000000..414d04d
Binary files /dev/null and b/docs/examples/batch/hanok-tea.jpg differ
diff --git a/docs/examples/batch/harbor.jpg b/docs/examples/batch/harbor.jpg
deleted file mode 100644
index 3d7c90a..0000000
Binary files a/docs/examples/batch/harbor.jpg and /dev/null differ
diff --git a/docs/examples/batch/mountain-lake.jpg b/docs/examples/batch/mountain-lake.jpg
deleted file mode 100644
index 938fb64..0000000
Binary files a/docs/examples/batch/mountain-lake.jpg and /dev/null differ
diff --git a/docs/examples/batch/palace-pond.jpg b/docs/examples/batch/palace-pond.jpg
new file mode 100644
index 0000000..2c00aea
Binary files /dev/null and b/docs/examples/batch/palace-pond.jpg differ
diff --git a/docs/examples/channel-swap.jpg b/docs/examples/channel-swap.jpg
index c1c8856..9e36f20 100644
Binary files a/docs/examples/channel-swap.jpg and b/docs/examples/channel-swap.jpg differ
diff --git a/docs/examples/classic-bw.jpg b/docs/examples/classic-bw.jpg
index f8e46c5..4154089 100644
Binary files a/docs/examples/classic-bw.jpg and b/docs/examples/classic-bw.jpg differ
diff --git a/docs/examples/colorize.jpg b/docs/examples/colorize.jpg
index fdd2acc..3f24683 100644
Binary files a/docs/examples/colorize.jpg and b/docs/examples/colorize.jpg differ
diff --git a/docs/examples/cross-process.jpg b/docs/examples/cross-process.jpg
index 1cbee1d..9f2ae18 100644
Binary files a/docs/examples/cross-process.jpg and b/docs/examples/cross-process.jpg differ
diff --git a/docs/examples/cyanotype.jpg b/docs/examples/cyanotype.jpg
index 4bdd45f..6b10519 100644
Binary files a/docs/examples/cyanotype.jpg and b/docs/examples/cyanotype.jpg differ
diff --git a/docs/examples/double-exposure.jpg b/docs/examples/double-exposure.jpg
index 9e1480c..7c82bb8 100644
Binary files a/docs/examples/double-exposure.jpg and b/docs/examples/double-exposure.jpg differ
diff --git a/docs/examples/duotone.jpg b/docs/examples/duotone.jpg
index 726ef1d..880c05d 100644
Binary files a/docs/examples/duotone.jpg and b/docs/examples/duotone.jpg differ
diff --git a/docs/examples/faded-film.jpg b/docs/examples/faded-film.jpg
index 5aab7a2..26f04e7 100644
Binary files a/docs/examples/faded-film.jpg and b/docs/examples/faded-film.jpg differ
diff --git a/docs/examples/food-pop.jpg b/docs/examples/food-pop.jpg
index 907ad4e..6595572 100644
Binary files a/docs/examples/food-pop.jpg and b/docs/examples/food-pop.jpg differ
diff --git a/docs/examples/golden-glow.jpg b/docs/examples/golden-glow.jpg
index d8ac118..2ca495f 100644
Binary files a/docs/examples/golden-glow.jpg and b/docs/examples/golden-glow.jpg differ
diff --git a/docs/examples/graduated-filter.jpg b/docs/examples/graduated-filter.jpg
index 16cef94..ea7383e 100644
Binary files a/docs/examples/graduated-filter.jpg and b/docs/examples/graduated-filter.jpg differ
diff --git a/docs/examples/high-key.jpg b/docs/examples/high-key.jpg
index 12438b9..36503ec 100644
Binary files a/docs/examples/high-key.jpg and b/docs/examples/high-key.jpg differ
diff --git a/docs/examples/instant-film.jpg b/docs/examples/instant-film.jpg
index 2e35798..ffd8b1e 100644
Binary files a/docs/examples/instant-film.jpg and b/docs/examples/instant-film.jpg differ
diff --git a/docs/examples/lomo.jpg b/docs/examples/lomo.jpg
index 9b95833..1bfcdfe 100644
Binary files a/docs/examples/lomo.jpg and b/docs/examples/lomo.jpg differ
diff --git a/docs/examples/moody-forest.jpg b/docs/examples/moody-forest.jpg
index 873e7d1..17ca215 100644
Binary files a/docs/examples/moody-forest.jpg and b/docs/examples/moody-forest.jpg differ
diff --git a/docs/examples/neon-nights.jpg b/docs/examples/neon-nights.jpg
index a70f84e..1c5ada4 100644
Binary files a/docs/examples/neon-nights.jpg and b/docs/examples/neon-nights.jpg differ
diff --git a/docs/examples/noir.jpg b/docs/examples/noir.jpg
index 7afc4d9..5fe8c57 100644
Binary files a/docs/examples/noir.jpg and b/docs/examples/noir.jpg differ
diff --git a/docs/examples/originals/architecture.jpg b/docs/examples/originals/architecture.jpg
deleted file mode 100644
index f9392d9..0000000
Binary files a/docs/examples/originals/architecture.jpg and /dev/null differ
diff --git a/docs/examples/originals/bamboo-forest.jpg b/docs/examples/originals/bamboo-forest.jpg
new file mode 100644
index 0000000..b08f31d
Binary files /dev/null and b/docs/examples/originals/bamboo-forest.jpg differ
diff --git a/docs/examples/originals/beach.jpg b/docs/examples/originals/beach.jpg
deleted file mode 100644
index 9aafb72..0000000
Binary files a/docs/examples/originals/beach.jpg and /dev/null differ
diff --git a/docs/examples/originals/bibimbap.jpg b/docs/examples/originals/bibimbap.jpg
new file mode 100644
index 0000000..ba016cb
Binary files /dev/null and b/docs/examples/originals/bibimbap.jpg differ
diff --git a/docs/examples/originals/breakfast.jpg b/docs/examples/originals/breakfast.jpg
deleted file mode 100644
index 09835c9..0000000
Binary files a/docs/examples/originals/breakfast.jpg and /dev/null differ
diff --git a/docs/examples/originals/cafe.jpg b/docs/examples/originals/cafe.jpg
deleted file mode 100644
index 3ec0d98..0000000
Binary files a/docs/examples/originals/cafe.jpg and /dev/null differ
diff --git a/docs/examples/originals/city-night.jpg b/docs/examples/originals/city-night.jpg
deleted file mode 100644
index 312cf2f..0000000
Binary files a/docs/examples/originals/city-night.jpg and /dev/null differ
diff --git a/docs/examples/originals/fishing-harbor.jpg b/docs/examples/originals/fishing-harbor.jpg
new file mode 100644
index 0000000..16dbd8c
Binary files /dev/null and b/docs/examples/originals/fishing-harbor.jpg differ
diff --git a/docs/examples/originals/flower-market.jpg b/docs/examples/originals/flower-market.jpg
deleted file mode 100644
index 86a8eda..0000000
Binary files a/docs/examples/originals/flower-market.jpg and /dev/null differ
diff --git a/docs/examples/originals/forest.jpg b/docs/examples/originals/forest.jpg
deleted file mode 100644
index 93c090a..0000000
Binary files a/docs/examples/originals/forest.jpg and /dev/null differ
diff --git a/docs/examples/originals/gamcheon-village.jpg b/docs/examples/originals/gamcheon-village.jpg
new file mode 100644
index 0000000..9e7c406
Binary files /dev/null and b/docs/examples/originals/gamcheon-village.jpg differ
diff --git a/docs/examples/originals/hanbok-portrait.jpg b/docs/examples/originals/hanbok-portrait.jpg
new file mode 100644
index 0000000..426910b
Binary files /dev/null and b/docs/examples/originals/hanbok-portrait.jpg differ
diff --git a/docs/examples/originals/hanbok-silhouette.jpg b/docs/examples/originals/hanbok-silhouette.jpg
new file mode 100644
index 0000000..ad06373
Binary files /dev/null and b/docs/examples/originals/hanbok-silhouette.jpg differ
diff --git a/docs/examples/originals/hanok-tea.jpg b/docs/examples/originals/hanok-tea.jpg
new file mode 100644
index 0000000..1d232c3
Binary files /dev/null and b/docs/examples/originals/hanok-tea.jpg differ
diff --git a/docs/examples/originals/harbor.jpg b/docs/examples/originals/harbor.jpg
deleted file mode 100644
index 6775336..0000000
Binary files a/docs/examples/originals/harbor.jpg and /dev/null differ
diff --git a/docs/examples/originals/jeju-coast.jpg b/docs/examples/originals/jeju-coast.jpg
new file mode 100644
index 0000000..f388248
Binary files /dev/null and b/docs/examples/originals/jeju-coast.jpg differ
diff --git a/docs/examples/originals/lotus-lanterns.jpg b/docs/examples/originals/lotus-lanterns.jpg
new file mode 100644
index 0000000..f1e1206
Binary files /dev/null and b/docs/examples/originals/lotus-lanterns.jpg differ
diff --git a/docs/examples/originals/mountain-lake.jpg b/docs/examples/originals/mountain-lake.jpg
deleted file mode 100644
index 9765a1f..0000000
Binary files a/docs/examples/originals/mountain-lake.jpg and /dev/null differ
diff --git a/docs/examples/originals/palace-eaves.jpg b/docs/examples/originals/palace-eaves.jpg
new file mode 100644
index 0000000..64aca0c
Binary files /dev/null and b/docs/examples/originals/palace-eaves.jpg differ
diff --git a/docs/examples/originals/palace-pond.jpg b/docs/examples/originals/palace-pond.jpg
new file mode 100644
index 0000000..935a535
Binary files /dev/null and b/docs/examples/originals/palace-pond.jpg differ
diff --git a/docs/examples/originals/portrait.jpg b/docs/examples/originals/portrait.jpg
deleted file mode 100644
index 496bfaf..0000000
Binary files a/docs/examples/originals/portrait.jpg and /dev/null differ
diff --git a/docs/examples/originals/seoul-night.jpg b/docs/examples/originals/seoul-night.jpg
new file mode 100644
index 0000000..d77d75e
Binary files /dev/null and b/docs/examples/originals/seoul-night.jpg differ
diff --git a/docs/examples/originals/silhouette.jpg b/docs/examples/originals/silhouette.jpg
deleted file mode 100644
index 5a24f4b..0000000
Binary files a/docs/examples/originals/silhouette.jpg and /dev/null differ
diff --git a/docs/examples/originals/snowy-cabin.jpg b/docs/examples/originals/snowy-cabin.jpg
deleted file mode 100644
index b890f2b..0000000
Binary files a/docs/examples/originals/snowy-cabin.jpg and /dev/null differ
diff --git a/docs/examples/originals/snowy-hanok.jpg b/docs/examples/originals/snowy-hanok.jpg
new file mode 100644
index 0000000..185d736
Binary files /dev/null and b/docs/examples/originals/snowy-hanok.jpg differ
diff --git a/docs/examples/originals/vintage-car.jpg b/docs/examples/originals/vintage-car.jpg
deleted file mode 100644
index 4d3def7..0000000
Binary files a/docs/examples/originals/vintage-car.jpg and /dev/null differ
diff --git a/docs/examples/pastel-dream.jpg b/docs/examples/pastel-dream.jpg
index 17b575e..48b0155 100644
Binary files a/docs/examples/pastel-dream.jpg and b/docs/examples/pastel-dream.jpg differ
diff --git a/docs/examples/red-filter-bw.jpg b/docs/examples/red-filter-bw.jpg
index 8a5786a..92137da 100644
Binary files a/docs/examples/red-filter-bw.jpg and b/docs/examples/red-filter-bw.jpg differ
diff --git a/docs/examples/rescue-flat-photo.jpg b/docs/examples/rescue-flat-photo.jpg
index a69e02b..b3f975c 100644
Binary files a/docs/examples/rescue-flat-photo.jpg and b/docs/examples/rescue-flat-photo.jpg differ
diff --git a/docs/examples/sepia.jpg b/docs/examples/sepia.jpg
index 4199c6e..9454bbe 100644
Binary files a/docs/examples/sepia.jpg and b/docs/examples/sepia.jpg differ
diff --git a/docs/examples/teal-and-orange.jpg b/docs/examples/teal-and-orange.jpg
index 43c053d..06589a8 100644
Binary files a/docs/examples/teal-and-orange.jpg and b/docs/examples/teal-and-orange.jpg differ
diff --git a/docs/examples/warm-white-balance.jpg b/docs/examples/warm-white-balance.jpg
index e01ee1c..16d9f39 100644
Binary files a/docs/examples/warm-white-balance.jpg and b/docs/examples/warm-white-balance.jpg differ
diff --git a/docs/examples/winter-blues.jpg b/docs/examples/winter-blues.jpg
index 615ed1f..18a6eda 100644
Binary files a/docs/examples/winter-blues.jpg and b/docs/examples/winter-blues.jpg differ
diff --git a/docs/index.html b/docs/index.html
index 401d094..a47dc79 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -42,19 +42,19 @@ layeris
- image = LayerImage.from_file("photos/vintage-car.jpg")
The unedited sample photo, vintage-car.jpg.
image = LayerImage.from_file("photos/vintage-car.jpg")
+ image = LayerImage.from_file("photos/gamcheon-village.jpg")
The unedited sample photo, gamcheon-village.jpg.
image = LayerImage.from_file("photos/gamcheon-village.jpg")
(image.curve("r", [0, 0.15, 0.55, 0.9, 1])
.curve("g", [0, 0.2, 0.55, 0.85, 1])
- .curve("b", [0.22, 0.35, 0.48, 0.6, 0.72]))
From the examples: Cross-processed
image = LayerImage.from_file("photos/vintage-car.jpg")
+ .curve("b", [0.22, 0.35, 0.48, 0.6, 0.72]))
From the examples: Cross-processed
image = LayerImage.from_file("photos/gamcheon-village.jpg")
(image.lightness(0.18)
.saturation(-0.2)
- .screen("#ffc4d6", opacity=0.35))
From the examples: Pastel dream
image = LayerImage.from_file("photos/vintage-car.jpg")
+ .screen("#ffc4d6", opacity=0.35))
From the examples: Pastel dream
image = LayerImage.from_file("photos/gamcheon-village.jpg")
pixels = image.get_image_as_array()
image = LayerImage.from_array(pixels[..., [1, 0, 2]].copy()) # swap R and G
image.contrast(1.1)
From the examples: Channel swap
- 


+ 


@@ -160,7 +160,7 @@ Examples
Blend Modes
All 14 blend modes side by side on three photos, plus how opacity and the blend color change the result.
- 

+ 

diff --git a/docs/photos/architecture.jpg b/docs/photos/architecture.jpg
deleted file mode 100644
index 48bcb36..0000000
Binary files a/docs/photos/architecture.jpg and /dev/null differ
diff --git a/docs/photos/bamboo-forest.jpg b/docs/photos/bamboo-forest.jpg
new file mode 100644
index 0000000..79551d2
Binary files /dev/null and b/docs/photos/bamboo-forest.jpg differ
diff --git a/docs/photos/beach.jpg b/docs/photos/beach.jpg
deleted file mode 100644
index f0672b3..0000000
Binary files a/docs/photos/beach.jpg and /dev/null differ
diff --git a/docs/photos/bibimbap.jpg b/docs/photos/bibimbap.jpg
new file mode 100644
index 0000000..27f4dda
Binary files /dev/null and b/docs/photos/bibimbap.jpg differ
diff --git a/docs/photos/breakfast.jpg b/docs/photos/breakfast.jpg
deleted file mode 100644
index b7dee16..0000000
Binary files a/docs/photos/breakfast.jpg and /dev/null differ
diff --git a/docs/photos/cafe.jpg b/docs/photos/cafe.jpg
deleted file mode 100644
index 23e57b3..0000000
Binary files a/docs/photos/cafe.jpg and /dev/null differ
diff --git a/docs/photos/city-night.jpg b/docs/photos/city-night.jpg
deleted file mode 100644
index fb72657..0000000
Binary files a/docs/photos/city-night.jpg and /dev/null differ
diff --git a/docs/photos/fishing-harbor.jpg b/docs/photos/fishing-harbor.jpg
new file mode 100644
index 0000000..ef5a84f
Binary files /dev/null and b/docs/photos/fishing-harbor.jpg differ
diff --git a/docs/photos/flower-market.jpg b/docs/photos/flower-market.jpg
deleted file mode 100644
index 5c8273f..0000000
Binary files a/docs/photos/flower-market.jpg and /dev/null differ
diff --git a/docs/photos/forest.jpg b/docs/photos/forest.jpg
deleted file mode 100644
index cfd7248..0000000
Binary files a/docs/photos/forest.jpg and /dev/null differ
diff --git a/docs/photos/gamcheon-village.jpg b/docs/photos/gamcheon-village.jpg
new file mode 100644
index 0000000..59f57f0
Binary files /dev/null and b/docs/photos/gamcheon-village.jpg differ
diff --git a/docs/photos/hanbok-portrait.jpg b/docs/photos/hanbok-portrait.jpg
new file mode 100644
index 0000000..8b1abf0
Binary files /dev/null and b/docs/photos/hanbok-portrait.jpg differ
diff --git a/docs/photos/hanbok-silhouette.jpg b/docs/photos/hanbok-silhouette.jpg
new file mode 100644
index 0000000..a955d18
Binary files /dev/null and b/docs/photos/hanbok-silhouette.jpg differ
diff --git a/docs/photos/hanok-tea.jpg b/docs/photos/hanok-tea.jpg
new file mode 100644
index 0000000..cec39e6
Binary files /dev/null and b/docs/photos/hanok-tea.jpg differ
diff --git a/docs/photos/harbor.jpg b/docs/photos/harbor.jpg
deleted file mode 100644
index 91ff794..0000000
Binary files a/docs/photos/harbor.jpg and /dev/null differ
diff --git a/docs/photos/jeju-coast.jpg b/docs/photos/jeju-coast.jpg
new file mode 100644
index 0000000..40aa9fd
Binary files /dev/null and b/docs/photos/jeju-coast.jpg differ
diff --git a/docs/photos/lotus-lanterns.jpg b/docs/photos/lotus-lanterns.jpg
new file mode 100644
index 0000000..c266e70
Binary files /dev/null and b/docs/photos/lotus-lanterns.jpg differ
diff --git a/docs/photos/mountain-lake.jpg b/docs/photos/mountain-lake.jpg
deleted file mode 100644
index 53c0fb0..0000000
Binary files a/docs/photos/mountain-lake.jpg and /dev/null differ
diff --git a/docs/photos/palace-eaves.jpg b/docs/photos/palace-eaves.jpg
new file mode 100644
index 0000000..9258d78
Binary files /dev/null and b/docs/photos/palace-eaves.jpg differ
diff --git a/docs/photos/palace-pond.jpg b/docs/photos/palace-pond.jpg
new file mode 100644
index 0000000..30b9374
Binary files /dev/null and b/docs/photos/palace-pond.jpg differ
diff --git a/docs/photos/portrait.jpg b/docs/photos/portrait.jpg
deleted file mode 100644
index 320e567..0000000
Binary files a/docs/photos/portrait.jpg and /dev/null differ
diff --git a/docs/photos/seoul-night.jpg b/docs/photos/seoul-night.jpg
new file mode 100644
index 0000000..93337ba
Binary files /dev/null and b/docs/photos/seoul-night.jpg differ
diff --git a/docs/photos/silhouette.jpg b/docs/photos/silhouette.jpg
deleted file mode 100644
index 0c77cd3..0000000
Binary files a/docs/photos/silhouette.jpg and /dev/null differ
diff --git a/docs/photos/snowy-cabin.jpg b/docs/photos/snowy-cabin.jpg
deleted file mode 100644
index 67476c8..0000000
Binary files a/docs/photos/snowy-cabin.jpg and /dev/null differ
diff --git a/docs/photos/snowy-hanok.jpg b/docs/photos/snowy-hanok.jpg
new file mode 100644
index 0000000..6eec236
Binary files /dev/null and b/docs/photos/snowy-hanok.jpg differ
diff --git a/docs/photos/vintage-car.jpg b/docs/photos/vintage-car.jpg
deleted file mode 100644
index f7d089d..0000000
Binary files a/docs/photos/vintage-car.jpg and /dev/null differ
diff --git a/docs/previews/brightness.jpg b/docs/previews/brightness.jpg
index 8744033..b6d7540 100644
Binary files a/docs/previews/brightness.jpg and b/docs/previews/brightness.jpg differ
diff --git a/docs/previews/chaining.jpg b/docs/previews/chaining.jpg
index dd03f30..d248669 100644
Binary files a/docs/previews/chaining.jpg and b/docs/previews/chaining.jpg differ
diff --git a/docs/previews/color_burn.jpg b/docs/previews/color_burn.jpg
index 2fcb724..04a6c5f 100644
Binary files a/docs/previews/color_burn.jpg and b/docs/previews/color_burn.jpg differ
diff --git a/docs/previews/color_dodge.jpg b/docs/previews/color_dodge.jpg
index 4339599..838b110 100644
Binary files a/docs/previews/color_dodge.jpg and b/docs/previews/color_dodge.jpg differ
diff --git a/docs/previews/contrast.jpg b/docs/previews/contrast.jpg
index 9bd6452..d7ea213 100644
Binary files a/docs/previews/contrast.jpg and b/docs/previews/contrast.jpg differ
diff --git a/docs/previews/curve_red.jpg b/docs/previews/curve_red.jpg
index 53b91bc..260482a 100644
Binary files a/docs/previews/curve_red.jpg and b/docs/previews/curve_red.jpg differ
diff --git a/docs/previews/curve_rgb.jpg b/docs/previews/curve_rgb.jpg
index 6bbd7c6..dd8901c 100644
Binary files a/docs/previews/curve_rgb.jpg and b/docs/previews/curve_rgb.jpg differ
diff --git a/docs/previews/darken.jpg b/docs/previews/darken.jpg
index 9123bff..da4196b 100644
Binary files a/docs/previews/darken.jpg and b/docs/previews/darken.jpg differ
diff --git a/docs/previews/grayscale.jpg b/docs/previews/grayscale.jpg
index c3d465c..a209b6c 100644
Binary files a/docs/previews/grayscale.jpg and b/docs/previews/grayscale.jpg differ
diff --git a/docs/previews/hard_light.jpg b/docs/previews/hard_light.jpg
index 3abd566..a2ea5b0 100644
Binary files a/docs/previews/hard_light.jpg and b/docs/previews/hard_light.jpg differ
diff --git a/docs/previews/hue.jpg b/docs/previews/hue.jpg
index 88d32fe..7a81d0c 100644
Binary files a/docs/previews/hue.jpg and b/docs/previews/hue.jpg differ
diff --git a/docs/previews/lighten.jpg b/docs/previews/lighten.jpg
index 817186e..44251aa 100644
Binary files a/docs/previews/lighten.jpg and b/docs/previews/lighten.jpg differ
diff --git a/docs/previews/lightness.jpg b/docs/previews/lightness.jpg
index 7698e9b..d105a3b 100644
Binary files a/docs/previews/lightness.jpg and b/docs/previews/lightness.jpg differ
diff --git a/docs/previews/linear_burn.jpg b/docs/previews/linear_burn.jpg
index 175d6fd..3f15ca3 100644
Binary files a/docs/previews/linear_burn.jpg and b/docs/previews/linear_burn.jpg differ
diff --git a/docs/previews/linear_dodge.jpg b/docs/previews/linear_dodge.jpg
index 00f9c7a..edbe0d4 100644
Binary files a/docs/previews/linear_dodge.jpg and b/docs/previews/linear_dodge.jpg differ
diff --git a/docs/previews/linear_light.jpg b/docs/previews/linear_light.jpg
index 64f40f4..9e28704 100644
Binary files a/docs/previews/linear_light.jpg and b/docs/previews/linear_light.jpg differ
diff --git a/docs/previews/multiply.jpg b/docs/previews/multiply.jpg
index 6352b67..371a5ed 100644
Binary files a/docs/previews/multiply.jpg and b/docs/previews/multiply.jpg differ
diff --git a/docs/previews/overlay.jpg b/docs/previews/overlay.jpg
index 29b5094..47dd2c6 100644
Binary files a/docs/previews/overlay.jpg and b/docs/previews/overlay.jpg differ
diff --git a/docs/previews/pin_light.jpg b/docs/previews/pin_light.jpg
index 2512c68..471ca63 100644
Binary files a/docs/previews/pin_light.jpg and b/docs/previews/pin_light.jpg differ
diff --git a/docs/previews/resize.jpg b/docs/previews/resize.jpg
index 404963a..b25d68c 100644
Binary files a/docs/previews/resize.jpg and b/docs/previews/resize.jpg differ
diff --git a/docs/previews/saturation.jpg b/docs/previews/saturation.jpg
index 463a668..c32fec0 100644
Binary files a/docs/previews/saturation.jpg and b/docs/previews/saturation.jpg differ
diff --git a/docs/previews/screen.jpg b/docs/previews/screen.jpg
index 5f86cdd..fa25aba 100644
Binary files a/docs/previews/screen.jpg and b/docs/previews/screen.jpg differ
diff --git a/docs/previews/soft_light.jpg b/docs/previews/soft_light.jpg
index 6cf9cf9..707357a 100644
Binary files a/docs/previews/soft_light.jpg and b/docs/previews/soft_light.jpg differ
diff --git a/docs/previews/vivid_light.jpg b/docs/previews/vivid_light.jpg
index b068cd6..e768c97 100644
Binary files a/docs/previews/vivid_light.jpg and b/docs/previews/vivid_light.jpg differ
diff --git a/scripts/build_docs.py b/scripts/build_docs.py
index c4201ef..6b86572 100644
--- a/scripts/build_docs.py
+++ b/scripts/build_docs.py
@@ -73,7 +73,7 @@
# The look switcher at the top of the overview page; the first look shown is
# HERO_LOOKS[0], after the unedited "Original".
-HERO_PHOTO = "vintage-car"
+HERO_PHOTO = "gamcheon-village"
HERO_LOOKS = ["cross-process", "pastel-dream", "channel-swap"]
ARRAY_SNIPPET = """\
h, w, _ = image.get_image_as_array().shape
@@ -85,7 +85,7 @@
# Thumbnails beside each page in the overview's "In these docs" list.
CONTENTS_EXAMPLES = ["teal-and-orange", "double-exposure", "cyanotype"]
CONTENTS_BLEND_MODES = [
- ("portrait", "soft_light"), ("mountain-lake", "color_dodge"), ("flower-market", "overlay"),
+ ("hanbok-portrait", "soft_light"), ("palace-pond", "color_dodge"), ("lotus-lanterns", "overlay"),
]
# Measured on a 12-megapixel JPEG: load, curve, brightness, contrast, overlay,
diff --git a/scripts/docs_examples.py b/scripts/docs_examples.py
index c04f507..3f4a8bf 100644
--- a/scripts/docs_examples.py
+++ b/scripts/docs_examples.py
@@ -21,19 +21,19 @@ class Photo:
PHOTOS: dict[str, Photo] = {
p.slug: p
for p in [
- Photo("mountain-lake", "Alpine lake at sunrise with mountains reflected in the water"),
- Photo("portrait", "Older man with a grey beard and knitted cap by a workshop window"),
- Photo("city-night", "Rainy city street at night with neon reflections"),
- Photo("breakfast", "Overhead view of a breakfast table with avocado toast and berries"),
- Photo("flower-market", "Flower stall with tulips, roses, sunflowers and peonies"),
- Photo("architecture", "White building with curved balconies against a blue sky"),
- Photo("beach", "Sandy beach in late afternoon light with a lifeguard tower"),
- Photo("forest", "Misty pine forest with sun rays and ferns"),
- Photo("cafe", "Cozy cafe interior with a barista behind the counter"),
- Photo("vintage-car", "Turquoise 1960s car on a street of pastel houses"),
- Photo("harbor", "Fishing harbor on a hazy, overcast day"),
- Photo("silhouette", "Profile silhouette of a woman against a white sky"),
- Photo("snowy-cabin", "Wooden cabin in a snowy landscape with smoke from the chimney"),
+ Photo("palace-pond", "Gyeonghoeru pavilion reflected in its pond at sunrise"),
+ Photo("hanbok-portrait", "Older man in a white hanbok and black gat hat by a paper window"),
+ Photo("seoul-night", "Seoul alley at night with neon and lanterns reflected on wet pavement"),
+ Photo("bibimbap", "Overhead view of bibimbap in a stone bowl with banchan side dishes"),
+ Photo("lotus-lanterns", "Rows of colorful lotus lanterns over a temple courtyard"),
+ Photo("palace-eaves", "Painted palace eaves against a deep blue sky"),
+ Photo("jeju-coast", "Jeju beach with black volcanic rocks in late afternoon light"),
+ Photo("bamboo-forest", "Misty bamboo forest with sun rays"),
+ Photo("hanok-tea", "Celadon tea set on a low table in a hanok room"),
+ Photo("gamcheon-village", "Pastel houses on a hillside in Gamcheon Culture Village, Busan"),
+ Photo("fishing-harbor", "Fishing harbor on Korea's east coast on a hazy day"),
+ Photo("hanbok-silhouette", "Profile silhouette of a woman in hanbok against a white sky"),
+ Photo("snowy-hanok", "Hanok house with a snow-covered tiled roof"),
]
}
@@ -70,7 +70,7 @@ def snippet(self) -> str:
slug="rescue-flat-photo",
title="Rescue a flat photo",
category="Everyday fixes",
- photo="harbor",
+ photo="fishing-harbor",
description=(
"A hazy, overcast shot has no true blacks or whites. Curve points "
"computed with NumPy emulate a Levels adjustment that stretches the "
@@ -78,9 +78,9 @@ def snippet(self) -> str:
"colors back."
),
code="""
-# Levels: map input 0.12 to black and 0.88 to white
+# Levels: map input 0.16 to black and 0.82 to white
x = np.linspace(0, 1, 26)
-levels = np.clip((x - 0.12) / 0.76, 0, 1)
+levels = np.clip((x - 0.16) / 0.66, 0, 1)
(image.curve("rgb", levels.tolist())
.saturation(0.35)
@@ -91,7 +91,7 @@ def snippet(self) -> str:
slug="food-pop",
title="Make food pop",
category="Everyday fixes",
- photo="breakfast",
+ photo="bibimbap",
description=(
"Food photos benefit from rich color and a touch of warmth. A gentle "
"S-curve adds contrast without crushing the shadows."
@@ -106,7 +106,7 @@ def snippet(self) -> str:
slug="warm-white-balance",
title="Warm up skin tones",
category="Everyday fixes",
- photo="portrait",
+ photo="hanbok-portrait",
description=(
"Per-channel curves act as a white-balance slider: raise the red "
"midtones and lower the blue ones to warm up a cool, grey-lit "
@@ -125,7 +125,7 @@ def snippet(self) -> str:
slug="teal-and-orange",
title="Teal & orange",
category="Color grading",
- photo="beach",
+ photo="jeju-coast",
description=(
"The blockbuster grade: an S-curve on red and an inverted S-curve "
"on blue push the shadows toward teal and the highlights toward "
@@ -142,7 +142,7 @@ def snippet(self) -> str:
slug="neon-nights",
title="Neon nights",
category="Color grading",
- photo="city-night",
+ photo="seoul-night",
description=(
"Lift the blue in the shadows, wash the scene with magenta using "
"Soft Light, and turn up saturation for a cyberpunk feel."
@@ -158,7 +158,7 @@ def snippet(self) -> str:
slug="winter-blues",
title="Winter blues",
category="Color grading",
- photo="snowy-cabin",
+ photo="snowy-hanok",
description=(
"A blue Soft Light layer and a curve that lifts the blue shadows "
"cool the whole scene; a small lightness lift keeps it airy."
@@ -173,7 +173,7 @@ def snippet(self) -> str:
slug="moody-forest",
title="Moody forest",
category="Color grading",
- photo="forest",
+ photo="bamboo-forest",
description=(
"Muted greens and a deep teal cast: desaturate, crush the shadows "
"with a curve, then multiply with a dark teal at low opacity."
@@ -189,7 +189,7 @@ def snippet(self) -> str:
slug="golden-glow",
title="Golden glow",
category="Color grading",
- photo="mountain-lake",
+ photo="palace-pond",
description=(
"Exaggerate the sunrise: Screen a dark orange to lift and tint the "
"shadows, Overlay a brighter one to warm the midtones, then restore "
@@ -208,7 +208,7 @@ def snippet(self) -> str:
slug="faded-film",
title="Faded film",
category="Film & vintage",
- photo="cafe",
+ photo="hanok-tea",
description=(
"Lifted blacks and softened whites give the matte look of expired "
"film; a cream Soft Light layer warms the midtones."
@@ -223,7 +223,7 @@ def snippet(self) -> str:
slug="cross-process",
title="Cross-processed",
category="Film & vintage",
- photo="vintage-car",
+ photo="gamcheon-village",
description=(
"Developing slide film in negative chemistry gave punchy, yellow-"
"green highlights and blue shadows. Opposing curves on each channel "
@@ -239,7 +239,7 @@ def snippet(self) -> str:
slug="instant-film",
title="Instant film",
category="Film & vintage",
- photo="beach",
+ photo="jeju-coast",
description=(
"Lighten with a deep navy so no shadow goes below it, warm the "
"image with Overlay and pull back saturation for a Polaroid feel."
@@ -255,7 +255,7 @@ def snippet(self) -> str:
slug="sepia",
title="Sepia print",
category="Film & vintage",
- photo="portrait",
+ photo="hanbok-portrait",
description=(
"Convert to grayscale, then use per-channel curves as a gradient "
"map: black becomes dark brown and white becomes warm cream."
@@ -271,7 +271,7 @@ def snippet(self) -> str:
slug="lomo",
title="Lomo",
category="Film & vintage",
- photo="flower-market",
+ photo="lotus-lanterns",
description=(
"Toy-camera look: high contrast, oversaturated color and dark "
"corners. The vignette is a NumPy array blended with Multiply, "
@@ -294,10 +294,10 @@ def snippet(self) -> str:
slug="classic-bw",
title="Classic black & white",
category="Black & white",
- photo="portrait",
+ photo="hanbok-portrait",
description=(
"Grayscale alone is often flat. An S-curve restores depth and "
- "makes the textures of the beard and sweater stand out."
+ "brings out the texture of the beard and the woven gat hat."
),
code="""
(image.grayscale()
@@ -308,7 +308,7 @@ def snippet(self) -> str:
slug="red-filter-bw",
title="B&W with a red filter",
category="Black & white",
- photo="architecture",
+ photo="jeju-coast",
description=(
"Film photographers used a red lens filter to turn blue skies "
"almost black. Multiplying by red before converting to grayscale "
@@ -325,7 +325,7 @@ def snippet(self) -> str:
slug="noir",
title="Noir",
category="Black & white",
- photo="city-night",
+ photo="seoul-night",
description=(
"Crushed blacks and bright highlights for a film-noir street. The "
"curve's low second point drives most of the frame into shadow."
@@ -340,7 +340,7 @@ def snippet(self) -> str:
slug="cyanotype",
title="Cyanotype",
category="Black & white",
- photo="forest",
+ photo="bamboo-forest",
description=(
"The 19th-century blueprint process, recreated with a gradient map "
"from deep Prussian blue to pale cyan."
@@ -357,7 +357,7 @@ def snippet(self) -> str:
slug="high-key",
title="High-key monochrome",
category="Black & white",
- photo="snowy-cabin",
+ photo="snowy-hanok",
description=(
"Bright, airy black and white: lighten toward white, then add "
"contrast so the darkest details still read."
@@ -375,7 +375,7 @@ def snippet(self) -> str:
slug="duotone",
title="Duotone poster",
category="Creative effects",
- photo="architecture",
+ photo="palace-eaves",
description=(
"Map shadows to one color and highlights to another. A two-point "
"curve per channel acts as a gradient map."
@@ -392,16 +392,16 @@ def snippet(self) -> str:
slug="double-exposure",
title="Double exposure",
category="Creative effects",
- photo="silhouette",
+ photo="hanbok-silhouette",
description=(
"Blend modes accept another photo's pixels. Screen keeps the white "
- "sky white and fills the dark silhouette with the forest; a pale "
+ "sky white and fills the dark silhouette with the bamboo; a pale "
"green Multiply tints the result."
),
code="""
-forest = LayerImage.from_file("photos/forest.jpg").get_image_as_array()
+bamboo = LayerImage.from_file("photos/bamboo-forest.jpg").get_image_as_array()
-(image.screen(forest)
+(image.screen(bamboo)
.grayscale()
.curve("rgb", [0, 0.12, 0.5, 0.88, 1])
.multiply("#cfe3dc"))
@@ -411,10 +411,10 @@ def snippet(self) -> str:
slug="graduated-filter",
title="Graduated sky filter",
category="Creative effects",
- photo="mountain-lake",
+ photo="palace-pond",
description=(
"A vertical gradient from warm tint to white, blended with Multiply, "
- "deepens the sky while leaving the lake untouched, like a "
+ "deepens the sky while leaving the pond untouched, like a "
"graduated filter on a lens."
),
code="""
@@ -429,11 +429,11 @@ def snippet(self) -> str:
slug="channel-swap",
title="Channel swap",
category="Creative effects",
- photo="vintage-car",
+ photo="gamcheon-village",
description=(
"LayerImage works on plain NumPy arrays, so anything NumPy can do is "
"fair game. Swapping the red and green channels turns the turquoise "
- "car violet and the pink house lime."
+ "houses violet and the pink ones lime."
),
code="""
pixels = image.get_image_as_array()
@@ -446,10 +446,10 @@ def snippet(self) -> str:
slug="colorize",
title="Colorize",
category="Creative effects",
- photo="flower-market",
+ photo="lotus-lanterns",
description=(
"hue() sets every pixel to a single hue while keeping saturation "
- "and value, so the whole stall becomes shades of magenta."
+ "and value, so every lantern becomes a shade of magenta."
),
code="""
image.hue(0.88).contrast(1.1)
@@ -459,7 +459,7 @@ def snippet(self) -> str:
slug="pastel-dream",
title="Pastel dream",
category="Creative effects",
- photo="vintage-car",
+ photo="gamcheon-village",
description=(
"Soft and airy: lighten toward white, mute the colors and Screen a "
"pale pink over everything."
@@ -485,7 +485,7 @@ def snippet(self) -> str:
{"type": "lighten", "hex": "#1e1b2e"},
]
}
-BATCH_PHOTOS: list[str] = ["mountain-lake", "breakfast", "harbor", "cafe"]
+BATCH_PHOTOS: list[str] = ["palace-pond", "bibimbap", "fishing-harbor", "hanok-tea"]
# ----------------------------------------------------------------------
@@ -493,7 +493,7 @@ def snippet(self) -> str:
# ----------------------------------------------------------------------
BLEND_COLOR = "#e8743b"
-BLEND_PHOTOS: list[str] = ["portrait", "mountain-lake", "flower-market"]
+BLEND_PHOTOS: list[str] = ["hanbok-portrait", "palace-pond", "lotus-lanterns"]
# (method, group, formula) in the order of Photoshop's blend-mode menu.
BLEND_MODES: list[tuple[str, str, str]] = [
@@ -513,11 +513,11 @@ def snippet(self) -> str:
("pin_light", "Contrast", "Replace A depending on B"),
]
-OPACITY_PHOTO = "mountain-lake"
+OPACITY_PHOTO = "palace-pond"
OPACITY_MODE = "overlay"
OPACITY_COLOR = "#ff4f8b"
OPACITY_STEPS: list[float] = [0.0, 0.25, 0.5, 0.75, 1.0]
-COLOR_PHOTO = "portrait"
+COLOR_PHOTO = "hanbok-portrait"
COLOR_MODE = "soft_light"
COLOR_SWATCHES: list[str] = ["#ff6b35", "#ffd23f", "#3bceac", "#0ead69", "#4361ee", "#c42bd8"]
diff --git a/scripts/generate_photos.py b/scripts/generate_photos.py
index 98e6bc2..e5b96fd 100644
--- a/scripts/generate_photos.py
+++ b/scripts/generate_photos.py
@@ -35,73 +35,81 @@
SIZE = "1536x1024"
JPEG_QUALITY = 85
-# Shared suffix that keeps the photos natural-looking: heavily stylised
-# source images would leave little room to show what each edit does.
+# Shared suffix: minimal, clean compositions that still look natural, since
+# heavily stylised source images would leave little room to show what each
+# edit does. Every photo shows something from Korea.
STYLE = (
- "Realistic documentary photograph, natural unedited colors, neutral color "
- "grading, no text, no watermark."
+ "Minimal, clean composition with generous negative space. Realistic "
+ "documentary photograph, natural unedited colors, neutral color grading, "
+ "no text, no watermark."
)
PHOTOS: dict[str, str] = {
- "mountain-lake": (
- "An alpine lake at sunrise, pine trees on the rocky shore, snow-capped "
- "mountains reflected in calm water, soft morning light."
+ "palace-pond": (
+ "Gyeonghoeru, the pavilion of Gyeongbokgung Palace in Seoul, reflected "
+ "in its calm pond at sunrise, soft pink and orange sky, mountains in "
+ "the distance."
),
- "portrait": (
- "Environmental portrait of a smiling older man with a grey beard and a "
- "knitted wool cap, sitting by a window in a workshop, soft natural "
- "window light from the side, shallow depth of field."
+ "hanbok-portrait": (
+ "Environmental portrait of a smiling older Korean man with a grey "
+ "beard, wearing a white hanbok and a traditional black horsehair gat "
+ "hat, sitting by a hanji paper window, soft cool daylight from the "
+ "side, shallow depth of field."
),
- "city-night": (
- "A rainy city street at night, wet asphalt reflecting neon shop signs "
- "and street lights, a few pedestrians with umbrellas, taxis passing."
+ "seoul-night": (
+ "A narrow alley in Seoul at night after rain, glowing neon shop signs "
+ "and red paper lanterns reflected on the wet pavement, a few "
+ "pedestrians with umbrellas. The signs are blurred, with no legible "
+ "lettering."
),
- "breakfast": (
- "Overhead flat-lay of a breakfast table: avocado toast, a bowl of "
- "berries and yogurt, a cup of coffee, orange juice, on a light wooden "
- "table, daylight."
+ "bibimbap": (
+ "Overhead flat-lay of a Korean meal on a light wooden table: bibimbap "
+ "in a hot stone bowl topped with a fried egg, small dishes of banchan "
+ "and kimchi, a bowl of rice, metal chopsticks and a spoon, daylight."
),
- "flower-market": (
- "A colorful flower stall at an outdoor market with buckets of tulips, "
- "roses, sunflowers and peonies, a vendor arranging bouquets in the "
- "background, daylight."
+ "lotus-lanterns": (
+ "Rows of colorful lotus lanterns in pink, red, yellow, green and blue "
+ "hanging over a Korean temple courtyard for Buddha's Birthday, "
+ "daylight."
),
- "architecture": (
- "Modern white concrete building with repeating curved balconies, shot "
- "from below against a clear blue sky, strong geometric shapes and "
- "shadows, midday sun."
+ "palace-eaves": (
+ "The upturned eaves of a Korean palace roof painted with colorful "
+ "dancheong patterns, shot from below against a clear deep blue sky, "
+ "strong geometric shapes and shadows, midday sun."
),
- "beach": (
- "A wide sandy beach in the late afternoon, gentle waves, a lifeguard "
- "tower, two people walking along the waterline, a few clouds."
+ "jeju-coast": (
+ "A beach on Jeju Island in the late afternoon, black volcanic rocks in "
+ "the foreground, turquoise water and gentle waves, two people walking "
+ "along the waterline, a few clouds."
),
- "forest": (
- "A misty pine forest in the early morning, tall straight trunks, light "
- "fog between the trees, ferns and moss on the ground, sun rays."
+ "bamboo-forest": (
+ "A misty bamboo forest in the early morning, tall straight green "
+ "stalks, light fog between them, sun rays."
),
- "cafe": (
- "Interior of a cozy neighborhood cafe, a barista behind the counter "
- "preparing coffee, wooden furniture, plants, warm pendant lights and "
- "daylight from large windows."
+ "hanok-tea": (
+ "A pale green celadon tea set on a low wooden table inside a hanok "
+ "room, hanji paper sliding doors, warm wooden floor, soft daylight."
),
- "vintage-car": (
- "A classic 1960s turquoise car parked on a colorful old-town street "
- "with pastel painted houses, cobblestones, sunny afternoon."
+ "gamcheon-village": (
+ "Brightly painted pastel houses in pink, turquoise, yellow and blue "
+ "stacked on a hillside in Gamcheon Culture Village, Busan, sunny "
+ "afternoon, clear sky."
),
- "harbor": (
- "A small fishing harbor on a hazy overcast day, wooden boats moored at "
- "a pier, flat soft light, low contrast, muted colors, slightly washed "
- "out."
+ "fishing-harbor": (
+ "A small fishing harbor on Korea's east coast on a hazy overcast day, "
+ "wooden fishing boats moored at a pier, flat soft light, low contrast, "
+ "muted colors, slightly washed out."
),
- "silhouette": (
- "Side profile of a young woman's head and shoulders, centered, a dark "
- "silhouette against a plain bright white overcast sky, minimal, high "
- "key background."
+ "hanbok-silhouette": (
+ "Side profile of a young woman's head and shoulders, wearing a hanbok, "
+ "her hair in a traditional low bun with a binyeo hairpin, centered, a "
+ "dark silhouette against a plain bright white overcast sky, minimal, "
+ "high key background."
),
- "snowy-cabin": (
- "A small wooden cabin in a snowy landscape on a winter morning, snow on "
- "the roof and spruce trees, smoke rising from the chimney, footprints "
- "in the snow."
+ "snowy-hanok": (
+ "A traditional hanok house with a curved tiled roof covered in snow on "
+ "a winter morning, a stone wall and pine trees, smoke rising from the "
+ "chimney, footprints in the snow."
),
}
diff --git a/scripts/generate_previews.py b/scripts/generate_previews.py
index 160bace..0042f84 100644
--- a/scripts/generate_previews.py
+++ b/scripts/generate_previews.py
@@ -35,7 +35,7 @@
import docs_layout as layout # noqa: E402 (sibling module in scripts/)
-DEFAULT_SOURCE = REPO_ROOT / "test-images" / "piano-man-01.jpg"
+DEFAULT_SOURCE = REPO_ROOT / "docs" / "photos" / "gamcheon-village.jpg"
PANEL_WIDTH = 480 # width of each before/after panel
GUTTER = 4 # white gap between the two panels
JPEG_QUALITY = 82
diff --git a/video/.gitignore b/video/.gitignore
new file mode 100644
index 0000000..c2658d7
--- /dev/null
+++ b/video/.gitignore
@@ -0,0 +1 @@
+node_modules/
diff --git a/video/README.md b/video/README.md
new file mode 100644
index 0000000..60b8ab6
--- /dev/null
+++ b/video/README.md
@@ -0,0 +1,91 @@
+# layeris video
+
+A short walkthrough of layeris (1920×1080, 30 fps, 52.1 seconds),
+built with [Remotion](https://www.remotion.dev/). The rendered file is
+[`out/layeris.mp4`](out/layeris.mp4).
+
+It follows the docs site's look: white page, DM Mono for the wordmark and
+code, Schibsted Grotesk for text, Besley for section titles, and the same
+syntax colors. The logo is the vector from `docs/assets/logo.svg`: its three
+layers draw in fanned out, then settle into place.
+
+| Scene | What it shows |
+| --- | --- |
+| Intro | The logo animation and the `layeris` wordmark |
+| 01 Install | `pip install layeris`, with a quick download progress bar |
+| 02 Chain operations | A chained edit, with each step's result wiping in as its line is typed |
+| 03 Blend modes | All 14 blend modes applied to one photo |
+| 04 Curves | Control points on the `r` and `b` channels moving, with the photo updating |
+| 05 Looks | Three recipes from the docs, the last one loaded from a JSON preset |
+| Outro | Install command and links |
+
+## Every edit on screen is real
+
+Nothing is mocked up in the browser. `scripts/build_assets.py` runs layeris
+on the source photos and writes each result to `public/renders/`, along with
+`src/data/assets.json`, which holds the exact code that produced each image.
+The video reads its code snippets from that manifest, so the code on screen is
+always the code that ran.
+
+The source photos are the docs site's Korea-themed sample photos in
+`docs/photos/`, generated with OpenAI's `gpt-image-2.5-flare` model at
+`quality: low` from the prompts in the repository's
+`scripts/generate_photos.py`. The video uses six of them: the hanbok
+portrait, the Jeju coast, the bamboo forest, Gamcheon Culture Village, the
+palace pond and the palace eaves.
+
+## Working on it
+
+Requires Node 18+ and, for the asset scripts, Python 3.10+ with layeris
+installed from this repository (`pip install -e .` in the repo root).
+
+```sh
+cd video
+npm install
+
+npm run start # open Remotion Studio to preview and scrub
+npm run render # render out/layeris.mp4
+```
+
+To change an edit, update `scripts/build_assets.py` and rebuild the renders:
+
+```sh
+npm run assets # python3 scripts/build_assets.py
+```
+
+To regenerate a source photo (needs `OPENAI_API_KEY`), run the docs photo
+script from the repository root, then rebuild the renders:
+
+```sh
+python3 scripts/generate_photos.py --only hanbok-portrait # or --force for all
+cd video && npm run assets
+```
+
+Scene lengths are set in each file under `src/scenes/`; the composition's
+total length is their sum (see `src/Video.tsx`).
+
+## Sound
+
+The intro has no music: a short shimmer plays as the logo appears
+(`public/logo.mp3`), and a soft keystroke (`public/key.mp3`) plays on each
+letter as the wordmark is typed. Both come from
+`scripts/generate_sound_effect.py`.
+
+The music, `public/music.mp3`, starts as the logo fades out at the end of the
+intro. It is a mellow lo-fi instrumental: Rhodes chords over a laid-back beat
+with upright bass, about 86 BPM. It was generated with the ElevenLabs Music
+API (`music_v2_5`). This take is 52.1 seconds long, longer than the 47.8
+seconds between the intro and the end, so `src/Video.tsx` fades out its last
+second; by then it is already winding down.
+
+`scripts/generate_music.py` holds the prompts (`lofi`, `piano`, `marimba`) and
+sizes new takes to the 47.8 seconds exactly. Both scripts need
+`ELEVENLABS_API_KEY`:
+
+```sh
+python3 scripts/generate_music.py # "lofi" style, writes public/music.mp3
+python3 scripts/generate_sound_effect.py key # or logo; writes public/.mp3
+```
+
+Every run gives a different take. If the video's length changes, update
+`LENGTH_MS` in the music script first.
diff --git a/video/out/layeris.mp4 b/video/out/layeris.mp4
new file mode 100644
index 0000000..a7a07a8
Binary files /dev/null and b/video/out/layeris.mp4 differ
diff --git a/video/package-lock.json b/video/package-lock.json
new file mode 100644
index 0000000..322cfc3
--- /dev/null
+++ b/video/package-lock.json
@@ -0,0 +1,3915 @@
+{
+ "name": "layeris-video",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "layeris-video",
+ "version": "1.0.0",
+ "dependencies": {
+ "@fontsource/besley": "5.2.8",
+ "@fontsource/dm-mono": "5.2.7",
+ "@fontsource/schibsted-grotesk": "5.2.8",
+ "@remotion/cli": "4.0.531",
+ "@remotion/paths": "4.0.531",
+ "@remotion/transitions": "4.0.531",
+ "react": "19.1.1",
+ "react-dom": "19.1.1",
+ "remotion": "4.0.531"
+ },
+ "devDependencies": {
+ "@types/react": "19.1.12",
+ "typescript": "5.9.2"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/core/node_modules/@babel/parser": {
+ "version": "7.29.9",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz",
+ "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.8"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/core/node_modules/@babel/types": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
+ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz",
+ "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.8",
+ "@babel/types": "^7.29.8",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/generator/node_modules/@babel/parser": {
+ "version": "7.29.9",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz",
+ "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.8"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/generator/node_modules/@babel/types": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
+ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports/node_modules/@babel/types": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
+ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.10",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.10.tgz",
+ "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.8"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers/node_modules/@babel/types": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
+ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.24.1",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz",
+ "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==",
+ "license": "MIT",
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template/node_modules/@babel/parser": {
+ "version": "7.29.9",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz",
+ "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.8"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/template/node_modules/@babel/types": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
+ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.10",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.10.tgz",
+ "integrity": "sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.8",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.9",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.8",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse/node_modules/@babel/parser": {
+ "version": "7.29.9",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz",
+ "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.8"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/traverse/node_modules/@babel/types": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
+ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.24.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
+ "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.23.4",
+ "@babel/helper-validator-identifier": "^7.22.20",
+ "to-fast-properties": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.11.3",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz",
+ "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.3",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.11.3",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
+ "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz",
+ "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
+ "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
+ "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
+ "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
+ "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
+ "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
+ "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
+ "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
+ "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
+ "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
+ "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
+ "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
+ "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
+ "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
+ "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
+ "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
+ "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
+ "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
+ "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
+ "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
+ "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
+ "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
+ "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
+ "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@fontsource/besley": {
+ "version": "5.2.8",
+ "resolved": "https://registry.npmjs.org/@fontsource/besley/-/besley-5.2.8.tgz",
+ "integrity": "sha512-lLshUAmzsjjS4ZGPyOqZYUvlp7Y7ZYUwgtSXxYRQotJhc3W8pwL+Cvbv9Kqr2k3ZFlMOMQ1spcDuvkQN8erYAQ==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
+ "node_modules/@fontsource/dm-mono": {
+ "version": "5.2.7",
+ "resolved": "https://registry.npmjs.org/@fontsource/dm-mono/-/dm-mono-5.2.7.tgz",
+ "integrity": "sha512-Ma1az2atTVgQWuOWwjuxx26p/6A6CU9HBNKq1CFV6YKpKhpswnf9ry9Ql4+T6bTZzkdtSfS6tjJvqZOljVzIFQ==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
+ "node_modules/@fontsource/schibsted-grotesk": {
+ "version": "5.2.8",
+ "resolved": "https://registry.npmjs.org/@fontsource/schibsted-grotesk/-/schibsted-grotesk-5.2.8.tgz",
+ "integrity": "sha512-CyyDW5aS89oKGFAVndOsJTQ5pqzKuPnSKWjrdJdMT5TD/eA2JyWapUBhvy6X/lqqrB/GNk74PIff7coPifeVyg==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/source-map": {
+ "version": "0.3.11",
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
+ "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
+ "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@mediabunny/aac-encoder": {
+ "version": "1.56.1",
+ "resolved": "https://registry.npmjs.org/@mediabunny/aac-encoder/-/aac-encoder-1.56.1.tgz",
+ "integrity": "sha512-a+OY/BctP7zC4G/prHikooOVaoWXMD4+bUyEwHg2dtQiQdIwC6TDJdVYJu5dfrdSNOiAFkv1HKcYEeROdwMezg==",
+ "license": "MPL-2.0",
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/Vanilagy"
+ },
+ "peerDependencies": {
+ "mediabunny": "^1.0.0"
+ }
+ },
+ "node_modules/@mediabunny/flac-encoder": {
+ "version": "1.56.1",
+ "resolved": "https://registry.npmjs.org/@mediabunny/flac-encoder/-/flac-encoder-1.56.1.tgz",
+ "integrity": "sha512-zbmorMVlF1izwtlz5mWnu7A1rJLM+V26I0pEFr9Td1EDTwE5uAGRSW+/UZlnjwj1t00Woe3Zonfh9kb7lRRBqg==",
+ "license": "MPL-2.0",
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/Vanilagy"
+ },
+ "peerDependencies": {
+ "mediabunny": "^1.0.0"
+ }
+ },
+ "node_modules/@mediabunny/mp3-encoder": {
+ "version": "1.56.1",
+ "resolved": "https://registry.npmjs.org/@mediabunny/mp3-encoder/-/mp3-encoder-1.56.1.tgz",
+ "integrity": "sha512-p66ovEq6mZt3ejje45NOju84hIJI4v8rHfsmc1D2J34ziV0USE8AdJBzmiwqxl6nZkIAQLNB19HzbVRcQfc51g==",
+ "license": "MPL-2.0",
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/Vanilagy"
+ },
+ "peerDependencies": {
+ "mediabunny": "^1.0.0"
+ }
+ },
+ "node_modules/@module-federation/error-codes": {
+ "version": "0.22.0",
+ "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.22.0.tgz",
+ "integrity": "sha512-xF9SjnEy7vTdx+xekjPCV5cIHOGCkdn3pIxo9vU7gEZMIw0SvAEdsy6Uh17xaCpm8V0FWvR0SZoK9Ik6jGOaug==",
+ "license": "MIT"
+ },
+ "node_modules/@module-federation/runtime": {
+ "version": "0.22.0",
+ "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.22.0.tgz",
+ "integrity": "sha512-38g5iPju2tPC3KHMPxRKmy4k4onNp6ypFPS1eKGsNLUkXgHsPMBFqAjDw96iEcjri91BrahG4XcdyKi97xZzlA==",
+ "license": "MIT",
+ "dependencies": {
+ "@module-federation/error-codes": "0.22.0",
+ "@module-federation/runtime-core": "0.22.0",
+ "@module-federation/sdk": "0.22.0"
+ }
+ },
+ "node_modules/@module-federation/runtime-core": {
+ "version": "0.22.0",
+ "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.22.0.tgz",
+ "integrity": "sha512-GR1TcD6/s7zqItfhC87zAp30PqzvceoeDGYTgF3Vx2TXvsfDrhP6Qw9T4vudDQL3uJRne6t7CzdT29YyVxlgIA==",
+ "license": "MIT",
+ "dependencies": {
+ "@module-federation/error-codes": "0.22.0",
+ "@module-federation/sdk": "0.22.0"
+ }
+ },
+ "node_modules/@module-federation/runtime-tools": {
+ "version": "0.22.0",
+ "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.22.0.tgz",
+ "integrity": "sha512-4ScUJ/aUfEernb+4PbLdhM/c60VHl698Gn1gY21m9vyC1Ucn69fPCA1y2EwcCB7IItseRMoNhdcWQnzt/OPCNA==",
+ "license": "MIT",
+ "dependencies": {
+ "@module-federation/runtime": "0.22.0",
+ "@module-federation/webpack-bundler-runtime": "0.22.0"
+ }
+ },
+ "node_modules/@module-federation/sdk": {
+ "version": "0.22.0",
+ "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.22.0.tgz",
+ "integrity": "sha512-x4aFNBKn2KVQRuNVC5A7SnrSCSqyfIWmm1DvubjbO9iKFe7ith5niw8dqSFBekYBg2Fwy+eMg4sEFNVvCAdo6g==",
+ "license": "MIT"
+ },
+ "node_modules/@module-federation/webpack-bundler-runtime": {
+ "version": "0.22.0",
+ "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.22.0.tgz",
+ "integrity": "sha512-aM8gCqXu+/4wBmJtVeMeeMN5guw3chf+2i6HajKtQv7SJfxV/f4IyNQJUeUQu9HfiAZHjqtMV5Lvq/Lvh8LdyA==",
+ "license": "MIT",
+ "dependencies": {
+ "@module-federation/runtime": "0.22.0",
+ "@module-federation/sdk": "0.22.0"
+ }
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz",
+ "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.5.0",
+ "@emnapi/runtime": "^1.5.0",
+ "@tybys/wasm-util": "^0.10.1"
+ }
+ },
+ "node_modules/@remotion/bundler": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/bundler/-/bundler-4.0.531.tgz",
+ "integrity": "sha512-Dv7LjGA/Uhy9H1D7vrrrwZJyL8hT70o8jxW8LsZbBYsrtn+N2Ntzt1mukdj1NM4xFLBjz8qY5flyJyNEdficSA==",
+ "license": "SEE LICENSE IN LICENSE.md",
+ "dependencies": {
+ "@remotion/media-parser": "4.0.531",
+ "@remotion/studio": "4.0.531",
+ "@remotion/studio-shared": "4.0.531",
+ "@remotion/timeline-utils": "4.0.531",
+ "@rspack/core": "1.7.11",
+ "@rspack/plugin-react-refresh": "1.6.1",
+ "css-loader": "7.1.4",
+ "esbuild": "0.28.1",
+ "react-refresh": "0.18.0",
+ "remotion": "4.0.531",
+ "style-loader": "4.0.0",
+ "webpack": "5.105.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@remotion/canvas": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/canvas/-/canvas-4.0.531.tgz",
+ "integrity": "sha512-VINlJX8nLozdf55lkgFj/hoILPuyBaXIzkwpaoBLh3C+heLDKf4LvfXh8JXorgCNDiwv7uKlkw6gsbkAIgcCGw==",
+ "license": "Remotion License",
+ "dependencies": {
+ "@remotion/player": "4.0.531",
+ "@remotion/studio-shared": "4.0.531",
+ "remotion": "4.0.531"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@remotion/captions": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/captions/-/captions-4.0.531.tgz",
+ "integrity": "sha512-/T1VsjEhAM0ShTuRIx2M0cnAkADGeqxqK+A0MyEZefU4p/ddD25qul6Sk73MdSovKy8tx/jD+e6xnkogOLHIOA==",
+ "license": "MIT"
+ },
+ "node_modules/@remotion/cli": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/cli/-/cli-4.0.531.tgz",
+ "integrity": "sha512-p99o3iG4NOMTneeqIDVCDEHU61GR6x5VdDSkjpfjHn7oKhp5sPu02mQEWG3lzdYa0r40xkdankQ1Blztqo8zJg==",
+ "license": "SEE LICENSE IN LICENSE.md",
+ "dependencies": {
+ "@babel/parser": "7.24.1",
+ "@remotion/bundler": "4.0.531",
+ "@remotion/media-utils": "4.0.531",
+ "@remotion/player": "4.0.531",
+ "@remotion/renderer": "4.0.531",
+ "@remotion/studio": "4.0.531",
+ "@remotion/studio-server": "4.0.531",
+ "@remotion/studio-shared": "4.0.531",
+ "dotenv": "17.3.1",
+ "minimist": "1.2.6",
+ "prompts": "2.4.2",
+ "remotion": "4.0.531",
+ "semver": "7.5.3"
+ },
+ "bin": {
+ "remotion": "remotion-cli.js",
+ "remotionb": "remotionb-cli.js",
+ "remotiond": "remotiond-cli.js"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@remotion/codemods": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/codemods/-/codemods-4.0.531.tgz",
+ "integrity": "sha512-rkzC3nda0dsYwElqKxgtzTL6sCqo5K9i0lLEah8CeC0naYOA85w9ySaY+/OdQZfsOWX9LNCZBrR9nwo4hoQ49Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "7.24.1",
+ "@babel/types": "7.24.0",
+ "@remotion/studio-protocol": "4.0.531",
+ "@remotion/studio-shared": "4.0.531",
+ "ast-types": "0.16.1",
+ "recast": "0.23.21",
+ "remotion": "4.0.531"
+ }
+ },
+ "node_modules/@remotion/compositor-darwin-arm64": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/compositor-darwin-arm64/-/compositor-darwin-arm64-4.0.531.tgz",
+ "integrity": "sha512-4EUrY22YfNrlXAe2PVg3wJWGLwFnVlHTwmptPJ6ZlYnN8sGYfYZkACTVTLOTO/YmLkRb9I+md1cH2mBX0ugAuQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@remotion/compositor-darwin-x64": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/compositor-darwin-x64/-/compositor-darwin-x64-4.0.531.tgz",
+ "integrity": "sha512-Uhr8cHwYhvYVYS+/Sl+RHyc0QGey+h9lsr1dEVeVKeGVQOESI5L0Xz0enyuj5vO6lgSxhBnXrFRmyOJ/JhtkiQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@remotion/compositor-linux-arm64-gnu": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-arm64-gnu/-/compositor-linux-arm64-gnu-4.0.531.tgz",
+ "integrity": "sha512-tOzrR5AqWVaQ25uPoYBRmOul1NEtb3CAElMriIfGDJqOEqXJZ+PzcULnjyfyj+B0Q38c27a6sF6UcXVAfyHBTw==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@remotion/compositor-linux-arm64-musl": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-arm64-musl/-/compositor-linux-arm64-musl-4.0.531.tgz",
+ "integrity": "sha512-Q2Pt6p9M9WRXaKmd1jYngSMDac42Kj3Cha4n2coTSxiqvMtAtKq7+I+LerGratYw8HsqGJx2SrUwfRH1UMf54g==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@remotion/compositor-linux-x64-gnu": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-x64-gnu/-/compositor-linux-x64-gnu-4.0.531.tgz",
+ "integrity": "sha512-o9L1kBAiDVK5SGUpsgXKAeUj1uj2JUR8NoUYSo1nIWFd2Dgt/zRvyMtusp9ojofmiFQhvZszpOSbEB58muMc8Q==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@remotion/compositor-linux-x64-musl": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-x64-musl/-/compositor-linux-x64-musl-4.0.531.tgz",
+ "integrity": "sha512-gn9OPNe4VTKwt91EtZWoGdlLjc+FTDwsQxeynfBA6favxkzYZl5DMyi24xwh0blHVrzMuzXY/neywMqKlc9Lig==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@remotion/compositor-win32-x64-msvc": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/compositor-win32-x64-msvc/-/compositor-win32-x64-msvc-4.0.531.tgz",
+ "integrity": "sha512-q2/xGTcN+ZLTrPzTXORf+z6GretJEF2Vi2OTgLxV1S3tDOcBx4K16XAUM2KuieKv2E9hYO9VsU1AyQanJOyT5g==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@remotion/elevenlabs": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/elevenlabs/-/elevenlabs-4.0.531.tgz",
+ "integrity": "sha512-h6rN4dEjaaVqev3Z3vMaqjeKmB9PneiRQRqsuoVrL7TG6YzG4tWub27kri/y6NMGLbDWF49suK0ucfx0aPzJxA==",
+ "license": "MIT",
+ "dependencies": {
+ "@remotion/captions": "4.0.531",
+ "zod": "4.5.4"
+ }
+ },
+ "node_modules/@remotion/licensing": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/licensing/-/licensing-4.0.531.tgz",
+ "integrity": "sha512-h17IbJSbD8GmkwJrayVJFUGCjIPTR4uj1AVOWQQYfBfv7/mgPNtCZPXDths9yQOphFXfwPq4IRRIZFPXYJFJZw==",
+ "license": "MIT"
+ },
+ "node_modules/@remotion/media": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/media/-/media-4.0.531.tgz",
+ "integrity": "sha512-MQhJqvn0WC51hBUyqHsR368gYrN+kyZe0HWX652UqxQ601iOHGIJyIHUAx/YNDxOE9qoBt85M+5r0wKULKnrvw==",
+ "dependencies": {
+ "mediabunny": "1.56.1",
+ "remotion": "4.0.531",
+ "zod": "4.5.4"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@remotion/media-parser": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/media-parser/-/media-parser-4.0.531.tgz",
+ "integrity": "sha512-R8z0K2vPACQ5wsxOHnykIN2vbDpfyobGuuScJxtCEfH5IycuS1TL+JGnd1cFJGMg3+XPePeenJjEJPsrrni/TA==",
+ "license": "Remotion License https://remotion.dev/license"
+ },
+ "node_modules/@remotion/media-utils": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/media-utils/-/media-utils-4.0.531.tgz",
+ "integrity": "sha512-z3Zi8nqAfYrMG3Kkqji5KeAavNmF/af2jNffSsSKLfNoIcg+tgmsKeLy5srtLVrDdfgku8E8Jootnq2IE8emwA==",
+ "license": "MIT",
+ "dependencies": {
+ "mediabunny": "1.56.1",
+ "remotion": "4.0.531"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@remotion/openai-whisper": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/openai-whisper/-/openai-whisper-4.0.531.tgz",
+ "integrity": "sha512-RUfZ/1EjvvyY6fGpAbYrLaUWmiqys6UCwd5+ozcD/0NkDLsg9vcmyED5MnZnsLRO8eDaLRzjGbL+QuA/o8ePGg==",
+ "license": "MIT",
+ "dependencies": {
+ "@remotion/captions": "4.0.531",
+ "zod": "4.5.4"
+ }
+ },
+ "node_modules/@remotion/paths": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/paths/-/paths-4.0.531.tgz",
+ "integrity": "sha512-fpSoVjz5mSa5whVOy4TvCWwDPHaVDIhf9x1nusxa2faNi/Y6IPzNUq2WHtxoZ5gxd9iExgDu4VGjf1arVnPLOQ==",
+ "license": "MIT",
+ "dependencies": {
+ "remotion": "4.0.531"
+ }
+ },
+ "node_modules/@remotion/player": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/player/-/player-4.0.531.tgz",
+ "integrity": "sha512-a2iq13CQvUJMXq2Lf9aJD1TOyQ0Z44n4dEtHjOV1i4P8z6fjnvVDH19n+5ya/NLmakBdLyYmTghF3t0962b92g==",
+ "license": "SEE LICENSE IN LICENSE.md",
+ "dependencies": {
+ "remotion": "4.0.531"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@remotion/renderer": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/renderer/-/renderer-4.0.531.tgz",
+ "integrity": "sha512-xsPrN0gmweNjEhpt9bOLUDV9odfPlVrUJkn4tOv4TA621X33U/gejfGdvMbOM/kPkZ62Bx6P3rY3eBCgFGQnkw==",
+ "license": "SEE LICENSE IN LICENSE.md",
+ "dependencies": {
+ "@remotion/licensing": "4.0.531",
+ "@remotion/streaming": "4.0.531",
+ "execa": "5.1.1",
+ "remotion": "4.0.531",
+ "source-map": "0.8.0",
+ "ws": "8.21.0"
+ },
+ "optionalDependencies": {
+ "@remotion/compositor-darwin-arm64": "4.0.531",
+ "@remotion/compositor-darwin-x64": "4.0.531",
+ "@remotion/compositor-linux-arm64-gnu": "4.0.531",
+ "@remotion/compositor-linux-arm64-musl": "4.0.531",
+ "@remotion/compositor-linux-x64-gnu": "4.0.531",
+ "@remotion/compositor-linux-x64-musl": "4.0.531",
+ "@remotion/compositor-win32-x64-msvc": "4.0.531"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@remotion/shapes": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/shapes/-/shapes-4.0.531.tgz",
+ "integrity": "sha512-0g2TdbC67PibqUv4GgTc6abQzuTBu2Si4R2mDQKxlBHwHIvwLRx4D3UgX6Ccux7kzkVkBnxlHdl4nZv0SxwQJw==",
+ "license": "MIT",
+ "dependencies": {
+ "@remotion/paths": "4.0.531",
+ "remotion": "4.0.531"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@remotion/streaming": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/streaming/-/streaming-4.0.531.tgz",
+ "integrity": "sha512-cCXg6HBvgTPCvMGxnpb+2hmwfaBmD+0VuRVX9yjDWzxoDoAG+abYq/JQHsSbJHQRnRkfwHaow73u1H1DOW27qQ==",
+ "license": "MIT"
+ },
+ "node_modules/@remotion/studio": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/studio/-/studio-4.0.531.tgz",
+ "integrity": "sha512-mFdI2tzKX8MiXDBpqIImV4LqPA3BNEg1jzn++h/vISltJQ6FuwMxXYSWa8QPlTW5zMaFEVODuPLhooGuMU8aWQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.31",
+ "@remotion/canvas": "4.0.531",
+ "@remotion/captions": "4.0.531",
+ "@remotion/codemods": "4.0.531",
+ "@remotion/elevenlabs": "4.0.531",
+ "@remotion/media": "4.0.531",
+ "@remotion/media-utils": "4.0.531",
+ "@remotion/openai-whisper": "4.0.531",
+ "@remotion/paths": "4.0.531",
+ "@remotion/player": "4.0.531",
+ "@remotion/renderer": "4.0.531",
+ "@remotion/studio-protocol": "4.0.531",
+ "@remotion/studio-shared": "4.0.531",
+ "@remotion/timeline-utils": "4.0.531",
+ "@remotion/web-renderer": "4.0.531",
+ "@remotion/zod-types": "4.0.531",
+ "@tanstack/react-virtual": "3.14.9",
+ "mediabunny": "1.56.1",
+ "memfs": "3.4.3",
+ "open": "8.4.2",
+ "prismjs": "1.30.0",
+ "remotion": "4.0.531",
+ "semver": "7.5.3",
+ "zod": "4.5.4"
+ },
+ "peerDependencies": {
+ "@remotion/video-matting": "4.0.531",
+ "@remotion/whisper-webgpu": "4.0.531",
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@remotion/video-matting": {
+ "optional": true
+ },
+ "@remotion/whisper-webgpu": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@remotion/studio-protocol": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/studio-protocol/-/studio-protocol-4.0.531.tgz",
+ "integrity": "sha512-8UewGkNOSRVNwgzKiAYZpHJo6QOdzuHpaP7pR503fIlPn5ZpDb771T8ZqJ91oWzoih6B4V6aHu6/Ny+jUkJoEw==",
+ "license": "Remotion License",
+ "dependencies": {
+ "zod": "4.5.4"
+ }
+ },
+ "node_modules/@remotion/studio-server": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/studio-server/-/studio-server-4.0.531.tgz",
+ "integrity": "sha512-HqyvUnFNb78j+ht/jEqykxzNVqyrFwr6suDOidOvSrwUhfb+dgorrolSjz7vLZmMGDvlSeo2333UcJ+WJnfGxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "7.24.1",
+ "@babel/types": "7.24.0",
+ "@remotion/bundler": "4.0.531",
+ "@remotion/codemods": "4.0.531",
+ "@remotion/renderer": "4.0.531",
+ "@remotion/studio-protocol": "4.0.531",
+ "@remotion/studio-shared": "4.0.531",
+ "@svgr/core": "8.1.0",
+ "@svgr/plugin-jsx": "8.1.0",
+ "kiwi-schema": "0.5.0",
+ "memfs": "3.4.3",
+ "open": "8.4.2",
+ "recast": "0.23.21",
+ "remotion": "4.0.531",
+ "semver": "7.5.3",
+ "zod": "4.5.4"
+ }
+ },
+ "node_modules/@remotion/studio-shared": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/studio-shared/-/studio-shared-4.0.531.tgz",
+ "integrity": "sha512-no/I+Tt6df/guYRpBFGEj/JOS532pmyGCQfor6wfLDjW4zWDmhsehny1JZrYHWgg25DVrnl731JlkfVaoZIF8A==",
+ "license": "MIT",
+ "dependencies": {
+ "remotion": "4.0.531"
+ }
+ },
+ "node_modules/@remotion/timeline-utils": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/timeline-utils/-/timeline-utils-4.0.531.tgz",
+ "integrity": "sha512-XE2ikcvYYgxZW57TUE3UEaJVR4zpujOsHq7rmxucqnaDqyu0N6+tGXpJK+MKzW0RVsYik1nQ8Gv0NSp4DjaL6w==",
+ "license": "MIT",
+ "dependencies": {
+ "mediabunny": "1.56.1",
+ "remotion": "4.0.531"
+ }
+ },
+ "node_modules/@remotion/transitions": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/transitions/-/transitions-4.0.531.tgz",
+ "integrity": "sha512-WQJCitaF0guxYxZuCujasL6WeFnpOaObmM8nzwduEJizE0HNGq8e8ragRznpt2op7osfri8/eHjKzVRKBsngjw==",
+ "license": "UNLICENSED",
+ "dependencies": {
+ "@remotion/paths": "4.0.531",
+ "@remotion/shapes": "4.0.531",
+ "remotion": "4.0.531"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@remotion/web-renderer": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/web-renderer/-/web-renderer-4.0.531.tgz",
+ "integrity": "sha512-fIS7v0ZB97xGEKKKMvFQ/7/puV4ZqJRfoQQwczcGRp9WppJLO65IQYPYnWp206j0KBDo8Qff6CtICRIrd5C2Qw==",
+ "license": "SEE LICENSE IN LICENSE.md",
+ "dependencies": {
+ "@mediabunny/aac-encoder": "1.56.1",
+ "@mediabunny/flac-encoder": "1.56.1",
+ "@mediabunny/mp3-encoder": "1.56.1",
+ "@remotion/licensing": "4.0.531",
+ "mediabunny": "1.56.1",
+ "remotion": "4.0.531"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@remotion/zod-types": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/@remotion/zod-types/-/zod-types-4.0.531.tgz",
+ "integrity": "sha512-EWaHyq1i2ATtVRbpNiQlS2DCub6Rc5L0eU5OgYBFfY4TWQ3yihXgt3X3XIOFwLLA63ZAEzBEpZXUk4KBC3inOw==",
+ "license": "MIT",
+ "dependencies": {
+ "remotion": "4.0.531"
+ }
+ },
+ "node_modules/@rspack/binding": {
+ "version": "1.7.11",
+ "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.7.11.tgz",
+ "integrity": "sha512-2MGdy2s2HimsDT444Bp5XnALzNRxuBNc7y0JzyuqKbHBywd4x2NeXyhWXXoxufaCFu5PBc9Qq9jyfjW2Aeh06Q==",
+ "license": "MIT",
+ "optionalDependencies": {
+ "@rspack/binding-darwin-arm64": "1.7.11",
+ "@rspack/binding-darwin-x64": "1.7.11",
+ "@rspack/binding-linux-arm64-gnu": "1.7.11",
+ "@rspack/binding-linux-arm64-musl": "1.7.11",
+ "@rspack/binding-linux-x64-gnu": "1.7.11",
+ "@rspack/binding-linux-x64-musl": "1.7.11",
+ "@rspack/binding-wasm32-wasi": "1.7.11",
+ "@rspack/binding-win32-arm64-msvc": "1.7.11",
+ "@rspack/binding-win32-ia32-msvc": "1.7.11",
+ "@rspack/binding-win32-x64-msvc": "1.7.11"
+ }
+ },
+ "node_modules/@rspack/binding-darwin-arm64": {
+ "version": "1.7.11",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.7.11.tgz",
+ "integrity": "sha512-oduECiZVqbO5zlVw+q7Vy65sJFth99fWPTyucwvLJJtJkPL5n17Uiql2cYP6Ijn0pkqtf1SXgK8WjiKLG5bIig==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rspack/binding-darwin-x64": {
+ "version": "1.7.11",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.7.11.tgz",
+ "integrity": "sha512-a1+TtTE9ap6RalgFi7FGIgkJP6O4Vy6ctv+9WGJy53E4kuqHR0RygzaiVxCI/GMc/vBT9vY23hyrpWb3d1vtXA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rspack/binding-linux-arm64-gnu": {
+ "version": "1.7.11",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.7.11.tgz",
+ "integrity": "sha512-P0QrGRPbTWu6RKWfN0bDtbnEps3rXH0MWIMreZABoUrVmNQKtXR6e73J3ub6a+di5s2+K0M2LJ9Bh2/H4UsDUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rspack/binding-linux-arm64-musl": {
+ "version": "1.7.11",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.7.11.tgz",
+ "integrity": "sha512-6ky7R43VMjWwmx3Yx7Jl7faLBBMAgMDt+/bN35RgwjiPgsIByz65EwytUVuW9rikB43BGHvA/eqlnjLrUzNBqw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rspack/binding-linux-x64-gnu": {
+ "version": "1.7.11",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.7.11.tgz",
+ "integrity": "sha512-cuOJMfCOvb2Wgsry5enXJ3iT1FGUjdPqtGUBVupQlEG4ntSYsQ2PtF4wIDVasR3wdxC5nQbipOrDiN/u6fYsdQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rspack/binding-linux-x64-musl": {
+ "version": "1.7.11",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.7.11.tgz",
+ "integrity": "sha512-CoK37hva4AmHGh3VCsQXmGr40L36m1/AdnN5LEjUX6kx5rEH7/1nEBN6Ii72pejqDVvk9anEROmPDiPw10tpFg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rspack/binding-wasm32-wasi": {
+ "version": "1.7.11",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.7.11.tgz",
+ "integrity": "sha512-OtrmnPUVJMxjNa3eDMfHyPdtlLRmmp/aIm0fQHlAOATbZvlGm12q7rhPW5BXTu1yh+1rQ1/uqvz+SzKEZXuJaQ==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "1.0.7"
+ }
+ },
+ "node_modules/@rspack/binding-win32-arm64-msvc": {
+ "version": "1.7.11",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.7.11.tgz",
+ "integrity": "sha512-lObFW6e5lCWNgTBNwT//yiEDbsxm9QG4BYUojqeXxothuzJ/L6ibXz6+gLMvbOvLGV3nKgkXmx8GvT9WDKR0mA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rspack/binding-win32-ia32-msvc": {
+ "version": "1.7.11",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.7.11.tgz",
+ "integrity": "sha512-0pYGnZd8PPqNR68zQ8skamqNAXEA1sUfXuAdYcknIIRq2wsbiwFzIc0Pov1cIfHYab37G7sSIPBiOUdOWF5Ivw==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rspack/binding-win32-x64-msvc": {
+ "version": "1.7.11",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.7.11.tgz",
+ "integrity": "sha512-EeQXayoQk/uBkI3pdoXfQBXNIUrADq56L3s/DFyM2pJeUDrWmhfIw2UFIGkYPTMSCo8F2JcdcGM32FGJrSnU0Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rspack/core": {
+ "version": "1.7.11",
+ "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.7.11.tgz",
+ "integrity": "sha512-rsD9b+Khmot5DwCMiB3cqTQo53ioPG3M/A7BySu8+0+RS7GCxKm+Z+mtsjtG/vsu4Tn2tcqCdZtA3pgLoJB+ew==",
+ "license": "MIT",
+ "dependencies": {
+ "@module-federation/runtime-tools": "0.22.0",
+ "@rspack/binding": "1.7.11",
+ "@rspack/lite-tapable": "1.1.0"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ },
+ "peerDependencies": {
+ "@swc/helpers": ">=0.5.1"
+ },
+ "peerDependenciesMeta": {
+ "@swc/helpers": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rspack/lite-tapable": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rspack/lite-tapable/-/lite-tapable-1.1.0.tgz",
+ "integrity": "sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw==",
+ "license": "MIT"
+ },
+ "node_modules/@rspack/plugin-react-refresh": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@rspack/plugin-react-refresh/-/plugin-react-refresh-1.6.1.tgz",
+ "integrity": "sha512-eqqW5645VG3CzGzFgNg5HqNdHVXY+567PGjtDhhrM8t67caxmsSzRmT5qfoEIfBcGgFkH9vEg7kzXwmCYQdQDw==",
+ "license": "MIT",
+ "dependencies": {
+ "error-stack-parser": "^2.1.4",
+ "html-entities": "^2.6.0"
+ },
+ "peerDependencies": {
+ "react-refresh": ">=0.10.0 <1.0.0",
+ "webpack-hot-middleware": "2.x"
+ },
+ "peerDependenciesMeta": {
+ "webpack-hot-middleware": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@svgr/babel-plugin-add-jsx-attribute": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz",
+ "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-remove-jsx-attribute": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz",
+ "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz",
+ "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz",
+ "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-svg-dynamic-title": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz",
+ "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-svg-em-dimensions": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz",
+ "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-transform-react-native-svg": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz",
+ "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-transform-svg-component": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz",
+ "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-preset": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz",
+ "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==",
+ "license": "MIT",
+ "dependencies": {
+ "@svgr/babel-plugin-add-jsx-attribute": "8.0.0",
+ "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0",
+ "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0",
+ "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0",
+ "@svgr/babel-plugin-svg-dynamic-title": "8.0.0",
+ "@svgr/babel-plugin-svg-em-dimensions": "8.0.0",
+ "@svgr/babel-plugin-transform-react-native-svg": "8.1.0",
+ "@svgr/babel-plugin-transform-svg-component": "8.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/core": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz",
+ "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.21.3",
+ "@svgr/babel-preset": "8.1.0",
+ "camelcase": "^6.2.0",
+ "cosmiconfig": "^8.1.3",
+ "snake-case": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/hast-util-to-babel-ast": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz",
+ "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.21.3",
+ "entities": "^4.4.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/plugin-jsx": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz",
+ "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.21.3",
+ "@svgr/babel-preset": "8.1.0",
+ "@svgr/hast-util-to-babel-ast": "8.0.0",
+ "svg-parser": "^2.0.4"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@svgr/core": "*"
+ }
+ },
+ "node_modules/@tanstack/react-virtual": {
+ "version": "3.14.9",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.9.tgz",
+ "integrity": "sha512-qZyr0FZDP8rDC4WBhsryIZmAd9bveJvFGUJJtskWaew6/0dTRS6wZxnR6VQ5bY2KwL3LjerrHqQLk3a0GKcPXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/virtual-core": "3.17.7"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@tanstack/virtual-core": {
+ "version": "3.17.7",
+ "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.7.tgz",
+ "integrity": "sha512-bp+v10y65sp2H7WpWfIMyxTNfl8ZVfxFTLRjPIFRryi6FV/J33z4IS53WO4pTk36KlvJ4iLiQz+oaydDC1xbcA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.4",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz",
+ "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/dom-mediacapture-transform": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/@types/dom-mediacapture-transform/-/dom-mediacapture-transform-0.1.12.tgz",
+ "integrity": "sha512-d7/QsLRwF864A5mgIM/YrfiglHoYn7zgCcAoJgW404r+2DwnNr7EBbLnCWpmOMgH8y0te73L1AV6H1bmauaWFw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/dom-webcodecs": "*"
+ }
+ },
+ "node_modules/@types/dom-webcodecs": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.13.tgz",
+ "integrity": "sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/eslint": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
+ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "*",
+ "@types/json-schema": "*"
+ }
+ },
+ "node_modules/@types/eslint-scope": {
+ "version": "3.7.7",
+ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
+ "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/eslint": "*",
+ "@types/estree": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "26.6.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.5.tgz",
+ "integrity": "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~8.9.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.1.12",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz",
+ "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@webassemblyjs/ast": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
+ "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/helper-numbers": "1.13.2",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2"
+ }
+ },
+ "node_modules/@webassemblyjs/floating-point-hex-parser": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
+ "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
+ "license": "MIT"
+ },
+ "node_modules/@webassemblyjs/helper-api-error": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
+ "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
+ "license": "MIT"
+ },
+ "node_modules/@webassemblyjs/helper-buffer": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
+ "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
+ "license": "MIT"
+ },
+ "node_modules/@webassemblyjs/helper-numbers": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz",
+ "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/floating-point-hex-parser": "1.13.2",
+ "@webassemblyjs/helper-api-error": "1.13.2",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "node_modules/@webassemblyjs/helper-wasm-bytecode": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
+ "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
+ "license": "MIT"
+ },
+ "node_modules/@webassemblyjs/helper-wasm-section": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz",
+ "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@webassemblyjs/helper-buffer": "1.14.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+ "@webassemblyjs/wasm-gen": "1.14.1"
+ }
+ },
+ "node_modules/@webassemblyjs/ieee754": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz",
+ "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
+ "license": "MIT",
+ "dependencies": {
+ "@xtuc/ieee754": "^1.2.0"
+ }
+ },
+ "node_modules/@webassemblyjs/leb128": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz",
+ "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "node_modules/@webassemblyjs/utf8": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
+ "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
+ "license": "MIT"
+ },
+ "node_modules/@webassemblyjs/wasm-edit": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz",
+ "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@webassemblyjs/helper-buffer": "1.14.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+ "@webassemblyjs/helper-wasm-section": "1.14.1",
+ "@webassemblyjs/wasm-gen": "1.14.1",
+ "@webassemblyjs/wasm-opt": "1.14.1",
+ "@webassemblyjs/wasm-parser": "1.14.1",
+ "@webassemblyjs/wast-printer": "1.14.1"
+ }
+ },
+ "node_modules/@webassemblyjs/wasm-gen": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz",
+ "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+ "@webassemblyjs/ieee754": "1.13.2",
+ "@webassemblyjs/leb128": "1.13.2",
+ "@webassemblyjs/utf8": "1.13.2"
+ }
+ },
+ "node_modules/@webassemblyjs/wasm-opt": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz",
+ "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@webassemblyjs/helper-buffer": "1.14.1",
+ "@webassemblyjs/wasm-gen": "1.14.1",
+ "@webassemblyjs/wasm-parser": "1.14.1"
+ }
+ },
+ "node_modules/@webassemblyjs/wasm-parser": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz",
+ "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@webassemblyjs/helper-api-error": "1.13.2",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+ "@webassemblyjs/ieee754": "1.13.2",
+ "@webassemblyjs/leb128": "1.13.2",
+ "@webassemblyjs/utf8": "1.13.2"
+ }
+ },
+ "node_modules/@webassemblyjs/wast-printer": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz",
+ "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "node_modules/@xtuc/ieee754": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
+ "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@xtuc/long": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
+ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/acorn": {
+ "version": "8.19.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz",
+ "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==",
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-import-phases": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
+ "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "peerDependencies": {
+ "acorn": "^8.14.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
+ "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ajv-keywords": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
+ "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3"
+ },
+ "peerDependencies": {
+ "ajv": "^8.8.2"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "license": "Python-2.0"
+ },
+ "node_modules/ast-types": {
+ "version": "0.16.1",
+ "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz",
+ "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.11.29",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.29.tgz",
+ "integrity": "sha512-YeesxhUmR4h5y5V8KF8iBCqJb8YFrBJ++XBTeGcWIaQOYXGN19JM0FoiOjAhwuDoln8owMgXcWBmgHtYSkky2w==",
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.29.3",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz",
+ "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.11.26",
+ "caniuse-lite": "^1.0.30001813",
+ "electron-to-chromium": "^1.5.439",
+ "node-releases": "^2.0.57",
+ "update-browserslist-db": "^1.3.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "license": "MIT"
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001816",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001816.tgz",
+ "integrity": "sha512-+8Yx7BXCoqk6IF8yTicjfUR6D/ldJzdlsodlEOiOSPmhK844WIgk67/zIQaGAOxNkZyyvBlrRFnLvqKIoqbAFw==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chrome-trace-event": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
+ "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "license": "MIT"
+ },
+ "node_modules/cosmiconfig": {
+ "version": "8.3.6",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
+ "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
+ "license": "MIT",
+ "dependencies": {
+ "import-fresh": "^3.3.0",
+ "js-yaml": "^4.1.0",
+ "parse-json": "^5.2.0",
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/d-fischer"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.9.5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/css-loader": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.4.tgz",
+ "integrity": "sha512-vv3J9tlOl04WjiMvHQI/9tmIrCxVrj6PFbHemBB1iihpeRbi/I4h033eoFIhwxBBqLhI0KYFS7yvynBFhIZfTw==",
+ "license": "MIT",
+ "dependencies": {
+ "icss-utils": "^5.1.0",
+ "postcss": "^8.4.40",
+ "postcss-modules-extract-imports": "^3.1.0",
+ "postcss-modules-local-by-default": "^4.0.5",
+ "postcss-modules-scope": "^3.2.0",
+ "postcss-modules-values": "^4.0.0",
+ "postcss-value-parser": "^4.2.0",
+ "semver": "^7.6.3"
+ },
+ "engines": {
+ "node": ">= 18.12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0",
+ "webpack": "^5.27.0"
+ },
+ "peerDependenciesMeta": {
+ "@rspack/core": {
+ "optional": true
+ },
+ "webpack": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/css-loader/node_modules/semver": {
+ "version": "7.8.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/define-lazy-prop": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
+ "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dot-case": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
+ "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==",
+ "license": "MIT",
+ "dependencies": {
+ "no-case": "^3.0.4",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "17.3.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz",
+ "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.457",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.457.tgz",
+ "integrity": "sha512-RNKsgErRmkzRgC8nOV1wzFhSbfGlYJ6k4LT/aE7oFrDYaQgHhTh5oTp0ZgSbRRybKR8HlZIdCWc38Y+CDTk1Jw==",
+ "license": "ISC"
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.26.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.26.0.tgz",
+ "integrity": "sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/error-stack-parser": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
+ "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
+ "license": "MIT",
+ "dependencies": {
+ "stackframe": "^1.3.4"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz",
+ "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==",
+ "license": "MIT"
+ },
+ "node_modules/esbuild": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
+ "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.28.1",
+ "@esbuild/android-arm": "0.28.1",
+ "@esbuild/android-arm64": "0.28.1",
+ "@esbuild/android-x64": "0.28.1",
+ "@esbuild/darwin-arm64": "0.28.1",
+ "@esbuild/darwin-x64": "0.28.1",
+ "@esbuild/freebsd-arm64": "0.28.1",
+ "@esbuild/freebsd-x64": "0.28.1",
+ "@esbuild/linux-arm": "0.28.1",
+ "@esbuild/linux-arm64": "0.28.1",
+ "@esbuild/linux-ia32": "0.28.1",
+ "@esbuild/linux-loong64": "0.28.1",
+ "@esbuild/linux-mips64el": "0.28.1",
+ "@esbuild/linux-ppc64": "0.28.1",
+ "@esbuild/linux-riscv64": "0.28.1",
+ "@esbuild/linux-s390x": "0.28.1",
+ "@esbuild/linux-x64": "0.28.1",
+ "@esbuild/netbsd-arm64": "0.28.1",
+ "@esbuild/netbsd-x64": "0.28.1",
+ "@esbuild/openbsd-arm64": "0.28.1",
+ "@esbuild/openbsd-x64": "0.28.1",
+ "@esbuild/openharmony-arm64": "0.28.1",
+ "@esbuild/sunos-x64": "0.28.1",
+ "@esbuild/win32-arm64": "0.28.1",
+ "@esbuild/win32-ia32": "0.28.1",
+ "@esbuild/win32-x64": "0.28.1"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esrecurse/node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/events": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.x"
+ }
+ },
+ "node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz",
+ "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/fs-monkey": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz",
+ "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==",
+ "license": "Unlicense"
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob-to-regexp": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
+ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/html-entities": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz",
+ "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/mdevils"
+ },
+ {
+ "type": "patreon",
+ "url": "https://patreon.com/mdevils"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
+ "node_modules/icss-utils": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
+ "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
+ "license": "ISC",
+ "engines": {
+ "node": "^10 || ^12 || >= 14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "license": "MIT"
+ },
+ "node_modules/is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "license": "MIT",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "license": "MIT",
+ "dependencies": {
+ "is-docker": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/jest-worker": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
+ "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz",
+ "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/kiwi-schema": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/kiwi-schema/-/kiwi-schema-0.5.0.tgz",
+ "integrity": "sha512-X+FpfU0yTEtc6aTHS7VwbOpvQwRt70+pXXWRI5fd6CvWhe7pSVC854TVo4Zo0x5/wwcWj+/9KUlXpdcP0dY9AA==",
+ "license": "MIT",
+ "bin": {
+ "kiwic": "cli.js"
+ }
+ },
+ "node_modules/kleur": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
+ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "license": "MIT"
+ },
+ "node_modules/loader-runner": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz",
+ "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.11.5"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/lower-case": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
+ "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/mediabunny": {
+ "version": "1.56.1",
+ "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.56.1.tgz",
+ "integrity": "sha512-DNOm0bRBmeOxJgCK/g3LxNWbxY5morTauNdlychujGmJ9L37ezVezkNoI1WtO2Xdb4WlWWJlYfIKnOEzd5phRw==",
+ "license": "MPL-2.0",
+ "workspaces": [
+ ".",
+ "packages/*"
+ ],
+ "dependencies": {
+ "@types/dom-mediacapture-transform": "^0.1.11",
+ "@types/dom-webcodecs": "0.1.13"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/Vanilagy"
+ }
+ },
+ "node_modules/memfs": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.3.tgz",
+ "integrity": "sha512-eivjfi7Ahr6eQTn44nvTnR60e4a1Fs1Via2kCR5lHo/kyNoiMWaXCNJ/GpSd0ilXas2JSOl9B5FTIhflXu0hlg==",
+ "license": "Unlicense",
+ "dependencies": {
+ "fs-monkey": "1.0.3"
+ },
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "license": "MIT"
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
+ "license": "MIT"
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.20",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
+ "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "license": "MIT"
+ },
+ "node_modules/no-case": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
+ "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
+ "license": "MIT",
+ "dependencies": {
+ "lower-case": "^2.0.2",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.59",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.59.tgz",
+ "integrity": "sha512-5qr2okC4ItAI0hG5SaV0HoYMpnM/Y6+Oqr7AmPYy/2uEoGvS+GbSKLeRxSS1uAdu59yz0ouzcuXievSgg///WA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/open": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
+ "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
+ "license": "MIT",
+ "dependencies": {
+ "define-lazy-prop": "^2.0.0",
+ "is-docker": "^2.1.1",
+ "is-wsl": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/postcss": {
+ "version": "8.5.30",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.30.tgz",
+ "integrity": "sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.20",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-modules-extract-imports": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz",
+ "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==",
+ "license": "ISC",
+ "engines": {
+ "node": "^10 || ^12 || >= 14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/postcss-modules-local-by-default": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz",
+ "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==",
+ "license": "MIT",
+ "dependencies": {
+ "icss-utils": "^5.0.0",
+ "postcss-selector-parser": "^7.0.0",
+ "postcss-value-parser": "^4.1.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >= 14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/postcss-modules-scope": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz",
+ "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==",
+ "license": "ISC",
+ "dependencies": {
+ "postcss-selector-parser": "^7.0.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >= 14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/postcss-modules-values": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz",
+ "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==",
+ "license": "ISC",
+ "dependencies": {
+ "icss-utils": "^5.0.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >= 14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "license": "MIT"
+ },
+ "node_modules/prismjs": {
+ "version": "1.30.0",
+ "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
+ "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/prompts": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
+ "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "kleur": "^3.0.3",
+ "sisteransi": "^1.0.5"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.1.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
+ "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.1.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
+ "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.26.0"
+ },
+ "peerDependencies": {
+ "react": "^19.1.1"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
+ "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/recast": {
+ "version": "0.23.21",
+ "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.21.tgz",
+ "integrity": "sha512-mFAyJq9vUbSTARLZUvAEf1z3YxlvAwswbmxMx2mPA/MSm4KmpwvwvhsH/NIrZhyOuwD60Lzyw2qh83uCbgTPYw==",
+ "license": "MIT",
+ "dependencies": {
+ "ast-types": "^0.16.1",
+ "esprima": "~4.0.0",
+ "source-map": "~0.6.1",
+ "tiny-invariant": "^1.3.3",
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/recast/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/remotion": {
+ "version": "4.0.531",
+ "resolved": "https://registry.npmjs.org/remotion/-/remotion-4.0.531.tgz",
+ "integrity": "sha512-GchTilW0M1zk9NsYxK9pIMBeDQMiQ1TrIz3zhPpMiysf9P0rTUYwxkJ9bFANwykz4nlImpWRtUHSxZnhBYeDdw==",
+ "license": "SEE LICENSE IN LICENSE.md",
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.26.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
+ "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
+ "license": "MIT"
+ },
+ "node_modules/schema-utils": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.5.0.tgz",
+ "integrity": "sha512-zJlMCZ0cAR5p/Y4oVpRoqioDMJcGxaXRrQ/4rP4WyR84vc5z/DolXdbvXeDpTwbtocDFr2rhPqHPErDCUtz2kA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15",
+ "ajv": "^8.20.0",
+ "ajv-formats": "^3.0.1",
+ "ajv-keywords": "^5.1.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.5.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
+ "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
+ "license": "ISC",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/semver/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/semver/node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "license": "ISC"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "license": "ISC"
+ },
+ "node_modules/sisteransi": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
+ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
+ "license": "MIT"
+ },
+ "node_modules/snake-case": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz",
+ "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==",
+ "license": "MIT",
+ "dependencies": {
+ "dot-case": "^3.0.4",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0.tgz",
+ "integrity": "sha512-d8EqvL+k/SOXCreS/SUzg2ciyHqBBLcN/yuRjFsbvVhHTE2pgei7oAhmPM7kWFbkX6OSMQfUq4KbkF3au9lhYQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
+ "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/source-map-support/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stackframe": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
+ "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
+ "license": "MIT"
+ },
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/style-loader": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz",
+ "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 18.12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.27.0"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/svg-parser": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.1.0.tgz",
+ "integrity": "sha512-bwLf38YmY+TDYHJw1Ex0Co8c4yeXuJAo8YnXGZrscxrvYoVVIvLeniEkV1Ks/54VteMnL4FtyN1+P+TZJdUPmQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tapable": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
+ "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/terser": {
+ "version": "5.51.2",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.51.2.tgz",
+ "integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@jridgewell/source-map": "^0.3.3",
+ "acorn": "^8.15.0",
+ "commander": "^2.20.0",
+ "source-map-support": "~0.5.20"
+ },
+ "bin": {
+ "terser": "bin/terser"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/terser-webpack-plugin": {
+ "version": "5.6.1",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz",
+ "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jest-worker": "^27.4.5",
+ "schema-utils": "^4.3.0",
+ "terser": "^5.31.1"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.1.0"
+ },
+ "peerDependenciesMeta": {
+ "@minify-html/node": {
+ "optional": true
+ },
+ "@swc/core": {
+ "optional": true
+ },
+ "@swc/css": {
+ "optional": true
+ },
+ "@swc/html": {
+ "optional": true
+ },
+ "clean-css": {
+ "optional": true
+ },
+ "cssnano": {
+ "optional": true
+ },
+ "csso": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "html-minifier-terser": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ },
+ "uglify-js": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tiny-invariant": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
+ "license": "MIT"
+ },
+ "node_modules/to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/typescript": {
+ "version": "5.9.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
+ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "8.9.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz",
+ "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==",
+ "license": "MIT"
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz",
+ "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
+ "node_modules/watchpack": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.6.1.tgz",
+ "integrity": "sha512-8mk/LnXpQAiM2xKZhTkxikwly5GhL2Z+DWiLTkMa/E1Nfv9zURhHgiFyWvM4BWqW+LjIgL2AIS7JntEUx6v3vQ==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.1.2"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/webpack": {
+ "version": "5.105.0",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz",
+ "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/eslint-scope": "^3.7.7",
+ "@types/estree": "^1.0.8",
+ "@types/json-schema": "^7.0.15",
+ "@webassemblyjs/ast": "^1.14.1",
+ "@webassemblyjs/wasm-edit": "^1.14.1",
+ "@webassemblyjs/wasm-parser": "^1.14.1",
+ "acorn": "^8.15.0",
+ "acorn-import-phases": "^1.0.3",
+ "browserslist": "^4.28.1",
+ "chrome-trace-event": "^1.0.2",
+ "enhanced-resolve": "^5.19.0",
+ "es-module-lexer": "^2.0.0",
+ "eslint-scope": "5.1.1",
+ "events": "^3.2.0",
+ "glob-to-regexp": "^0.4.1",
+ "graceful-fs": "^4.2.11",
+ "json-parse-even-better-errors": "^2.3.1",
+ "loader-runner": "^4.3.1",
+ "mime-types": "^2.1.27",
+ "neo-async": "^2.6.2",
+ "schema-utils": "^4.3.3",
+ "tapable": "^2.3.0",
+ "terser-webpack-plugin": "^5.3.16",
+ "watchpack": "^2.5.1",
+ "webpack-sources": "^3.3.3"
+ },
+ "bin": {
+ "webpack": "bin/webpack.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependenciesMeta": {
+ "webpack-cli": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/webpack-sources": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.6.0.tgz",
+ "integrity": "sha512-EIMmPVvNI0CYXyRPp34F2Qk7W37/BZVZIofS6LAUnrNjCVahS+hO4mxwFJDjQjODTGZDtVzLYoN2+1dYkhk9qA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
+ "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "license": "ISC"
+ },
+ "node_modules/zod": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz",
+ "integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ }
+ }
+}
diff --git a/video/package.json b/video/package.json
new file mode 100644
index 0000000..67d2e39
--- /dev/null
+++ b/video/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "layeris-video",
+ "version": "1.0.0",
+ "private": true,
+ "description": "A short walkthrough video of layeris, built with Remotion",
+ "scripts": {
+ "assets": "python3 scripts/build_assets.py",
+ "start": "remotion studio",
+ "render": "remotion render LayerisVideo out/layeris.mp4",
+ "still": "remotion still LayerisVideo out/still.png",
+ "typecheck": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@fontsource/besley": "5.2.8",
+ "@fontsource/dm-mono": "5.2.7",
+ "@fontsource/schibsted-grotesk": "5.2.8",
+ "@remotion/cli": "4.0.531",
+ "@remotion/paths": "4.0.531",
+ "@remotion/transitions": "4.0.531",
+ "react": "19.1.1",
+ "react-dom": "19.1.1",
+ "remotion": "4.0.531"
+ },
+ "devDependencies": {
+ "@types/react": "19.1.12",
+ "typescript": "5.9.2"
+ }
+}
diff --git a/video/public/key.mp3 b/video/public/key.mp3
new file mode 100644
index 0000000..c915215
Binary files /dev/null and b/video/public/key.mp3 differ
diff --git a/video/public/logo.mp3 b/video/public/logo.mp3
new file mode 100644
index 0000000..4ec6ff7
Binary files /dev/null and b/video/public/logo.mp3 differ
diff --git a/video/public/music.mp3 b/video/public/music.mp3
new file mode 100644
index 0000000..3c99beb
Binary files /dev/null and b/video/public/music.mp3 differ
diff --git a/video/public/renders/blend/color_burn.jpg b/video/public/renders/blend/color_burn.jpg
new file mode 100644
index 0000000..b04c5d7
Binary files /dev/null and b/video/public/renders/blend/color_burn.jpg differ
diff --git a/video/public/renders/blend/color_dodge.jpg b/video/public/renders/blend/color_dodge.jpg
new file mode 100644
index 0000000..21191b4
Binary files /dev/null and b/video/public/renders/blend/color_dodge.jpg differ
diff --git a/video/public/renders/blend/darken.jpg b/video/public/renders/blend/darken.jpg
new file mode 100644
index 0000000..52ded0d
Binary files /dev/null and b/video/public/renders/blend/darken.jpg differ
diff --git a/video/public/renders/blend/hard_light.jpg b/video/public/renders/blend/hard_light.jpg
new file mode 100644
index 0000000..03620c4
Binary files /dev/null and b/video/public/renders/blend/hard_light.jpg differ
diff --git a/video/public/renders/blend/lighten.jpg b/video/public/renders/blend/lighten.jpg
new file mode 100644
index 0000000..000f239
Binary files /dev/null and b/video/public/renders/blend/lighten.jpg differ
diff --git a/video/public/renders/blend/linear_burn.jpg b/video/public/renders/blend/linear_burn.jpg
new file mode 100644
index 0000000..a196695
Binary files /dev/null and b/video/public/renders/blend/linear_burn.jpg differ
diff --git a/video/public/renders/blend/linear_dodge.jpg b/video/public/renders/blend/linear_dodge.jpg
new file mode 100644
index 0000000..467a002
Binary files /dev/null and b/video/public/renders/blend/linear_dodge.jpg differ
diff --git a/video/public/renders/blend/linear_light.jpg b/video/public/renders/blend/linear_light.jpg
new file mode 100644
index 0000000..8bb37a0
Binary files /dev/null and b/video/public/renders/blend/linear_light.jpg differ
diff --git a/video/public/renders/blend/multiply.jpg b/video/public/renders/blend/multiply.jpg
new file mode 100644
index 0000000..d2c6ceb
Binary files /dev/null and b/video/public/renders/blend/multiply.jpg differ
diff --git a/video/public/renders/blend/original.jpg b/video/public/renders/blend/original.jpg
new file mode 100644
index 0000000..3b46274
Binary files /dev/null and b/video/public/renders/blend/original.jpg differ
diff --git a/video/public/renders/blend/overlay.jpg b/video/public/renders/blend/overlay.jpg
new file mode 100644
index 0000000..1a6e043
Binary files /dev/null and b/video/public/renders/blend/overlay.jpg differ
diff --git a/video/public/renders/blend/pin_light.jpg b/video/public/renders/blend/pin_light.jpg
new file mode 100644
index 0000000..33781d3
Binary files /dev/null and b/video/public/renders/blend/pin_light.jpg differ
diff --git a/video/public/renders/blend/screen.jpg b/video/public/renders/blend/screen.jpg
new file mode 100644
index 0000000..fb67805
Binary files /dev/null and b/video/public/renders/blend/screen.jpg differ
diff --git a/video/public/renders/blend/soft_light.jpg b/video/public/renders/blend/soft_light.jpg
new file mode 100644
index 0000000..8c9b827
Binary files /dev/null and b/video/public/renders/blend/soft_light.jpg differ
diff --git a/video/public/renders/blend/vivid_light.jpg b/video/public/renders/blend/vivid_light.jpg
new file mode 100644
index 0000000..d7176a4
Binary files /dev/null and b/video/public/renders/blend/vivid_light.jpg differ
diff --git a/video/public/renders/chain/0-original.jpg b/video/public/renders/chain/0-original.jpg
new file mode 100644
index 0000000..36c0d95
Binary files /dev/null and b/video/public/renders/chain/0-original.jpg differ
diff --git a/video/public/renders/chain/1-grayscale.jpg b/video/public/renders/chain/1-grayscale.jpg
new file mode 100644
index 0000000..e45d0b9
Binary files /dev/null and b/video/public/renders/chain/1-grayscale.jpg differ
diff --git a/video/public/renders/chain/2-contrast.jpg b/video/public/renders/chain/2-contrast.jpg
new file mode 100644
index 0000000..a94dcd3
Binary files /dev/null and b/video/public/renders/chain/2-contrast.jpg differ
diff --git a/video/public/renders/chain/3-multiply.jpg b/video/public/renders/chain/3-multiply.jpg
new file mode 100644
index 0000000..c66d020
Binary files /dev/null and b/video/public/renders/chain/3-multiply.jpg differ
diff --git a/video/public/renders/chain/4-curve.jpg b/video/public/renders/chain/4-curve.jpg
new file mode 100644
index 0000000..0a0502d
Binary files /dev/null and b/video/public/renders/chain/4-curve.jpg differ
diff --git a/video/public/renders/curves/0-r-00.jpg b/video/public/renders/curves/0-r-00.jpg
new file mode 100644
index 0000000..0c61b54
Binary files /dev/null and b/video/public/renders/curves/0-r-00.jpg differ
diff --git a/video/public/renders/curves/0-r-01.jpg b/video/public/renders/curves/0-r-01.jpg
new file mode 100644
index 0000000..590486a
Binary files /dev/null and b/video/public/renders/curves/0-r-01.jpg differ
diff --git a/video/public/renders/curves/0-r-02.jpg b/video/public/renders/curves/0-r-02.jpg
new file mode 100644
index 0000000..1e7f328
Binary files /dev/null and b/video/public/renders/curves/0-r-02.jpg differ
diff --git a/video/public/renders/curves/0-r-03.jpg b/video/public/renders/curves/0-r-03.jpg
new file mode 100644
index 0000000..2359480
Binary files /dev/null and b/video/public/renders/curves/0-r-03.jpg differ
diff --git a/video/public/renders/curves/0-r-04.jpg b/video/public/renders/curves/0-r-04.jpg
new file mode 100644
index 0000000..c970a68
Binary files /dev/null and b/video/public/renders/curves/0-r-04.jpg differ
diff --git a/video/public/renders/curves/0-r-05.jpg b/video/public/renders/curves/0-r-05.jpg
new file mode 100644
index 0000000..1d5c42c
Binary files /dev/null and b/video/public/renders/curves/0-r-05.jpg differ
diff --git a/video/public/renders/curves/0-r-06.jpg b/video/public/renders/curves/0-r-06.jpg
new file mode 100644
index 0000000..6a5ce75
Binary files /dev/null and b/video/public/renders/curves/0-r-06.jpg differ
diff --git a/video/public/renders/curves/0-r-07.jpg b/video/public/renders/curves/0-r-07.jpg
new file mode 100644
index 0000000..c27c689
Binary files /dev/null and b/video/public/renders/curves/0-r-07.jpg differ
diff --git a/video/public/renders/curves/0-r-08.jpg b/video/public/renders/curves/0-r-08.jpg
new file mode 100644
index 0000000..3940642
Binary files /dev/null and b/video/public/renders/curves/0-r-08.jpg differ
diff --git a/video/public/renders/curves/0-r-09.jpg b/video/public/renders/curves/0-r-09.jpg
new file mode 100644
index 0000000..3ac4464
Binary files /dev/null and b/video/public/renders/curves/0-r-09.jpg differ
diff --git a/video/public/renders/curves/0-r-10.jpg b/video/public/renders/curves/0-r-10.jpg
new file mode 100644
index 0000000..eb7c16d
Binary files /dev/null and b/video/public/renders/curves/0-r-10.jpg differ
diff --git a/video/public/renders/curves/0-r-11.jpg b/video/public/renders/curves/0-r-11.jpg
new file mode 100644
index 0000000..28693c8
Binary files /dev/null and b/video/public/renders/curves/0-r-11.jpg differ
diff --git a/video/public/renders/curves/0-r-12.jpg b/video/public/renders/curves/0-r-12.jpg
new file mode 100644
index 0000000..d0f2e8c
Binary files /dev/null and b/video/public/renders/curves/0-r-12.jpg differ
diff --git a/video/public/renders/curves/0-r-13.jpg b/video/public/renders/curves/0-r-13.jpg
new file mode 100644
index 0000000..5adf8ee
Binary files /dev/null and b/video/public/renders/curves/0-r-13.jpg differ
diff --git a/video/public/renders/curves/0-r-14.jpg b/video/public/renders/curves/0-r-14.jpg
new file mode 100644
index 0000000..78ff0f9
Binary files /dev/null and b/video/public/renders/curves/0-r-14.jpg differ
diff --git a/video/public/renders/curves/0-r-15.jpg b/video/public/renders/curves/0-r-15.jpg
new file mode 100644
index 0000000..d2a9ab1
Binary files /dev/null and b/video/public/renders/curves/0-r-15.jpg differ
diff --git a/video/public/renders/curves/0-r-16.jpg b/video/public/renders/curves/0-r-16.jpg
new file mode 100644
index 0000000..cfe7e73
Binary files /dev/null and b/video/public/renders/curves/0-r-16.jpg differ
diff --git a/video/public/renders/curves/0-r-17.jpg b/video/public/renders/curves/0-r-17.jpg
new file mode 100644
index 0000000..82a9780
Binary files /dev/null and b/video/public/renders/curves/0-r-17.jpg differ
diff --git a/video/public/renders/curves/0-r-18.jpg b/video/public/renders/curves/0-r-18.jpg
new file mode 100644
index 0000000..0b145c7
Binary files /dev/null and b/video/public/renders/curves/0-r-18.jpg differ
diff --git a/video/public/renders/curves/0-r-19.jpg b/video/public/renders/curves/0-r-19.jpg
new file mode 100644
index 0000000..5d3b592
Binary files /dev/null and b/video/public/renders/curves/0-r-19.jpg differ
diff --git a/video/public/renders/curves/0-r-20.jpg b/video/public/renders/curves/0-r-20.jpg
new file mode 100644
index 0000000..c5d9fe2
Binary files /dev/null and b/video/public/renders/curves/0-r-20.jpg differ
diff --git a/video/public/renders/curves/1-b-00.jpg b/video/public/renders/curves/1-b-00.jpg
new file mode 100644
index 0000000..c5d9fe2
Binary files /dev/null and b/video/public/renders/curves/1-b-00.jpg differ
diff --git a/video/public/renders/curves/1-b-01.jpg b/video/public/renders/curves/1-b-01.jpg
new file mode 100644
index 0000000..c3753c6
Binary files /dev/null and b/video/public/renders/curves/1-b-01.jpg differ
diff --git a/video/public/renders/curves/1-b-02.jpg b/video/public/renders/curves/1-b-02.jpg
new file mode 100644
index 0000000..2937ab8
Binary files /dev/null and b/video/public/renders/curves/1-b-02.jpg differ
diff --git a/video/public/renders/curves/1-b-03.jpg b/video/public/renders/curves/1-b-03.jpg
new file mode 100644
index 0000000..9485a13
Binary files /dev/null and b/video/public/renders/curves/1-b-03.jpg differ
diff --git a/video/public/renders/curves/1-b-04.jpg b/video/public/renders/curves/1-b-04.jpg
new file mode 100644
index 0000000..c16781e
Binary files /dev/null and b/video/public/renders/curves/1-b-04.jpg differ
diff --git a/video/public/renders/curves/1-b-05.jpg b/video/public/renders/curves/1-b-05.jpg
new file mode 100644
index 0000000..66c85d6
Binary files /dev/null and b/video/public/renders/curves/1-b-05.jpg differ
diff --git a/video/public/renders/curves/1-b-06.jpg b/video/public/renders/curves/1-b-06.jpg
new file mode 100644
index 0000000..c1325a2
Binary files /dev/null and b/video/public/renders/curves/1-b-06.jpg differ
diff --git a/video/public/renders/curves/1-b-07.jpg b/video/public/renders/curves/1-b-07.jpg
new file mode 100644
index 0000000..1f36891
Binary files /dev/null and b/video/public/renders/curves/1-b-07.jpg differ
diff --git a/video/public/renders/curves/1-b-08.jpg b/video/public/renders/curves/1-b-08.jpg
new file mode 100644
index 0000000..01e4ac1
Binary files /dev/null and b/video/public/renders/curves/1-b-08.jpg differ
diff --git a/video/public/renders/curves/1-b-09.jpg b/video/public/renders/curves/1-b-09.jpg
new file mode 100644
index 0000000..815b357
Binary files /dev/null and b/video/public/renders/curves/1-b-09.jpg differ
diff --git a/video/public/renders/curves/1-b-10.jpg b/video/public/renders/curves/1-b-10.jpg
new file mode 100644
index 0000000..6c7278a
Binary files /dev/null and b/video/public/renders/curves/1-b-10.jpg differ
diff --git a/video/public/renders/curves/1-b-11.jpg b/video/public/renders/curves/1-b-11.jpg
new file mode 100644
index 0000000..309e3a4
Binary files /dev/null and b/video/public/renders/curves/1-b-11.jpg differ
diff --git a/video/public/renders/curves/1-b-12.jpg b/video/public/renders/curves/1-b-12.jpg
new file mode 100644
index 0000000..ed080f7
Binary files /dev/null and b/video/public/renders/curves/1-b-12.jpg differ
diff --git a/video/public/renders/curves/1-b-13.jpg b/video/public/renders/curves/1-b-13.jpg
new file mode 100644
index 0000000..782636e
Binary files /dev/null and b/video/public/renders/curves/1-b-13.jpg differ
diff --git a/video/public/renders/curves/1-b-14.jpg b/video/public/renders/curves/1-b-14.jpg
new file mode 100644
index 0000000..fd805e5
Binary files /dev/null and b/video/public/renders/curves/1-b-14.jpg differ
diff --git a/video/public/renders/curves/1-b-15.jpg b/video/public/renders/curves/1-b-15.jpg
new file mode 100644
index 0000000..a1f7159
Binary files /dev/null and b/video/public/renders/curves/1-b-15.jpg differ
diff --git a/video/public/renders/curves/1-b-16.jpg b/video/public/renders/curves/1-b-16.jpg
new file mode 100644
index 0000000..01d0da5
Binary files /dev/null and b/video/public/renders/curves/1-b-16.jpg differ
diff --git a/video/public/renders/curves/1-b-17.jpg b/video/public/renders/curves/1-b-17.jpg
new file mode 100644
index 0000000..51d0ac8
Binary files /dev/null and b/video/public/renders/curves/1-b-17.jpg differ
diff --git a/video/public/renders/curves/1-b-18.jpg b/video/public/renders/curves/1-b-18.jpg
new file mode 100644
index 0000000..e8796ec
Binary files /dev/null and b/video/public/renders/curves/1-b-18.jpg differ
diff --git a/video/public/renders/curves/1-b-19.jpg b/video/public/renders/curves/1-b-19.jpg
new file mode 100644
index 0000000..6855fb4
Binary files /dev/null and b/video/public/renders/curves/1-b-19.jpg differ
diff --git a/video/public/renders/curves/1-b-20.jpg b/video/public/renders/curves/1-b-20.jpg
new file mode 100644
index 0000000..e24d53a
Binary files /dev/null and b/video/public/renders/curves/1-b-20.jpg differ
diff --git a/video/public/renders/looks/cross-process-after.jpg b/video/public/renders/looks/cross-process-after.jpg
new file mode 100644
index 0000000..a895d99
Binary files /dev/null and b/video/public/renders/looks/cross-process-after.jpg differ
diff --git a/video/public/renders/looks/cross-process-before.jpg b/video/public/renders/looks/cross-process-before.jpg
new file mode 100644
index 0000000..212d282
Binary files /dev/null and b/video/public/renders/looks/cross-process-before.jpg differ
diff --git a/video/public/renders/looks/cyanotype-after.jpg b/video/public/renders/looks/cyanotype-after.jpg
new file mode 100644
index 0000000..58f6304
Binary files /dev/null and b/video/public/renders/looks/cyanotype-after.jpg differ
diff --git a/video/public/renders/looks/cyanotype-before.jpg b/video/public/renders/looks/cyanotype-before.jpg
new file mode 100644
index 0000000..b367e12
Binary files /dev/null and b/video/public/renders/looks/cyanotype-before.jpg differ
diff --git a/video/public/renders/looks/duotone-after.jpg b/video/public/renders/looks/duotone-after.jpg
new file mode 100644
index 0000000..7e995bd
Binary files /dev/null and b/video/public/renders/looks/duotone-after.jpg differ
diff --git a/video/public/renders/looks/duotone-before.jpg b/video/public/renders/looks/duotone-before.jpg
new file mode 100644
index 0000000..c3bb816
Binary files /dev/null and b/video/public/renders/looks/duotone-before.jpg differ
diff --git a/video/public/renders/presets/duotone.json b/video/public/renders/presets/duotone.json
new file mode 100644
index 0000000..f286170
--- /dev/null
+++ b/video/public/renders/presets/duotone.json
@@ -0,0 +1,9 @@
+{
+ "operations": [
+ {"type": "grayscale"},
+ {"type": "contrast", "factor": 1.2},
+ {"type": "curve", "channels": "r", "curve_points": [0.12, 1.0]},
+ {"type": "curve", "channels": "g", "curve_points": [0.08, 0.45]},
+ {"type": "curve", "channels": "b", "curve_points": [0.4, 0.42]}
+ ]
+}
diff --git a/video/remotion.config.ts b/video/remotion.config.ts
new file mode 100644
index 0000000..07bb1cb
--- /dev/null
+++ b/video/remotion.config.ts
@@ -0,0 +1,9 @@
+import {Config} from '@remotion/cli/config';
+
+Config.setVideoImageFormat('jpeg');
+Config.setJpegQuality(95);
+Config.setCodec('h264');
+Config.setCrf(16);
+Config.setPixelFormat('yuv420p');
+Config.setColorSpace('bt709');
+Config.setOverwriteOutput(true);
diff --git a/video/scripts/build_assets.py b/video/scripts/build_assets.py
new file mode 100644
index 0000000..1cc9fab
--- /dev/null
+++ b/video/scripts/build_assets.py
@@ -0,0 +1,256 @@
+#!/usr/bin/env python3
+"""Render every edited image shown in the layeris video, using layeris itself.
+
+The video never fakes an edit: each image on screen is produced here from the
+docs' sample photos in ``docs/photos/``, and the code shown next to it is the
+code that ran. Results go to ``video/public/renders/`` and a manifest the video
+imports goes to ``video/src/data/assets.json``.
+
+Usage (from the repository root, with layeris installed)::
+
+ pip install -e .
+ python video/scripts/build_assets.py
+"""
+
+from __future__ import annotations
+
+import json
+import os
+import shutil
+from pathlib import Path
+from typing import Any
+
+from layeris import LayerImage
+
+VIDEO_ROOT = Path(__file__).resolve().parent.parent
+REPO_ROOT = VIDEO_ROOT.parent
+PUBLIC_DIR = VIDEO_ROOT / "public"
+# The same photos as the docs site (generated by scripts/generate_photos.py).
+PHOTOS_DIR = REPO_ROOT / "docs" / "photos"
+RENDERS_DIR = PUBLIC_DIR / "renders"
+MANIFEST = VIDEO_ROOT / "src" / "data" / "assets.json"
+
+JPEG_QUALITY = 88
+
+# ----------------------------------------------------------------------
+# Scene: chained operations
+# ----------------------------------------------------------------------
+
+CHAIN_PHOTO = "hanbok-portrait"
+CHAIN_SIZE = (1152, 768)
+CHAIN_STEPS: list[str] = [
+ "grayscale()",
+ "contrast(1.2)",
+ 'multiply("#f2c49b", opacity=0.8)',
+ 'curve("b", [0.12, 0.4, 0.7, 0.92])',
+]
+CHAIN_SAVE_AS = "toned.jpg"
+
+# ----------------------------------------------------------------------
+# Scene: blend modes
+# ----------------------------------------------------------------------
+
+BLEND_PHOTO = "jeju-coast"
+BLEND_SIZE = (480, 320)
+BLEND_COLOR = "#e8743b"
+BLEND_MODES: list[str] = [
+ "darken", "multiply", "color_burn", "linear_burn",
+ "lighten", "screen", "color_dodge", "linear_dodge",
+ "overlay", "soft_light", "hard_light", "vivid_light", "linear_light", "pin_light",
+]
+
+# ----------------------------------------------------------------------
+# Scene: per-channel curves
+# ----------------------------------------------------------------------
+
+CURVES_PHOTO = "bamboo-forest"
+CURVES_SIZE = (1008, 672)
+# Each phase eases one channel's control points from a straight line to the
+# target. Phases apply on top of each other, in order.
+CURVE_PHASES: list[tuple[str, list[float]]] = [
+ ("r", [0, 0.1, 0.5, 0.9, 1]),
+ ("b", [0.2, 0.36, 0.5, 0.62, 0.78]),
+]
+# Images rendered per phase; the video crossfades between neighbours.
+CURVE_STEPS = 21
+
+# ----------------------------------------------------------------------
+# Scene: looks (recipes adapted from the docs' examples page)
+# ----------------------------------------------------------------------
+
+LOOKS_SIZE = (1152, 768)
+LOOKS: list[dict[str, Any]] = [
+ {
+ "slug": "cross-process",
+ "title": "Cross-processed",
+ "photo": "gamcheon-village",
+ "code": """
+(image.curve("r", [0, 0.15, 0.55, 0.9, 1])
+ .curve("g", [0, 0.2, 0.55, 0.85, 1])
+ .curve("b", [0.22, 0.35, 0.48, 0.6, 0.72]))
+""",
+ },
+ {
+ "slug": "cyanotype",
+ "title": "Cyanotype",
+ "photo": "palace-pond",
+ "code": """
+(image.grayscale()
+ .curve("rgb", [0, 0.1, 0.55, 0.9, 1])
+ .curve("r", [0.02, 0.85])
+ .curve("g", [0.15, 0.95])
+ .curve("b", [0.35, 1.0]))
+""",
+ },
+ {
+ "slug": "duotone",
+ "title": "Duotone, from a JSON preset",
+ "photo": "palace-eaves",
+ "preset": {
+ "operations": [
+ {"type": "grayscale"},
+ {"type": "contrast", "factor": 1.2},
+ {"type": "curve", "channels": "r", "curve_points": [0.12, 1.0]},
+ {"type": "curve", "channels": "g", "curve_points": [0.08, 0.45]},
+ {"type": "curve", "channels": "b", "curve_points": [0.4, 0.42]},
+ ]
+ },
+ "code": 'image.apply_from_json("duotone.json")',
+ },
+]
+
+
+# ----------------------------------------------------------------------
+# Helpers
+# ----------------------------------------------------------------------
+
+
+def load(photo: str, size: tuple[int, int]) -> LayerImage:
+ return LayerImage.from_file(PHOTOS_DIR / f"{photo}.jpg").resize(*size)
+
+
+def save(image: LayerImage, rel: str) -> str:
+ path = RENDERS_DIR / rel
+ path.parent.mkdir(parents=True, exist_ok=True)
+ image.save(path, quality=JPEG_QUALITY)
+ return f"renders/{rel}"
+
+
+def run(snippet: str, image: LayerImage) -> LayerImage:
+ """Execute *snippet* exactly as shown on screen, with ``image`` in scope."""
+ scope: dict[str, Any] = {"LayerImage": LayerImage, "image": image}
+ exec(snippet, scope) # noqa: S102 - trusted, hard-coded snippets
+ return scope["image"]
+
+
+def format_preset(preset: dict[str, Any]) -> str:
+ """JSON with one operation per line, so the preset fits on screen."""
+ ops = ",\n".join(f" {json.dumps(op)}" for op in preset["operations"])
+ return '{\n "operations": [\n' + ops + "\n ]\n}"
+
+
+def lerp(a: list[float], b: list[float], t: float) -> list[float]:
+ return [x + (y - x) * t for x, y in zip(a, b)]
+
+
+def straight(n: int) -> list[float]:
+ return [i / (n - 1) for i in range(n)]
+
+
+# ----------------------------------------------------------------------
+# Builders
+# ----------------------------------------------------------------------
+
+
+def build_chain() -> dict[str, Any]:
+ image = load(CHAIN_PHOTO, CHAIN_SIZE)
+ frames = [save(image, "chain/0-original.jpg")]
+ for i, step in enumerate(CHAIN_STEPS, start=1):
+ image = run(f"image.{step}", image)
+ frames.append(save(image, f"chain/{i}-{step.split('(')[0]}.jpg"))
+ return {
+ "photo": f"{CHAIN_PHOTO}.jpg",
+ "steps": CHAIN_STEPS,
+ "saveAs": CHAIN_SAVE_AS,
+ "images": frames,
+ }
+
+
+def build_blend() -> dict[str, Any]:
+ base = load(BLEND_PHOTO, BLEND_SIZE)
+ tiles = [{"mode": "original", "image": save(base, "blend/original.jpg")}]
+ for mode in BLEND_MODES:
+ result = getattr(base.clone(), mode)(BLEND_COLOR)
+ tiles.append({"mode": mode, "image": save(result, f"blend/{mode}.jpg")})
+ return {"photo": f"{BLEND_PHOTO}.jpg", "color": BLEND_COLOR, "tiles": tiles}
+
+
+def build_curves() -> dict[str, Any]:
+ base = load(CURVES_PHOTO, CURVES_SIZE)
+ phases = []
+ for p, (channels, target) in enumerate(CURVE_PHASES):
+ start = straight(len(target))
+ images = []
+ for step in range(CURVE_STEPS):
+ t = step / (CURVE_STEPS - 1)
+ image = base.clone().curve(channels, lerp(start, target, t))
+ images.append(save(image, f"curves/{p}-{channels}-{step:02d}.jpg"))
+ phases.append(
+ {"channels": channels, "start": start, "target": target, "images": images}
+ )
+ # The next phase builds on top of this one, as chained calls would.
+ base.curve(channels, target)
+ return {"photo": f"{CURVES_PHOTO}.jpg", "phases": phases}
+
+
+def build_looks() -> list[dict[str, Any]]:
+ presets_dir = RENDERS_DIR / "presets"
+ presets_dir.mkdir(parents=True, exist_ok=True)
+ looks = []
+ for look in LOOKS:
+ image = load(look["photo"], LOOKS_SIZE)
+ before = save(image, f"looks/{look['slug']}-before.jpg")
+ preset_text = None
+ cwd = os.getcwd()
+ try:
+ if "preset" in look:
+ preset_text = format_preset(look["preset"])
+ assert json.loads(preset_text) == look["preset"]
+ (presets_dir / f"{look['slug']}.json").write_text(preset_text + "\n")
+ os.chdir(presets_dir) # so the snippet's relative path resolves
+ image = run(look["code"].strip(), image)
+ finally:
+ os.chdir(cwd)
+ after = save(image, f"looks/{look['slug']}-after.jpg")
+ looks.append(
+ {
+ "slug": look["slug"],
+ "title": look["title"],
+ "photo": f"{look['photo']}.jpg",
+ "code": look["code"].strip(),
+ "preset": preset_text,
+ "before": before,
+ "after": after,
+ }
+ )
+ return looks
+
+
+def main() -> None:
+ if RENDERS_DIR.exists():
+ shutil.rmtree(RENDERS_DIR)
+ manifest = {
+ "chain": build_chain(),
+ "blend": build_blend(),
+ "curves": build_curves(),
+ "looks": build_looks(),
+ }
+ MANIFEST.parent.mkdir(parents=True, exist_ok=True)
+ MANIFEST.write_text(json.dumps(manifest, indent=2) + "\n")
+ count = sum(1 for _ in RENDERS_DIR.rglob("*.jpg"))
+ print(f"Wrote {count} images to {RENDERS_DIR.relative_to(VIDEO_ROOT)}/")
+ print(f"Wrote {MANIFEST.relative_to(VIDEO_ROOT)}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/video/scripts/generate_music.py b/video/scripts/generate_music.py
new file mode 100644
index 0000000..c5e59db
--- /dev/null
+++ b/video/scripts/generate_music.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+"""Generate the video's background music with the ElevenLabs Music API.
+
+Each style below is an instrumental prompt sized to the video's exact length.
+The render only *reads* ``video/public/music.mp3``, so this script is needed
+only to replace the track. Every run gives a different take, even for the
+same style.
+
+Usage::
+
+ export ELEVENLABS_API_KEY=...
+ python video/scripts/generate_music.py # default style
+ python video/scripts/generate_music.py --style piano --out /tmp/piano.mp3
+"""
+
+from __future__ import annotations
+
+import argparse
+import os
+import sys
+from pathlib import Path
+
+import requests
+
+VIDEO_ROOT = Path(__file__).resolve().parent.parent
+DEFAULT_OUT = VIDEO_ROOT / "public" / "music.mp3"
+
+MODEL = "music_v2_5"
+# The music runs from the end of the intro to the end of the video: 1,433
+# frames at 30 fps (see MUSIC_START in src/Video.tsx).
+LENGTH_MS = 47_800
+
+# Shared by every style: what the track is for, and what to avoid.
+BRIEF = (
+ "Background music for a calm, minimal walkthrough video of a software "
+ "library. Clean, polished production with a spacious, airy mix. "
+ "Instrumental only: no vocals, no drops, no cinematic swells, nothing "
+ "corporate or cheesy."
+)
+
+STYLES: dict[str, str] = {
+ "piano": (
+ "Warm, minimal modern instrumental. Soft felt piano plays a simple, "
+ "hopeful chord progression, joined by gentle plucked synth arpeggios, "
+ "a light crisp beat with a soft kick, brushed snare and shaker, and a "
+ "round, warm bass. Around 100 BPM, optimistic but understated. It "
+ "starts with solo piano, the beat comes in after a few seconds, it "
+ "grows slightly fuller in the middle, and it ends on a soft final "
+ "piano chord that rings out."
+ ),
+ "marimba": (
+ "Light, playful minimal electronic instrumental. Marimba and kalimba "
+ "plucks trade a bright, curious motif over soft synth pads, finger "
+ "snaps, a gentle bouncy beat and a warm bass. Around 110 BPM, "
+ "friendly and focused. It opens with a solo marimba, builds lightly, "
+ "and ends on a clean final note that rings out."
+ ),
+ "lofi": (
+ "Mellow lo-fi chillhop instrumental. Jazzy Rhodes electric piano "
+ "chords over a soft, laid-back boom-bap beat with a slight swing, a "
+ "warm upright bass and subtle vinyl texture. Around 85 BPM, relaxed, "
+ "cozy and focused. It opens with the Rhodes alone, the beat drops in "
+ "after a few seconds, and it ends with the last chord ringing out."
+ ),
+}
+DEFAULT_STYLE = "lofi"
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--style", choices=sorted(STYLES), default=DEFAULT_STYLE,
+ help=f"prompt to use (default: {DEFAULT_STYLE})")
+ parser.add_argument("--model", default=MODEL, help=f"model id (default: {MODEL})")
+ parser.add_argument("--out", type=Path, default=DEFAULT_OUT, help="output MP3 path")
+ args = parser.parse_args()
+
+ api_key = os.environ.get("ELEVENLABS_API_KEY")
+ if not api_key:
+ sys.exit("ELEVENLABS_API_KEY is not set")
+
+ print(f"Generating {LENGTH_MS / 1000:.1f} s of '{args.style}' music with {args.model}")
+ response = requests.post(
+ "https://api.elevenlabs.io/v1/music",
+ headers={"xi-api-key": api_key},
+ json={
+ "prompt": f"{STYLES[args.style]} {BRIEF}",
+ "music_length_ms": LENGTH_MS,
+ "model_id": args.model,
+ "force_instrumental": True,
+ },
+ timeout=600,
+ )
+ if not response.ok:
+ sys.exit(f"ElevenLabs returned {response.status_code}: {response.text[:500]}")
+ args.out.parent.mkdir(parents=True, exist_ok=True)
+ args.out.write_bytes(response.content)
+ song_id = response.headers.get("song-id") or response.headers.get("x-song-id")
+ print(f" wrote {args.out} ({len(response.content) / 1024:.0f} KB)"
+ + (f", song id {song_id}" if song_id else ""))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/video/scripts/generate_sound_effect.py b/video/scripts/generate_sound_effect.py
new file mode 100644
index 0000000..5d61ebe
--- /dev/null
+++ b/video/scripts/generate_sound_effect.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python3
+"""Generate the video's sound effects with the ElevenLabs Sound Effects API.
+
+* ``logo``: plays as the logo appears (``video/public/logo.mp3``).
+* ``key``: one keystroke, played for each letter as the wordmark is typed
+ (``video/public/key.mp3``). It is trimmed to start right at the click and
+ normalized to a -6 dBFS peak.
+
+The render only *reads* these files, so this script is needed only to replace
+one. Name the effects to generate; each run gives a different take.
+
+Usage::
+
+ export ELEVENLABS_API_KEY=...
+ python video/scripts/generate_sound_effect.py key
+ python video/scripts/generate_sound_effect.py logo key
+"""
+
+from __future__ import annotations
+
+import argparse
+import os
+import re
+import subprocess
+import sys
+import tempfile
+from dataclasses import dataclass
+from pathlib import Path
+
+import requests
+
+VIDEO_ROOT = Path(__file__).resolve().parent.parent
+PUBLIC_DIR = VIDEO_ROOT / "public"
+# Trimmed effects are normalized to this peak; the mix sets their level.
+TRIMMED_PEAK_DB = -6.0
+
+
+@dataclass(frozen=True)
+class Effect:
+ prompt: str
+ duration_seconds: float
+ # Seconds to keep after the first sound, or None to keep the whole clip.
+ trim_to: float | None = None
+
+
+EFFECTS: dict[str, Effect] = {
+ # The logo's outlines draw in over the first ~0.7 s and its layers settle
+ # by ~1.2 s (see src/components/logoMotion.ts, played at 2x in the intro).
+ "logo": Effect(
+ prompt=(
+ "A soft, airy shimmer that rises gently and settles into a single "
+ "warm, glassy chime, like translucent layers sliding together. "
+ "Clean, minimal, modern interface sound, subtle and elegant, with "
+ "a short natural tail."
+ ),
+ duration_seconds=2.0,
+ ),
+ # Letters are typed 2 frames (67 ms) apart, so each click must be short.
+ "key": Effect(
+ prompt=(
+ "A single soft keystroke on a quiet, premium laptop keyboard: one "
+ "short, crisp, muted click, close up, in a silent room. Nothing "
+ "else."
+ ),
+ duration_seconds=0.5,
+ trim_to=0.12,
+ ),
+}
+
+
+def generate(name: str, effect: Effect, api_key: str) -> Path:
+ response = requests.post(
+ "https://api.elevenlabs.io/v1/sound-generation",
+ params={"output_format": "mp3_44100_128"},
+ headers={"xi-api-key": api_key},
+ json={
+ "text": effect.prompt,
+ "duration_seconds": effect.duration_seconds,
+ "prompt_influence": 0.6,
+ },
+ timeout=300,
+ )
+ if not response.ok:
+ sys.exit(f"ElevenLabs returned {response.status_code}: {response.text[:500]}")
+ out = PUBLIC_DIR / f"{name}.mp3"
+ out.parent.mkdir(parents=True, exist_ok=True)
+ if effect.trim_to is None:
+ out.write_bytes(response.content)
+ return out
+ # Drop the silence before the sound, keep `trim_to` seconds of it, fade
+ # the end so the clip doesn't click when it stops, then normalize the peak.
+ with tempfile.NamedTemporaryFile(suffix=".mp3") as raw:
+ raw.write(response.content)
+ raw.flush()
+ fade = min(0.04, effect.trim_to / 3)
+ trim = (
+ "silenceremove=start_periods=1:start_threshold=-45dB,"
+ f"atrim=0:{effect.trim_to},"
+ f"afade=t=out:st={effect.trim_to - fade}:d={fade}"
+ )
+ gain = TRIMMED_PEAK_DB - peak_db(raw.name, trim)
+ subprocess.run(
+ ["ffmpeg", "-v", "error", "-y", "-i", raw.name,
+ "-af", f"{trim},volume={gain:.1f}dB", "-b:a", "128k", str(out)],
+ check=True,
+ )
+ return out
+
+
+def peak_db(path: str, filters: str) -> float:
+ """Peak level in dBFS of `path` after `filters`, measured by ffmpeg."""
+ result = subprocess.run(
+ ["ffmpeg", "-nostats", "-i", path, "-af", f"{filters},volumedetect",
+ "-f", "null", "-"],
+ capture_output=True, text=True, check=True,
+ )
+ return float(re.search(r"max_volume: (-?[\d.]+) dB", result.stderr).group(1))
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("names", nargs="+", choices=sorted(EFFECTS), metavar="EFFECT",
+ help=f"effects to generate: {', '.join(EFFECTS)}")
+ args = parser.parse_args()
+
+ api_key = os.environ.get("ELEVENLABS_API_KEY")
+ if not api_key:
+ sys.exit("ELEVENLABS_API_KEY is not set")
+
+ for name in args.names:
+ path = generate(name, EFFECTS[name], api_key)
+ print(f" wrote {path.relative_to(VIDEO_ROOT)} ({path.stat().st_size / 1024:.0f} KB)")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/video/src/Root.tsx b/video/src/Root.tsx
new file mode 100644
index 0000000..bc3af74
--- /dev/null
+++ b/video/src/Root.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import {Composition} from 'remotion';
+import {FPS, HEIGHT, WIDTH} from './theme';
+import {LayerisVideo, TOTAL_FRAMES} from './Video';
+
+export const RemotionRoot: React.FC = () => (
+
+);
diff --git a/video/src/Video.tsx b/video/src/Video.tsx
new file mode 100644
index 0000000..78f01fd
--- /dev/null
+++ b/video/src/Video.tsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import {AbsoluteFill, Audio, Sequence, Series, interpolate, staticFile} from 'remotion';
+import {EXIT_FRAMES} from './components/Scene';
+import './fonts';
+import {BLEND_FRAMES, BlendModes} from './scenes/BlendModes';
+import {CHAIN_FRAMES, Chain} from './scenes/Chain';
+import {CURVES_FRAMES, Curves} from './scenes/Curves';
+import {INSTALL_FRAMES, Install} from './scenes/Install';
+import {INTRO_FRAMES, Intro} from './scenes/Intro';
+import {LOOKS_FRAMES, Looks} from './scenes/Looks';
+import {OUTRO_FRAMES, Outro} from './scenes/Outro';
+import {colors} from './theme';
+
+const SCENES: [React.FC, number][] = [
+ [Intro, INTRO_FRAMES],
+ [Install, INSTALL_FRAMES],
+ [Chain, CHAIN_FRAMES],
+ [BlendModes, BLEND_FRAMES],
+ [Curves, CURVES_FRAMES],
+ [Looks, LOOKS_FRAMES],
+ [Outro, OUTRO_FRAMES],
+];
+
+export const TOTAL_FRAMES = SCENES.reduce((sum, [, frames]) => sum + frames, 0);
+
+// The logo's sound effect (scripts/generate_sound_effect.py) plays as its
+// outlines start to draw.
+const LOGO_SOUND_START = 3;
+
+// The music (scripts/generate_music.py) starts as the logo fades out at the
+// end of the intro, and fades over the last second so it ends with the video.
+const MUSIC_START = INTRO_FRAMES - EXIT_FRAMES;
+const MUSIC_FRAMES = TOTAL_FRAMES - MUSIC_START;
+const MUSIC_FADE_FRAMES = 30;
+const musicVolume = (frame: number) =>
+ interpolate(frame, [MUSIC_FRAMES - MUSIC_FADE_FRAMES, MUSIC_FRAMES - 1], [1, 0], {
+ extrapolateLeft: 'clamp',
+ extrapolateRight: 'clamp',
+ });
+
+export const LayerisVideo: React.FC = () => (
+
+
+
+
+
+
+
+
+ {SCENES.map(([SceneComponent, frames], i) => (
+
+
+
+ ))}
+
+
+);
diff --git a/video/src/components/Code.tsx b/video/src/components/Code.tsx
new file mode 100644
index 0000000..16ea2ac
--- /dev/null
+++ b/video/src/components/Code.tsx
@@ -0,0 +1,193 @@
+import React from 'react';
+import {colors, fonts} from '../theme';
+
+export type Lang = 'python' | 'json' | 'shell';
+
+type TokenType =
+ | 'plain'
+ | 'keyword'
+ | 'name'
+ | 'call'
+ | 'string'
+ | 'number'
+ | 'comment'
+ | 'prompt'
+ | 'output';
+
+type Token = {text: string; type: TokenType};
+
+const KEYWORDS = new Set(['from', 'import', 'as', 'for', 'in', 'None', 'True', 'False']);
+const NAMES = new Set(['LayerImage', 'np']);
+
+// Matches, in order: strings, comments, numbers, identifiers, anything else.
+const TOKEN_RE =
+ /("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')|(#.*$)|(\b\d+(?:\.\d+)?\b)|([A-Za-z_][A-Za-z0-9_]*)|(\s+|.)/g;
+
+// Same colours and weights as the docs' syntax highlighting.
+const STYLES: Record = {
+ plain: {color: colors.ink},
+ keyword: {color: colors.ink, fontWeight: 500},
+ name: {color: colors.ink, fontWeight: 500},
+ call: {color: colors.call},
+ string: {color: colors.string},
+ number: {color: colors.number},
+ comment: {color: colors.comment, fontStyle: 'italic'},
+ prompt: {color: colors.paren},
+ output: {color: colors.muted},
+};
+
+export const tokenize = (line: string, lang: Lang): Token[] => {
+ if (lang === 'shell') {
+ if (line.startsWith('$ ')) {
+ return [
+ {text: '$ ', type: 'prompt'},
+ {text: line.slice(2), type: 'plain'},
+ ];
+ }
+ return [{text: line, type: 'output'}];
+ }
+ const tokens: Token[] = [];
+ for (const m of line.matchAll(TOKEN_RE)) {
+ const [text, str, comment, num, ident] = m;
+ let type: TokenType = 'plain';
+ if (str) type = 'string';
+ else if (comment) type = 'comment';
+ else if (num) type = 'number';
+ else if (ident) {
+ const rest = line.slice((m.index ?? 0) + text.length);
+ if (KEYWORDS.has(ident)) type = 'keyword';
+ else if (NAMES.has(ident)) type = 'name';
+ else if (rest.startsWith('(')) type = 'call';
+ }
+ tokens.push({text, type});
+ }
+ return tokens;
+};
+
+/** Keep the first `chars` characters of a token list. */
+const slice = (tokens: Token[], chars: number): Token[] => {
+ const out: Token[] = [];
+ let left = chars;
+ for (const t of tokens) {
+ if (left <= 0) break;
+ out.push(left >= t.text.length ? t : {...t, text: t.text.slice(0, left)});
+ left -= t.text.length;
+ }
+ return out;
+};
+
+export type CodeProps = {
+ code: string;
+ lang?: Lang;
+ fontSize?: number;
+ lineHeight?: number;
+ width?: number;
+ padding?: [number, number];
+ /** Visible characters per line; lines without an entry are fully visible. */
+ reveal?: number[];
+ /** Line to draw a text caret on, after its last visible character. */
+ caret?: {line: number; opacity: number};
+ /** Line to tint, for "this line just ran". */
+ highlight?: {line: number; opacity: number};
+ style?: React.CSSProperties;
+};
+
+export const Code: React.FC = ({
+ code,
+ lang = 'python',
+ fontSize = 26,
+ lineHeight = 1.7,
+ width,
+ padding = [30, 36],
+ reveal,
+ caret,
+ highlight,
+ style,
+}) => {
+ const lines = code.split('\n');
+ const lh = Math.round(fontSize * lineHeight);
+ return (
+
+ {highlight ? (
+
+ ) : null}
+ {lines.map((line, i) => {
+ const tokens = tokenize(line, lang);
+ const visible = reveal?.[i] ?? Infinity;
+ const shown = Number.isFinite(visible) ? slice(tokens, visible) : tokens;
+ return (
+
+ {shown.map((t, j) => (
+
+ {t.text}
+
+ ))}
+ {caret && caret.line === i ? (
+
+ ) : null}
+
+ );
+ })}
+
+ );
+};
+
+/**
+ * Characters to show on each line when typing `code` line by line.
+ * `starts[i]` is the frame line i starts typing; lines with no start are
+ * shown immediately.
+ */
+export const typeLines = (
+ code: string,
+ frame: number,
+ starts: (number | undefined)[],
+ charsPerFrame = 1.6,
+): number[] =>
+ code.split('\n').map((line, i) => {
+ const start = starts[i];
+ if (start === undefined) return line.length;
+ return Math.max(0, Math.min(line.length, Math.floor((frame - start) * charsPerFrame)));
+ });
+
+/** Blinking caret opacity: solid while typing, then blinks. */
+export const caretBlink = (frame: number, typingEnd: number) => {
+ if (frame < typingEnd) return 1;
+ return Math.floor((frame - typingEnd) / 15) % 2 === 0 ? 1 : 0;
+};
diff --git a/video/src/components/Logo.tsx b/video/src/components/Logo.tsx
new file mode 100644
index 0000000..d901774
--- /dev/null
+++ b/video/src/components/Logo.tsx
@@ -0,0 +1,80 @@
+import {evolvePath} from '@remotion/paths';
+import React, {useId} from 'react';
+import {colors} from '../theme';
+
+// The three layers of docs/assets/logo.svg, back to front.
+export const LOGO_PATHS = [
+ 'M93.5 201.7C83.8 203.3 76.2 199.9 69.1 193.5C62 187.1 55.3 177.8 51.3 166.4C47.3 155.1 46 141.8 46.1 128.7C46.3 115.5 48 102.5 51.5 89.6C55.1 76.7 60.5 64 67.3 54.2C74.2 44.5 82.5 37.7 92.5 34C102.5 30.3 114.1 29.5 127.1 30.4C140.1 31.3 154.4 33.8 168.4 37.2C182.4 40.6 196 45.1 209.6 50C223.3 54.9 236.9 60.4 250.8 66.4C264.7 72.5 278.9 79.1 287.8 85.9C296.7 92.6 300.2 99.5 300.8 105.5C301.4 111.6 299.1 116.9 290.8 121.3C282.6 125.8 268.3 129.5 253.6 134C238.8 138.6 223.6 144 209.4 149.4C195.2 154.8 182 160.2 168.5 166.5C154.9 172.8 141 180 128 186.8C115.1 193.7 103.2 200.1 93.5 201.7Z',
+ 'M88.1 239.9C77.8 241.9 69.4 238.8 61.6 233C53.8 227.2 46.6 218.6 42.2 207.3C37.9 196.1 36.5 182.2 36.9 168.4C37.2 154.5 39.3 140.7 43.7 127.8C48.1 114.9 54.7 103 63 93.1C71.3 83.2 81.2 75.2 92.5 70C103.9 64.8 116.7 62.3 130.6 61.3C144.5 60.2 159.4 60.7 173.8 62.4C188.2 64.2 202.1 67.3 215.8 72.2C229.6 77 243.1 83.6 255.2 91.4C267.3 99.3 277.9 108.4 287.6 119.1C297.4 129.8 306.4 142.2 311.1 152.2C315.8 162.2 316.3 170 313.5 175C310.7 180.1 304.6 182.5 293.2 183.5C281.7 184.4 265 183.9 250 184.6C235 185.3 221.9 187.1 207.8 190.3C193.6 193.4 178.5 197.8 164.2 203.6C149.8 209.3 136.3 216.4 123.5 223.5C110.6 230.6 98.4 237.8 88.1 239.9Z',
+ 'M71.1 290.1C64.6 291.7 60 289.9 54.7 286.5C49.3 283.1 43.4 278.2 38.6 268.1C33.8 258.1 30.2 242.9 30 227.3C29.7 211.7 32.8 195.7 37.7 182.6C42.7 169.5 49.5 159.3 60.6 149.1C71.6 139 86.9 128.9 102.8 121.9C118.8 114.9 135.5 110.9 152.4 109.5C169.3 108.1 186.4 109.2 204.4 113.6C222.4 118 241.3 125.7 257.8 134.4C274.3 143.2 288.4 153 300.2 164.8C311.9 176.6 321.3 190.3 326 199.5C330.7 208.8 330.8 213.6 329.7 217C328.7 220.4 326.5 222.5 323.1 224.4C319.6 226.3 314.8 228.1 299.3 226.3C283.8 224.5 257.6 219 235.9 217.3C214.2 215.5 197 217.4 181.5 220.5C165.9 223.6 151.9 227.9 137.6 237.7C123.3 247.5 108.7 263 97.2 273.2C85.8 283.4 77.5 288.5 71.1 290.1Z',
+];
+
+const VIEWBOX = {x: 28, y: 28, w: 304, h: 265};
+export const LOGO_ASPECT = VIEWBOX.h / VIEWBOX.w;
+
+// Fill opacity of each layer in the finished logo.
+export const LOGO_FILL_OPACITY = 0.36;
+
+export type LayerState = {
+ /** Offset from the layer's resting place, in logo units. */
+ dx?: number;
+ dy?: number;
+ /** 0 to 1: how much of the outline is drawn. */
+ stroke?: number;
+ strokeOpacity?: number;
+ /** 0 to 1, as a fraction of the finished logo's fill opacity. */
+ fill?: number;
+};
+
+const FINISHED: LayerState = {stroke: 0, strokeOpacity: 0, fill: 1};
+
+export const Logo: React.FC<{
+ width: number;
+ layers?: LayerState[];
+ style?: React.CSSProperties;
+}> = ({width, layers, style}) => {
+ const gradientId = `layeris-fill-${useId().replace(/[^a-zA-Z0-9]/g, '')}`;
+ return (
+
+ );
+};
diff --git a/video/src/components/Photo.tsx b/video/src/components/Photo.tsx
new file mode 100644
index 0000000..69769b0
--- /dev/null
+++ b/video/src/components/Photo.tsx
@@ -0,0 +1,66 @@
+import React from 'react';
+import {Img, staticFile} from 'remotion';
+import {colors} from '../theme';
+
+export const Photo: React.FC<{
+ src: string;
+ width: number;
+ height: number;
+ style?: React.CSSProperties;
+}> = ({src, width, height, style}) => (
+
+);
+
+/**
+ * `before` with `after` revealed from the left up to `t` (0 to 1), with a thin
+ * divider at the edge while the reveal is in progress.
+ */
+export const Wipe: React.FC<{
+ before: string;
+ after: string;
+ t: number;
+ width: number;
+ height: number;
+ divider?: boolean;
+}> = ({before, after, t, width, height, divider = true}) => {
+ const x = t * width;
+ const showDivider = divider && t > 0 && t < 1;
+ return (
+
+
+
+
+
+ {showDivider ? (
+
+ ) : null}
+
+ );
+};
diff --git a/video/src/components/Scene.tsx b/video/src/components/Scene.tsx
new file mode 100644
index 0000000..ff752fb
--- /dev/null
+++ b/video/src/components/Scene.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import {AbsoluteFill, useCurrentFrame, useVideoConfig} from 'remotion';
+import {colors, easeInOut, progress} from '../theme';
+
+export const EXIT_FRAMES = 10;
+
+/** A white page whose content fades out over the scene's last frames. */
+export const Scene: React.FC<{
+ children: React.ReactNode;
+ exit?: boolean;
+ exitFrames?: number;
+}> = ({children, exit = true, exitFrames = EXIT_FRAMES}) => {
+ const frame = useCurrentFrame();
+ const {durationInFrames} = useVideoConfig();
+ const out = exit ? progress(frame, durationInFrames - exitFrames, exitFrames, easeInOut) : 0;
+ return (
+
+
+ {children}
+
+
+ );
+};
diff --git a/video/src/components/SectionHeader.tsx b/video/src/components/SectionHeader.tsx
new file mode 100644
index 0000000..81d0e7a
--- /dev/null
+++ b/video/src/components/SectionHeader.tsx
@@ -0,0 +1,76 @@
+import React from 'react';
+import {useCurrentFrame} from 'remotion';
+import {MARGIN, colors, easeInOut, fonts, progress, rise} from '../theme';
+
+export const HEADER_TOP = 84;
+
+/** Mirrors the docs' section headings: a full-width rule, then a serif title. */
+export const SectionHeader: React.FC<{
+ index: string;
+ title: string;
+ subtitle?: React.ReactNode;
+ start?: number;
+}> = ({index, title, subtitle, start = 0}) => {
+ const frame = useCurrentFrame() - start;
+ const rule = progress(frame, 0, 22, easeInOut);
+ return (
+
+
+
+
+ {title}
+
+
+ {index}
+
+
+ {subtitle ? (
+
+ {subtitle}
+
+ ) : null}
+
+ );
+};
diff --git a/video/src/components/Wordmark.tsx b/video/src/components/Wordmark.tsx
new file mode 100644
index 0000000..02e5f1d
--- /dev/null
+++ b/video/src/components/Wordmark.tsx
@@ -0,0 +1,66 @@
+import React from 'react';
+import {colors, fonts} from '../theme';
+
+export const WORD = 'layeris';
+export const FRAMES_PER_CHAR = 2;
+
+/**
+ * The lowercase DM Mono wordmark from the docs home page, typed one letter at
+ * a time. Every letter is laid out from the start so the word never shifts.
+ */
+export const Wordmark: React.FC<{
+ fontSize: number;
+ frame: number;
+ start: number;
+ /** Frames the caret keeps blinking after the last letter. */
+ caretHold?: number;
+}> = ({fontSize, frame, start, caretHold = 20}) => {
+ const typed = Math.max(0, Math.min(WORD.length, Math.floor((frame - start) / FRAMES_PER_CHAR) + 1));
+ const typingEnd = start + WORD.length * FRAMES_PER_CHAR;
+ const caretVisible =
+ frame >= start - 6 &&
+ frame < typingEnd + caretHold &&
+ (frame < typingEnd || Math.floor((frame - typingEnd) / 10) % 2 === 0);
+ return (
+
+ {WORD.split('').map((ch, i) => (
+ = start && i < typed ? 1 : 0, position: 'relative'}}>
+ {ch}
+ {caretVisible && i === (frame < start ? -1 : typed - 1) ? : null}
+
+ ))}
+ {caretVisible && frame < start ? (
+
+
+
+ ) : null}
+
+ );
+};
+
+const Caret: React.FC<{fontSize: number}> = ({fontSize}) => (
+
+);
diff --git a/video/src/components/logoMotion.ts b/video/src/components/logoMotion.ts
new file mode 100644
index 0000000..e7423a4
--- /dev/null
+++ b/video/src/components/logoMotion.ts
@@ -0,0 +1,24 @@
+import {easeInOut, easeOut, progress} from '../theme';
+import type {LayerState} from './Logo';
+
+// Layers start fanned out vertically, like an exploded layer stack, with
+// their outlines drawing in. They then settle into place as the fills bloom,
+// and the overlap of the translucent fills forms the finished logo.
+const SPREAD = [-58, 0, 58];
+const STAGGER = 6;
+
+export const LOGO_ASSEMBLY_FRAMES = 92;
+
+export const assembleLogo = (frame: number, speed = 1): LayerState[] => {
+ const f = frame * speed;
+ return SPREAD.map((spread, i) => {
+ const s = i * STAGGER;
+ const settle = progress(f, 26 + s, 40, easeInOut);
+ return {
+ dy: spread * (1 - settle),
+ stroke: progress(f, s, 42, easeInOut),
+ strokeOpacity: 1 - progress(f, 62, 26),
+ fill: progress(f, 30 + s, 40, easeOut),
+ };
+ });
+};
diff --git a/video/src/data/assets.json b/video/src/data/assets.json
new file mode 100644
index 0000000..5d8da37
--- /dev/null
+++ b/video/src/data/assets.json
@@ -0,0 +1,199 @@
+{
+ "chain": {
+ "photo": "hanbok-portrait.jpg",
+ "steps": [
+ "grayscale()",
+ "contrast(1.2)",
+ "multiply(\"#f2c49b\", opacity=0.8)",
+ "curve(\"b\", [0.12, 0.4, 0.7, 0.92])"
+ ],
+ "saveAs": "toned.jpg",
+ "images": [
+ "renders/chain/0-original.jpg",
+ "renders/chain/1-grayscale.jpg",
+ "renders/chain/2-contrast.jpg",
+ "renders/chain/3-multiply.jpg",
+ "renders/chain/4-curve.jpg"
+ ]
+ },
+ "blend": {
+ "photo": "jeju-coast.jpg",
+ "color": "#e8743b",
+ "tiles": [
+ {
+ "mode": "original",
+ "image": "renders/blend/original.jpg"
+ },
+ {
+ "mode": "darken",
+ "image": "renders/blend/darken.jpg"
+ },
+ {
+ "mode": "multiply",
+ "image": "renders/blend/multiply.jpg"
+ },
+ {
+ "mode": "color_burn",
+ "image": "renders/blend/color_burn.jpg"
+ },
+ {
+ "mode": "linear_burn",
+ "image": "renders/blend/linear_burn.jpg"
+ },
+ {
+ "mode": "lighten",
+ "image": "renders/blend/lighten.jpg"
+ },
+ {
+ "mode": "screen",
+ "image": "renders/blend/screen.jpg"
+ },
+ {
+ "mode": "color_dodge",
+ "image": "renders/blend/color_dodge.jpg"
+ },
+ {
+ "mode": "linear_dodge",
+ "image": "renders/blend/linear_dodge.jpg"
+ },
+ {
+ "mode": "overlay",
+ "image": "renders/blend/overlay.jpg"
+ },
+ {
+ "mode": "soft_light",
+ "image": "renders/blend/soft_light.jpg"
+ },
+ {
+ "mode": "hard_light",
+ "image": "renders/blend/hard_light.jpg"
+ },
+ {
+ "mode": "vivid_light",
+ "image": "renders/blend/vivid_light.jpg"
+ },
+ {
+ "mode": "linear_light",
+ "image": "renders/blend/linear_light.jpg"
+ },
+ {
+ "mode": "pin_light",
+ "image": "renders/blend/pin_light.jpg"
+ }
+ ]
+ },
+ "curves": {
+ "photo": "bamboo-forest.jpg",
+ "phases": [
+ {
+ "channels": "r",
+ "start": [
+ 0.0,
+ 0.25,
+ 0.5,
+ 0.75,
+ 1.0
+ ],
+ "target": [
+ 0,
+ 0.1,
+ 0.5,
+ 0.9,
+ 1
+ ],
+ "images": [
+ "renders/curves/0-r-00.jpg",
+ "renders/curves/0-r-01.jpg",
+ "renders/curves/0-r-02.jpg",
+ "renders/curves/0-r-03.jpg",
+ "renders/curves/0-r-04.jpg",
+ "renders/curves/0-r-05.jpg",
+ "renders/curves/0-r-06.jpg",
+ "renders/curves/0-r-07.jpg",
+ "renders/curves/0-r-08.jpg",
+ "renders/curves/0-r-09.jpg",
+ "renders/curves/0-r-10.jpg",
+ "renders/curves/0-r-11.jpg",
+ "renders/curves/0-r-12.jpg",
+ "renders/curves/0-r-13.jpg",
+ "renders/curves/0-r-14.jpg",
+ "renders/curves/0-r-15.jpg",
+ "renders/curves/0-r-16.jpg",
+ "renders/curves/0-r-17.jpg",
+ "renders/curves/0-r-18.jpg",
+ "renders/curves/0-r-19.jpg",
+ "renders/curves/0-r-20.jpg"
+ ]
+ },
+ {
+ "channels": "b",
+ "start": [
+ 0.0,
+ 0.25,
+ 0.5,
+ 0.75,
+ 1.0
+ ],
+ "target": [
+ 0.2,
+ 0.36,
+ 0.5,
+ 0.62,
+ 0.78
+ ],
+ "images": [
+ "renders/curves/1-b-00.jpg",
+ "renders/curves/1-b-01.jpg",
+ "renders/curves/1-b-02.jpg",
+ "renders/curves/1-b-03.jpg",
+ "renders/curves/1-b-04.jpg",
+ "renders/curves/1-b-05.jpg",
+ "renders/curves/1-b-06.jpg",
+ "renders/curves/1-b-07.jpg",
+ "renders/curves/1-b-08.jpg",
+ "renders/curves/1-b-09.jpg",
+ "renders/curves/1-b-10.jpg",
+ "renders/curves/1-b-11.jpg",
+ "renders/curves/1-b-12.jpg",
+ "renders/curves/1-b-13.jpg",
+ "renders/curves/1-b-14.jpg",
+ "renders/curves/1-b-15.jpg",
+ "renders/curves/1-b-16.jpg",
+ "renders/curves/1-b-17.jpg",
+ "renders/curves/1-b-18.jpg",
+ "renders/curves/1-b-19.jpg",
+ "renders/curves/1-b-20.jpg"
+ ]
+ }
+ ]
+ },
+ "looks": [
+ {
+ "slug": "cross-process",
+ "title": "Cross-processed",
+ "photo": "gamcheon-village.jpg",
+ "code": "(image.curve(\"r\", [0, 0.15, 0.55, 0.9, 1])\n .curve(\"g\", [0, 0.2, 0.55, 0.85, 1])\n .curve(\"b\", [0.22, 0.35, 0.48, 0.6, 0.72]))",
+ "preset": null,
+ "before": "renders/looks/cross-process-before.jpg",
+ "after": "renders/looks/cross-process-after.jpg"
+ },
+ {
+ "slug": "cyanotype",
+ "title": "Cyanotype",
+ "photo": "palace-pond.jpg",
+ "code": "(image.grayscale()\n .curve(\"rgb\", [0, 0.1, 0.55, 0.9, 1])\n .curve(\"r\", [0.02, 0.85])\n .curve(\"g\", [0.15, 0.95])\n .curve(\"b\", [0.35, 1.0]))",
+ "preset": null,
+ "before": "renders/looks/cyanotype-before.jpg",
+ "after": "renders/looks/cyanotype-after.jpg"
+ },
+ {
+ "slug": "duotone",
+ "title": "Duotone, from a JSON preset",
+ "photo": "palace-eaves.jpg",
+ "code": "image.apply_from_json(\"duotone.json\")",
+ "preset": "{\n \"operations\": [\n {\"type\": \"grayscale\"},\n {\"type\": \"contrast\", \"factor\": 1.2},\n {\"type\": \"curve\", \"channels\": \"r\", \"curve_points\": [0.12, 1.0]},\n {\"type\": \"curve\", \"channels\": \"g\", \"curve_points\": [0.08, 0.45]},\n {\"type\": \"curve\", \"channels\": \"b\", \"curve_points\": [0.4, 0.42]}\n ]\n}",
+ "before": "renders/looks/duotone-before.jpg",
+ "after": "renders/looks/duotone-after.jpg"
+ }
+ ]
+}
diff --git a/video/src/fonts.ts b/video/src/fonts.ts
new file mode 100644
index 0000000..10af602
--- /dev/null
+++ b/video/src/fonts.ts
@@ -0,0 +1,26 @@
+// The same three families as the docs site, bundled locally so renders never
+// depend on network access.
+import '@fontsource/dm-mono/400.css';
+import '@fontsource/dm-mono/500.css';
+import '@fontsource/schibsted-grotesk/400.css';
+import '@fontsource/schibsted-grotesk/500.css';
+import '@fontsource/schibsted-grotesk/600.css';
+import '@fontsource/besley/500.css';
+import {continueRender, delayRender} from 'remotion';
+
+const FACES = [
+ '400 16px "DM Mono"',
+ '500 16px "DM Mono"',
+ '400 16px "Schibsted Grotesk"',
+ '500 16px "Schibsted Grotesk"',
+ '600 16px "Schibsted Grotesk"',
+ '500 16px "Besley"',
+];
+
+const handle = delayRender('Loading fonts');
+Promise.all(FACES.map((face) => document.fonts.load(face)))
+ .then(() => continueRender(handle))
+ .catch((err) => {
+ console.error(err);
+ continueRender(handle);
+ });
diff --git a/video/src/index.ts b/video/src/index.ts
new file mode 100644
index 0000000..d831f7b
--- /dev/null
+++ b/video/src/index.ts
@@ -0,0 +1,4 @@
+import {registerRoot} from 'remotion';
+import {RemotionRoot} from './Root';
+
+registerRoot(RemotionRoot);
diff --git a/video/src/scenes/BlendModes.tsx b/video/src/scenes/BlendModes.tsx
new file mode 100644
index 0000000..45a6609
--- /dev/null
+++ b/video/src/scenes/BlendModes.tsx
@@ -0,0 +1,97 @@
+import React from 'react';
+import {useCurrentFrame} from 'remotion';
+import {Photo, Wipe} from '../components/Photo';
+import {Scene} from '../components/Scene';
+import {SectionHeader} from '../components/SectionHeader';
+import assets from '../data/assets.json';
+import {MARGIN, colors, easeInOut, fonts, progress, rise} from '../theme';
+
+const {blend} = assets;
+
+const COLS = 5;
+const TILE_W = 312;
+const TILE_H = 208;
+const GAP_X = (1920 - 2 * MARGIN - COLS * TILE_W) / (COLS - 1);
+const GAP_Y = 24;
+const GRID_TOP = 300;
+
+const TILES_IN = 14;
+const APPLY_START = 36;
+const APPLY_EVERY = 6;
+const WIPE_FRAMES = 18;
+
+export const BLEND_FRAMES = APPLY_START + 13 * APPLY_EVERY + WIPE_FRAMES + 58;
+
+const Swatch: React.FC = () => (
+
+);
+
+export const BlendModes: React.FC = () => {
+ const frame = useCurrentFrame();
+ return (
+
+
+ Each tile blends
+
+ "{blend.color}"
+ {' '}
+ over the photo. The blend layer can also be a NumPy array.
+ >
+ }
+ />
+ {blend.tiles.map((tile, k) => {
+ const col = k % COLS;
+ const row = Math.floor(k / COLS);
+ const applyAt = APPLY_START + (k - 1) * APPLY_EVERY;
+ const t = k === 0 ? 1 : progress(frame, applyAt, WIPE_FRAMES, easeInOut);
+ const labelIn = k === 0 ? progress(frame, TILES_IN + 8, 10) : progress(frame, applyAt + 4, 10);
+ return (
+
+ {k === 0 || t >= 1 ? (
+
+ ) : (
+
+ )}
+
+ {tile.mode}
+
+
+ );
+ })}
+
+ );
+};
diff --git a/video/src/scenes/Chain.tsx b/video/src/scenes/Chain.tsx
new file mode 100644
index 0000000..003de9e
--- /dev/null
+++ b/video/src/scenes/Chain.tsx
@@ -0,0 +1,136 @@
+import React from 'react';
+import {interpolate, useCurrentFrame} from 'remotion';
+import {Code, caretBlink, typeLines} from '../components/Code';
+import {Photo, Wipe} from '../components/Photo';
+import {Scene} from '../components/Scene';
+import {SectionHeader} from '../components/SectionHeader';
+import assets from '../data/assets.json';
+import {MARGIN, colors, easeInOut, fonts, progress, rise} from '../theme';
+
+const {chain} = assets;
+
+const CODE = [
+ 'from layeris import LayerImage',
+ '',
+ `image = LayerImage.from_file("${chain.photo}")`,
+ ...chain.steps.map((step, i) => (i === 0 ? `(image.${step}` : ` .${step}`)),
+ ` .save("${chain.saveAs}"))`,
+].join('\n');
+
+const FIRST_STEP_LINE = 3;
+const SAVE_LINE = FIRST_STEP_LINE + chain.steps.length;
+
+const CODE_IN = 12;
+const IMAGE_IN = 50;
+const STEP_START = 72;
+const STEP_EVERY = 52;
+const TYPE_FRAMES = 18;
+const WIPE_FRAMES = 24;
+const CPF = 2.6; // characters typed per frame
+
+const stepStart = (k: number) => STEP_START + k * STEP_EVERY;
+const applyAt = (k: number) => stepStart(k) + TYPE_FRAMES;
+const SAVE_AT = stepStart(chain.steps.length);
+
+export const CHAIN_FRAMES = SAVE_AT + 50;
+
+const IMAGE_W = 810;
+const IMAGE_H = 540;
+
+export const Chain: React.FC = () => {
+ const frame = useCurrentFrame();
+
+ const starts: (number | undefined)[] = [CODE_IN + 6, undefined, CODE_IN + 22];
+ chain.steps.forEach((_, k) => starts.push(stepStart(k)));
+ starts.push(SAVE_AT);
+ const reveal = typeLines(CODE, frame, starts, CPF);
+
+ // The line being typed carries the caret.
+ let caretLine = 0;
+ starts.forEach((s, i) => {
+ if (s !== undefined && frame >= s) caretLine = i;
+ });
+ const lastTyped = SAVE_AT + 12;
+
+ // Which step's result is showing, and how far the next one has wiped in.
+ let shown = 0;
+ let wipe = 0;
+ chain.steps.forEach((_, k) => {
+ const t = progress(frame, applyAt(k), WIPE_FRAMES, easeInOut);
+ if (t >= 1) shown = k + 1;
+ else if (t > 0) wipe = t;
+ });
+
+ // Tint the most recently applied line until the next one applies.
+ let highlight: {line: number; opacity: number} | undefined;
+ chain.steps.forEach((_, k) => {
+ if (frame >= applyAt(k)) {
+ highlight = {line: FIRST_STEP_LINE + k, opacity: progress(frame, applyAt(k), 8)};
+ }
+ });
+ if (frame >= SAVE_AT + 14) {
+ highlight = {line: SAVE_LINE, opacity: progress(frame, SAVE_AT + 14, 8)};
+ }
+
+ let caption = chain.photo;
+ if (shown > 0 || wipe > 0) {
+ const k = wipe > 0 ? shown : shown - 1;
+ caption = `.${chain.steps[k]}`;
+ }
+ if (frame >= SAVE_AT + 14) caption = `saved ${chain.saveAs}`;
+
+ const imageIn = progress(frame, IMAGE_IN, 16);
+
+ return (
+
+
+
+
+
+
+ {wipe > 0 ? (
+
+ ) : (
+
+ )}
+
+ {caption}
+
+
+
+ );
+};
diff --git a/video/src/scenes/Curves.tsx b/video/src/scenes/Curves.tsx
new file mode 100644
index 0000000..bcfe89c
--- /dev/null
+++ b/video/src/scenes/Curves.tsx
@@ -0,0 +1,158 @@
+import React from 'react';
+import {AbsoluteFill, Img, staticFile, useCurrentFrame} from 'remotion';
+import {Code, caretBlink, typeLines} from '../components/Code';
+import {Photo} from '../components/Photo';
+import {Scene} from '../components/Scene';
+import {SectionHeader} from '../components/SectionHeader';
+import assets from '../data/assets.json';
+import {MARGIN, colors, easeInOut, fonts, progress, rise} from '../theme';
+
+const {curves} = assets;
+
+const IMAGE_W = 960;
+const IMAGE_H = 640;
+const TOP = 300;
+const COL_X = 1920 - MARGIN - 640;
+const GRAPH = 420;
+const GRAPH_TOP = TOP + 50;
+
+const CHANNEL_COLORS: Record = {r: colors.red, g: colors.green, b: colors.blue};
+
+const IN = 14;
+const PHASE_FRAMES = 66;
+// Per phase: when its code line starts typing, and when its points start moving.
+const PHASES = curves.phases.map((_, i) => {
+ const type = 30 + i * 100;
+ return {type, move: type + 22};
+});
+const LAST_TYPED = PHASES[PHASES.length - 1].type + 22;
+
+export const CURVES_FRAMES = PHASES[PHASES.length - 1].move + PHASE_FRAMES + 40;
+
+const lerp = (a: number[], b: number[], t: number) => a.map((x, i) => x + (b[i] - x) * t);
+
+const fmt = (points: number[]) => `[${points.map((v) => v.toFixed(2)).join(', ')}]`;
+
+export const Curves: React.FC = () => {
+ const frame = useCurrentFrame();
+ const t = curves.phases.map((_, i) => progress(frame, PHASES[i].move, PHASE_FRAMES, easeInOut));
+ const points = curves.phases.map((p, i) => lerp(p.start, p.target, t[i]));
+
+ const code = curves.phases
+ .map((p, i) => `image.curve("${p.channels}", ${fmt(points[i])})`)
+ .join('\n');
+ const reveal = typeLines(code, frame, PHASES.map((p) => p.type), 2.6);
+ const caretLine = frame >= PHASES[1]?.type ? 1 : 0;
+
+ // The image for the furthest phase that has started, crossfading between
+ // the two renders either side of the current point positions.
+ let phase = 0;
+ PHASES.forEach((p, i) => {
+ if (frame >= p.move) phase = i;
+ });
+ const images = curves.phases[phase].images;
+ const pos = t[phase] * (images.length - 1);
+ const lo = Math.floor(pos);
+ const hi = Math.min(images.length - 1, lo + 1);
+ const mix = pos - lo;
+
+ const toX = (x: number) => x * GRAPH;
+ const toY = (y: number) => GRAPH - y * GRAPH;
+
+ return (
+
+
+
+
+
+ {mix > 0 ? (
+
+
+
+ ) : null}
+
+
+
+
+
+ {curves.phases.map((p, i) => (
+
+
+ {p.channels}
+
+ ))}
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/video/src/scenes/Install.tsx b/video/src/scenes/Install.tsx
new file mode 100644
index 0000000..326231e
--- /dev/null
+++ b/video/src/scenes/Install.tsx
@@ -0,0 +1,97 @@
+import React from 'react';
+import {interpolate, useCurrentFrame} from 'remotion';
+import {Code, caretBlink, typeLines} from '../components/Code';
+import {Scene} from '../components/Scene';
+import {SectionHeader} from '../components/SectionHeader';
+import {MARGIN, colors, easeInOut, fonts, progress, rise} from '../theme';
+
+// The wheel name and size match `pip wheel .` for this version.
+const WHEEL = 'layeris-0.4.0-py3-none-any.whl';
+const WHEEL_KB = 19;
+
+const LINES = [
+ '$ pip install layeris',
+ 'Collecting layeris',
+ ` Downloading ${WHEEL} (${WHEEL_KB} kB)`,
+ '', // the progress bar is drawn over this line
+ 'Successfully installed layeris-0.4.0',
+];
+const BAR_LINE = 3;
+
+const FONT_SIZE = 30;
+const LINE_HEIGHT = Math.round(FONT_SIZE * 1.7);
+const PADDING: [number, number] = [28, 40];
+const BAR_WIDTH = 520;
+// Sized for the longest line so the block doesn't widen as lines appear.
+const BLOCK_WIDTH = Math.ceil(Math.max(...LINES.map((l) => l.length)) * FONT_SIZE * 0.6) + 2 * PADDING[1] + 8;
+
+const BLOCK_IN = 12;
+const TYPE_START = BLOCK_IN + 6;
+const TYPE_END = TYPE_START + 14;
+const COLLECTING_AT = TYPE_END + 4;
+const DOWNLOADING_AT = COLLECTING_AT + 5;
+const BAR_START = DOWNLOADING_AT + 3;
+const BAR_FRAMES = 15;
+const DONE_AT = BAR_START + BAR_FRAMES + 4;
+
+export const INSTALL_FRAMES = DONE_AT + 44;
+
+export const Install: React.FC = () => {
+ const frame = useCurrentFrame();
+ const shown = (at: number) => (frame >= at ? Infinity : 0);
+ const reveal = [
+ typeLines(LINES[0], frame, [TYPE_START], 1.5)[0],
+ shown(COLLECTING_AT),
+ shown(DOWNLOADING_AT),
+ 0,
+ shown(DONE_AT),
+ ];
+ const bar = progress(frame, BAR_START, BAR_FRAMES, easeInOut);
+ const kb = interpolate(bar, [0, 1], [0, WHEEL_KB]);
+
+ return (
+
+
+
+
+
+ {frame >= BAR_START ? (
+
+
+
+
+
+ {kb.toFixed(1)}/{WHEEL_KB.toFixed(1)} kB
+
+
+ ) : null}
+
+
+
+ );
+};
diff --git a/video/src/scenes/Intro.tsx b/video/src/scenes/Intro.tsx
new file mode 100644
index 0000000..f671e76
--- /dev/null
+++ b/video/src/scenes/Intro.tsx
@@ -0,0 +1,86 @@
+import React from 'react';
+import {AbsoluteFill, Audio, Sequence, interpolate, staticFile, useCurrentFrame} from 'remotion';
+import {Logo, LOGO_ASPECT} from '../components/Logo';
+import {LOGO_ASSEMBLY_FRAMES, assembleLogo} from '../components/logoMotion';
+import {Scene} from '../components/Scene';
+import {FRAMES_PER_CHAR, WORD, Wordmark} from '../components/Wordmark';
+import {colors, easeInOut, fonts, progress, rise} from '../theme';
+
+export const INTRO_FRAMES = 140;
+
+const LOGO_WIDTH = 440;
+const SPEED = 2;
+// After assembling, the logo shrinks and moves up to sit above the wordmark.
+const LIFT_START = Math.ceil(LOGO_ASSEMBLY_FRAMES / SPEED);
+const LIFT_FRAMES = 18;
+const TYPE_START = LIFT_START + LIFT_FRAMES + 4;
+
+// One keystroke per letter, on the frame the letter appears. The levels vary
+// a little so the clicks don't sound like a loop.
+const KEY_VOLUMES = [0.5, 0.42, 0.48, 0.4, 0.5, 0.44, 0.46];
+
+const Keystrokes: React.FC = () => (
+ <>
+ {WORD.split('').map((_, i) => (
+
+
+
+ ))}
+ >
+);
+
+export const Intro: React.FC = () => {
+ const frame = useCurrentFrame();
+ const lift = progress(frame, LIFT_START, LIFT_FRAMES, easeInOut);
+ const scale = interpolate(lift, [0, 1], [1, 0.52]);
+ const logoHeight = LOGO_WIDTH * LOGO_ASPECT;
+ const logoY = interpolate(lift, [0, 1], [540 - logoHeight / 2, 228]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ Photoshop-style blend modes, adjustments and curves for Python.
+
+
+
+ );
+};
diff --git a/video/src/scenes/Looks.tsx b/video/src/scenes/Looks.tsx
new file mode 100644
index 0000000..7fe107a
--- /dev/null
+++ b/video/src/scenes/Looks.tsx
@@ -0,0 +1,144 @@
+import React from 'react';
+import {Sequence, useCurrentFrame} from 'remotion';
+import {Code} from '../components/Code';
+import {Photo, Wipe} from '../components/Photo';
+import {EXIT_FRAMES, Scene} from '../components/Scene';
+import {SectionHeader} from '../components/SectionHeader';
+import assets from '../data/assets.json';
+import {MARGIN, colors, easeInOut, fonts, progress, rise} from '../theme';
+
+const {looks} = assets;
+
+type LookData = (typeof looks)[number];
+
+const FIRST_DELAY = 12;
+const WIPE_FRAMES = 32;
+const HOLD_FRAMES = 42;
+// The next look's photo fades in over the previous result for this long.
+const SWAP_FRAMES = 10;
+
+const IMAGE_W = 840;
+const IMAGE_H = 560;
+const TOP = 318;
+
+const pythonCode = (look: LookData) =>
+ `image = LayerImage.from_file("${look.photo}")\n${look.code}`;
+
+/** When each part of a look appears, in frames from the look's start. */
+const timing = (look: LookData, last: boolean) => {
+ const codeStart = 6;
+ const presetLines = look.preset ? look.preset.split('\n').length : 0;
+ const pythonStart = codeStart + (look.preset ? presetLines * 2 + 6 : 0);
+ const wipeStart = pythonStart + pythonCode(look).split('\n').length * 3 + 10;
+ // The last look also covers the scene's fade out.
+ const duration = wipeStart + WIPE_FRAMES + HOLD_FRAMES + (last ? EXIT_FRAMES : 0);
+ return {codeStart, pythonStart, wipeStart, duration};
+};
+
+const isLast = (i: number) => i === looks.length - 1;
+
+const STARTS = looks.reduce(
+ (acc, _look, i) => [...acc, i === 0 ? FIRST_DELAY : acc[i - 1] + timing(looks[i - 1], false).duration],
+ [],
+);
+
+export const LOOKS_FRAMES = STARTS[looks.length - 1] + timing(looks[looks.length - 1], true).duration;
+
+/** Lines appear one after another rather than being typed. */
+const lineReveal = (code: string, frame: number, start: number, every = 3) =>
+ code.split('\n').map((_, i) => (frame >= start + i * every ? Infinity : 0));
+
+/**
+ * One look. Looks follow each other without a fade out: the text swaps
+ * straight away and the new photo fades in over `previous`, the last result.
+ */
+const Look: React.FC<{look: LookData; last: boolean; previous?: string}> = ({
+ look,
+ last,
+ previous,
+}) => {
+ const frame = useCurrentFrame();
+ const {codeStart, pythonStart, wipeStart} = timing(look, last);
+ const python = pythonCode(look);
+ const presetName = `${look.slug}.json`;
+ const t = progress(frame, wipeStart, WIPE_FRAMES, easeInOut);
+ const textIn = (start: number) => rise(frame, start, previous ? 8 : 14, previous ? 10 : 22);
+
+ return (
+
+
+
+ {look.title}
+
+ {look.preset ? (
+
+
+ {presetName}
+
+
+
+ ) : null}
+
+
+
+
+
+
+ {previous ? : null}
+
+
+
+
+
+ {look.photo}
+ {look.slug}
+
+
+
+ );
+};
+
+export const Looks: React.FC = () => (
+
+
+ {looks.map((look, i) => (
+
+
+
+ ))}
+
+);
diff --git a/video/src/scenes/Outro.tsx b/video/src/scenes/Outro.tsx
new file mode 100644
index 0000000..0a27496
--- /dev/null
+++ b/video/src/scenes/Outro.tsx
@@ -0,0 +1,75 @@
+import React from 'react';
+import {useCurrentFrame} from 'remotion';
+import {Code} from '../components/Code';
+import {Logo} from '../components/Logo';
+import {assembleLogo} from '../components/logoMotion';
+import {Scene} from '../components/Scene';
+import {colors, fonts, progress, rise} from '../theme';
+
+export const OUTRO_FRAMES = 186;
+
+const LOGO_WIDTH = 230;
+const SPEED = 1.5;
+
+export const Outro: React.FC = () => {
+ const frame = useCurrentFrame();
+ return (
+
+
+
+
+ layeris
+
+
+
+
+
+ github.com/subwaymatch/layer-is-python
+ subwaymatch.github.io/layer-is-python
+
+
+ MIT License
+
+
+
+ );
+};
diff --git a/video/src/theme.ts b/video/src/theme.ts
new file mode 100644
index 0000000..393c1e9
--- /dev/null
+++ b/video/src/theme.ts
@@ -0,0 +1,58 @@
+import {Easing, interpolate} from 'remotion';
+
+export const WIDTH = 1920;
+export const HEIGHT = 1080;
+export const FPS = 30;
+
+// Page gutter shared by every scene.
+export const MARGIN = 120;
+
+// Light-theme tokens from docs/assets/site.css.
+export const colors = {
+ bg: '#ffffff',
+ ink: '#1a1b1e',
+ muted: '#5f6268',
+ faint: '#75777c',
+ paren: '#a3a5a9',
+ rule: '#e4e4e2',
+ code: '#f5f5f3',
+ hover: '#ebebe8',
+ comment: '#6d7076',
+ string: '#2d6a47',
+ number: '#a0561b',
+ call: '#2a4f8a',
+ // Logo gradient edge colour.
+ cyan: '#00b9d4',
+ // Channel colours for the curves graph.
+ red: '#e0474c',
+ green: '#30a46c',
+ blue: '#3e63dd',
+};
+
+export const fonts = {
+ sans: '"Schibsted Grotesk", system-ui, sans-serif',
+ serif: '"Besley", Georgia, serif',
+ mono: '"DM Mono", ui-monospace, Menlo, monospace',
+};
+
+export const easeOut = Easing.bezier(0.16, 1, 0.3, 1);
+export const easeInOut = Easing.bezier(0.65, 0, 0.35, 1);
+
+/** 0 to 1 over [start, start + duration], clamped and eased. */
+export const progress = (
+ frame: number,
+ start: number,
+ duration: number,
+ easing: (t: number) => number = easeOut,
+) =>
+ interpolate(frame, [start, start + duration], [0, 1], {
+ extrapolateLeft: 'clamp',
+ extrapolateRight: 'clamp',
+ easing,
+ });
+
+/** Fade in while rising a few pixels: the default entrance for text. */
+export const rise = (frame: number, start: number, distance = 14, duration = 22) => {
+ const t = progress(frame, start, duration);
+ return {opacity: t, transform: `translateY(${(1 - t) * distance}px)`};
+};
diff --git a/video/tsconfig.json b/video/tsconfig.json
new file mode 100644
index 0000000..de6c09c
--- /dev/null
+++ b/video/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ES2022",
+ "moduleResolution": "bundler",
+ "jsx": "react-jsx",
+ "strict": true,
+ "noEmit": true,
+ "skipLibCheck": true,
+ "resolveJsonModule": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "noUnusedLocals": true
+ },
+ "include": ["src", "remotion.config.ts"]
+}