diff --git a/src/App.tsx b/src/App.tsx index 8743bf7..e565da5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,131 +1,193 @@ -import { useCallback, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { measureSpring, SpringConfig } from "remotion"; -import { AnimationPreview } from "./AnimationPreview"; import { Sidebar } from "./Sidebar"; import { CanvasWrapper } from "./CanvasWrapper"; import { DEFAULT_DAMPING, DEFAULT_MASS, DEFAULT_STIFFNESS } from "./defaults"; import { Header } from "./Header"; +import { Ball } from "./Ball"; const fps = 60; -export type DraggedConfig = SpringConfig & { +export type ExtendedSpringConfig = SpringConfig & { reverse: boolean; durationInFrames: number | null; delay: number; }; +export type DraggedConfig = { + index: number; + config: ExtendedSpringConfig | null; +}; + +const DEFAULT_SPRING = { + damping: DEFAULT_DAMPING, + mass: DEFAULT_MASS, + stiffness: DEFAULT_STIFFNESS, + overshootClamping: false, + reverse: false, + durationInFrames: null, + delay: 0, +}; + function App() { - const [config, setConfig] = useState< - SpringConfig & { - reverse: boolean; - durationInFrames: number | null; - delay: number; - } - >({ - damping: DEFAULT_DAMPING, - mass: DEFAULT_MASS, - stiffness: DEFAULT_STIFFNESS, - overshootClamping: false, - reverse: false, - durationInFrames: null, - delay: 0, + const initialConfig = window.localStorage.getItem("springConfigs") + ? JSON.parse(window.localStorage.getItem("springConfigs") as string) + : [DEFAULT_SPRING]; + const [springConfigs, setSpringConfigs] = + useState(initialConfig); + + const updateLocalStorage = useCallback(() => { + window.localStorage.setItem("springConfigs", JSON.stringify(springConfigs)); + }, [springConfigs]); + const [draggedConfigs, setDraggedConfigs] = useState({ + index: 0, + config: null, }); - const [draggedConfig, setDraggedConfig] = useState( - null - ); + const addSpring = useCallback(() => { + setSpringConfigs([...springConfigs, DEFAULT_SPRING]); + }, [springConfigs]); + + useEffect(() => { + updateLocalStorage(); + }, [springConfigs, updateLocalStorage]); + + const removeSpring = useCallback((index: number) => { + setSpringConfigs((old) => [...old.splice(0, index), ...old.splice(index)]); + }, []); + + const resetSpring = useCallback(() => { + setSpringConfigs([DEFAULT_SPRING]); + }, []); const onMassChange = useCallback( - (e: [number]) => { - setDraggedConfig({ ...config, mass: e[0] }); + (e: number[], index: number) => { + setDraggedConfigs(() => ({ + index, + config: { ...springConfigs[index], mass: e[0] }, + })); }, - [config] + [springConfigs] ); - const onDampingChange = useCallback( - (e: number[]) => { - setDraggedConfig({ ...config, damping: e[0] }); + (e: number[], index: number) => { + setDraggedConfigs(() => ({ + index, + config: { + ...springConfigs[index], + damping: e[0], + }, + })); }, - [config] + [springConfigs] ); const onStiffnessChange = useCallback( - (e: number[]) => { - setDraggedConfig({ ...config, stiffness: e[0] }); + (e: number[], index: number) => { + setDraggedConfigs(() => ({ + index, + config: { + ...springConfigs[index], + stiffness: e[0], + }, + })); }, - [config] + [springConfigs] ); const onDurationInFramesChange = useCallback( - (e: number | null) => { - setDraggedConfig({ ...config, durationInFrames: e }); - setConfig({ ...config, durationInFrames: e }); + (e: number | null, index: number) => { + setDraggedConfigs(() => ({ + index, + config: { + ...springConfigs[index], + durationInFrames: e, + }, + })); + + setSpringConfigs((old) => [ + ...old.slice(0, index), + { + ...springConfigs[index], + durationInFrames: e, + }, + ...old.slice(index + 1), + ]); }, - [config] + + [springConfigs] ); const onDelayChange = useCallback( - (e: number) => { - setDraggedConfig({ ...config, delay: e }); - setConfig({ ...config, delay: e }); + (e: number, index: number) => { + setDraggedConfigs(() => ({ + index, + config: { + ...springConfigs[index], + delay: e, + }, + })); + setSpringConfigs((old) => [ + ...old.slice(0, index), + { ...old[index], delay: e }, + ...old.slice(index + 1), + ]); }, - [config] + [springConfigs] ); const onOvershootClampingChange = useCallback( - (checked: boolean) => { - setDraggedConfig({ - ...config, - overshootClamping: checked, - }); - setConfig({ - ...config, - overshootClamping: checked, - }); + (checked: boolean, index: number) => { + setSpringConfigs((old) => [ + ...old.slice(0, index), + { ...old[index], overshootClamping: checked }, + ...old.slice(index + 1), + ]); }, - [config] + [] ); - const onReverseChange = useCallback( - (checked: boolean) => { - setDraggedConfig({ - ...config, - reverse: checked, - }); - setConfig({ - ...config, - reverse: checked, - }); + const onReverseChange = useCallback((checked: boolean, index: number) => { + setSpringConfigs((old) => [ + ...old.slice(0, index), + { ...old[index], reverse: checked }, + ...old.slice(index + 1), + ]); + }, []); + + const onRelease = useCallback( + (index: number) => { + if (draggedConfigs && draggedConfigs.config) { + setSpringConfigs((old) => [ + ...old.slice(0, index), + draggedConfigs.config as ExtendedSpringConfig, + ...old.slice(index + 1), + ]); + } + setDraggedConfigs({ index: 0, config: null }); }, - [config] + [draggedConfigs] ); - const onRelease = useCallback(() => { - if (draggedConfig) { - setConfig(draggedConfig as DraggedConfig); - } - setDraggedConfig(null); - }, [draggedConfig]); - - const duration = - config.delay + - (config.durationInFrames - ? config.durationInFrames - : measureSpring({ - fps, - threshold: 0.001, - config, - })); - const draggedDuration = draggedConfig - ? draggedConfig.durationInFrames - ? draggedConfig.durationInFrames + draggedConfig.delay - : measureSpring({ + const duration = springConfigs.reduce((max, config) => { + const calculatedDuration = + config.delay + + (config.durationInFrames + ? config.durationInFrames + : measureSpring({ fps, threshold: 0.001, config })); + return calculatedDuration > max ? calculatedDuration : max; + }, 0); + + const draggedDuration = + // eslint-disable-next-line no-negated-condition + draggedConfigs.config !== null + ? measureSpring({ fps, threshold: 0.001, - config: draggedConfig, - }) + draggedConfig.delay - : null; + config: draggedConfigs.config, + }) + : null; return (
0 ? draggedDuration : null + } duration={duration} fps={fps} />
- - - +
{ + return ( +
+
+
+ ); +}; diff --git a/src/Canvas.tsx b/src/Canvas.tsx index 5218685..19a9f7c 100644 --- a/src/Canvas.tsx +++ b/src/Canvas.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useMemo } from "react"; import { draw, stopDrawing } from "./draw"; -import { DraggedConfig } from "./App"; +import { DraggedConfig, ExtendedSpringConfig } from "./App"; import { AnimationDuration } from "./AnimationDuration"; const canvasRef = React.createRef(); @@ -8,17 +8,17 @@ const canvasRef = React.createRef(); export const Canvas: React.FC<{ width: number; height: number; - draggedConfig: DraggedConfig | null; + draggedConfigs: DraggedConfig; draggedDuration: number | null; duration: number; - config: DraggedConfig; + springConfigs: ExtendedSpringConfig[]; fps: number; }> = ({ height, width, - draggedConfig, + draggedConfigs, draggedDuration, - config, + springConfigs, duration, fps, }) => { @@ -48,8 +48,8 @@ export const Canvas: React.FC<{ draw({ ref: canvasRef.current, duration: draggedDuration ?? duration, - config, - draggedConfig, + springConfigs, + draggedConfigs, fps, draggedDuration, height, @@ -59,12 +59,12 @@ export const Canvas: React.FC<{ }, [ draggedDuration, duration, - config, - draggedConfig, + draggedConfigs, fps, width, height, durationLabel, + springConfigs, ]); return ( diff --git a/src/CanvasWrapper.tsx b/src/CanvasWrapper.tsx index 2d7663a..668231b 100644 --- a/src/CanvasWrapper.tsx +++ b/src/CanvasWrapper.tsx @@ -1,17 +1,16 @@ import { PlayerInternals } from "@remotion/player"; import { useRef } from "react"; import { Canvas } from "./Canvas"; -import { DraggedConfig } from "./App"; +import { DraggedConfig, ExtendedSpringConfig } from "./App"; export const CanvasWrapper: React.FC<{ - draggedConfig: DraggedConfig | null; + draggedConfigs: DraggedConfig; draggedDuration: number | null; duration: number; - config: DraggedConfig; + springConfigs: ExtendedSpringConfig[]; fps: number; -}> = ({ config, draggedConfig, draggedDuration, duration, fps }) => { +}> = ({ springConfigs, draggedConfigs, draggedDuration, duration, fps }) => { const outer = useRef(null); - const elementSize = PlayerInternals.useElementSize(outer, { shouldApplyCssTransforms: false, triggerOnWindowResize: true, @@ -30,8 +29,8 @@ export const CanvasWrapper: React.FC<{ > {elementSize ? ( = ({ - damping, - mass, - stiffness, - overshotClamping, - reverse, - platform, - durationInFrames, - delay, -}) => { +const CodeFrame: React.FC<{ + springConfigs: ExtendedSpringConfig[]; + platform: "remotion" | "reanimated"; +}> = ({ springConfigs, platform }) => { const [h, setH] = useState(null); const code = useMemo(() => { - const isDefaultDamping = DEFAULT_DAMPING === damping; - const isDefaultMass = DEFAULT_MASS === mass; - const isDefaultStiffness = DEFAULT_STIFFNESS === stiffness; + const allLines: string[] = []; + + springConfigs.forEach((config, index) => { + const isDefaultDamping = DEFAULT_DAMPING === config.damping; + const isDefaultMass = DEFAULT_MASS === config.mass; + const isDefaultStiffness = DEFAULT_STIFFNESS === config.stiffness; + + const isAllDefault = + isDefaultDamping && isDefaultMass && isDefaultStiffness; - const isAllDefault = - isDefaultDamping && isDefaultMass && isDefaultStiffness; + const lines = [ + `const spr${index + 1} = spring({`, + platform === "remotion" ? " frame," : null, + platform === "remotion" ? " fps," : null, + platform === "reanimated" ? " toValue: 1," : null, + isAllDefault ? null : " config: {", + isDefaultDamping ? null : ` damping: ${config.damping}`, + isDefaultMass ? null : ` mass: ${config.mass}`, + isDefaultStiffness ? null : ` stiffness: ${config.stiffness}`, + isAllDefault ? null : " },", + config.durationInFrames === null || platform === "reanimated" + ? null + : ` durationInFrames: ${config.durationInFrames},`, + config.delay === 0 || platform === "reanimated" + ? 0 + : ` delay: ${config.delay},`, + config.overshootClamping ? " overshootClamping: true," : null, + config.reverse ? " reverse: true," : null, + "});", + ] + .filter(Boolean) + .join("\n"); + + allLines.push(lines); + }); + const additionLine = [`const spr =`]; + springConfigs.forEach((config, index) => { + if (index < springConfigs.length - 1) { + additionLine.push(`spr${index + 1} +`); + } else { + additionLine.push(`spr${index + 1};`); + } + }); + const joined = additionLine.join(" "); + allLines.push(joined); - const lines = [ - "const spr = spring({", - platform === "remotion" ? " frame," : null, - platform === "remotion" ? " fps," : null, - platform === "reanimated" ? " toValue: 1," : null, - isAllDefault ? null : " config: {", - isDefaultDamping ? null : ` damping: ${damping}`, - isDefaultMass ? null : ` mass: ${mass}`, - isDefaultStiffness ? null : ` stiffness: ${stiffness}`, - isAllDefault ? null : " },", - durationInFrames === null || platform === "reanimated" - ? null - : ` durationInFrames: ${durationInFrames},`, - delay === 0 || platform === "reanimated" ? 0 : ` delay: ${delay},`, - overshotClamping ? " overshootClamping: true," : null, - reverse ? " reverse: true," : null, - "});", - ] - .filter(Boolean) - .join("\n"); - return lines; - }, [ - damping, - mass, - stiffness, - platform, - durationInFrames, - delay, - overshotClamping, - reverse, - ]); + return allLines.join("\n\n"); + }, [springConfigs, platform]); useEffect(() => { codeToHtml(code, { @@ -100,7 +89,7 @@ const CodeFrame: React.FC< backgroundColor: "#24292E", paddingTop: 14, paddingLeft: 20, - height: 300, + paddingBottom: 14, borderRadius: 8, }} > @@ -120,7 +109,9 @@ const CodeFrame: React.FC< ); }; -export function CodeFrameTabs(props: Props) { +export function CodeFrameTabs(props: { + springConfigs: ExtendedSpringConfig[]; +}) { return ( @@ -128,10 +119,10 @@ export function CodeFrameTabs(props: Props) { Reanimated - + - + ); diff --git a/src/Sidebar.tsx b/src/Sidebar.tsx index bd92ef2..e51c426 100644 --- a/src/Sidebar.tsx +++ b/src/Sidebar.tsx @@ -1,47 +1,42 @@ import React from "react"; -import { Slider } from "./components/ui/slider"; - -import { SliderLabel } from "./SliderLabel"; -import { CheckboxWithLabel } from "./Checkbox"; -import { Spacing } from "./Spacing"; import { CodeFrameTabs } from "./CodeFrame"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "./components/ui/tabs"; import { PADDING_LEFT } from "./draw-trajectory"; +import { DraggedConfig, ExtendedSpringConfig } from "./App"; +import { SpringControls } from "./SpringControls"; +import { Button } from "./components/ui/button"; +import { Spacing } from "./Spacing"; export const Sidebar: React.FC<{ - mass: number; - damping: number; - stiffness: number; - fixedDurationInFrames: number | null; + springConfigs: ExtendedSpringConfig[]; + draggedConfigs: DraggedConfig; calculatedDurationInFrames: number; - delay: number; - onMassChange: (e: [number]) => void; - onDampingChange: (e: [number]) => void; - onStiffnessChange: (e: [number]) => void; - onDurationInFramesChange: (e: number | null) => void; - onDelayChange: (e: number) => void; - overshootClamping: boolean; - onRelease: () => void; - onOvershootClampingChange: (checked: boolean) => void; - reverse: boolean; - onReverseChange: (checked: boolean) => void; + addSpring: () => void; + removeSpring: (index: number) => void; + onMassChange: (e: number[], index: number) => void; + onDampingChange: (e: number[], index: number) => void; + onStiffnessChange: (e: number[], index: number) => void; + onDurationInFramesChange: (e: number | null, index: number) => void; + onDelayChange: (e: number, index: number) => void; + onRelease: (index: number) => void; + onOvershootClampingChange: (checked: boolean, index: number) => void; + onReverseChange: (checked: boolean, index: number) => void; + resetSpring: () => void; }> = ({ - mass, + springConfigs, + draggedConfigs, onDampingChange, onMassChange, onStiffnessChange, onDurationInFramesChange, - fixedDurationInFrames, - overshootClamping, onRelease, - damping, - stiffness, calculatedDurationInFrames, onOvershootClampingChange, onReverseChange, - reverse, - delay, onDelayChange, + addSpring, + removeSpring, + resetSpring, }) => { return (
@@ -59,92 +55,88 @@ export const Sidebar: React.FC<{ Code - - - -
- - -
- - -
- { - onDelayChange(val[0]); - }} - onPointerUp={onRelease} - /> - -
<> - { - onDurationInFramesChange(val[0]); - }} - onPointerUp={onRelease} - /> - { - if (enabled) { - onDurationInFramesChange(calculatedDurationInFrames); - } else { - onDurationInFramesChange(null); - } - }} - value={fixedDurationInFrames ?? null} - /> + {springConfigs.map((config, idx) => { + return ( + <> + + {idx < springConfigs.length - 1 ? ( +
+ + +
+ ) : null} + + ); + })} +
+
+ + + +
-
- - - - +
diff --git a/src/SpringControls.tsx b/src/SpringControls.tsx new file mode 100644 index 0000000..e0895c1 --- /dev/null +++ b/src/SpringControls.tsx @@ -0,0 +1,173 @@ +import { Slider } from "./components/ui/slider"; +import { Spacing } from "./Spacing"; +import { SliderLabel } from "./SliderLabel"; +import { CheckboxWithLabel } from "./Checkbox"; +import { Button } from "./components/ui/button"; + +export const SpringControls: React.FC<{ + mass: number; + stiffness: number; + delay: number; + damping: number; + overshootClamping: boolean; + reverse: boolean; + onMassChange: (e: number[], index: number) => void; + onDampingChange: (e: number[], index: number) => void; + onStiffnessChange: (e: number[], index: number) => void; + onDurationInFramesChange: (e: number | null, index: number) => void; + onDelayChange: (e: number, index: number) => void; + onRelease: (index: number) => void; + onOvershootClampingChange: (checked: boolean, index: number) => void; + onReverseChange: (checked: boolean, index: number) => void; + fixedDurationInFrames: number | null; + calculatedDurationInFrames: number; + index: number; + removeSpring: (index: number) => void; +}> = ({ + mass, + damping, + stiffness, + delay, + overshootClamping, + reverse, + onDampingChange, + onMassChange, + onStiffnessChange, + onDurationInFramesChange, + onRelease, + onOvershootClampingChange, + onReverseChange, + onDelayChange, + calculatedDurationInFrames, + fixedDurationInFrames, + index, + removeSpring, +}) => { + const crossIcon = ( + + + + ); + return ( +
+
+

+ Spring {index + 1} +

+ {index > 0 ? ( + + ) : null} +
+ + + onMassChange(e, index)} + onPointerUp={() => onRelease(index)} + /> + + + onDampingChange(e, index)} + onPointerUp={() => onRelease(index)} + /> + + + onStiffnessChange(e, index)} + onPointerUp={() => onRelease(index)} + /> + + + { + onDelayChange(val[0], index); + }} + onPointerUp={() => onRelease(index)} + /> + + + { + onDurationInFramesChange(val[0], index); + }} + onPointerUp={() => onRelease(index)} + /> + { + if (enabled) { + onDurationInFramesChange(calculatedDurationInFrames, index); + } else { + onDurationInFramesChange(null, index); + } + }} + value={fixedDurationInFrames ?? null} + /> + onOvershootClampingChange(e, index)} + /> + onReverseChange(e, index)} + /> +
+
+ ); +}; diff --git a/src/draw-trajectory.ts b/src/draw-trajectory.ts index b3b219b..c69e5fc 100644 --- a/src/draw-trajectory.ts +++ b/src/draw-trajectory.ts @@ -59,7 +59,6 @@ export const drawTrajectory = ({ let lastDraw = Date.now(); let stopped = false; - const executeDraw = async () => { for (let i = 0; i < springTrajectory.length; i++) { const timeSinceLastDraw = Date.now() - lastDraw; @@ -92,25 +91,11 @@ export const drawTrajectory = ({ context.stroke(); context.closePath(); lastDraw = Date.now(); - + const divisor = Math.round(max); if (animate) { - ( - document.getElementById("scale") as HTMLElement - ).style.transform = `scale(${springTrajectory[i]})`; - ( - document.getElementById("translate") as HTMLElement - ).style.transform = `translateY(${interpolate( - springTrajectory[i], - [0, 1], - [100, 0] - )}px)`; - ( - document.getElementById("rotate") as HTMLElement - ).style.transform = `rotate(${interpolate( - springTrajectory[i], - [0, 1], - [Math.PI * 2, 0] - )}rad)`; + (document.getElementById("ball") as HTMLElement).style.flex = `${ + springTrajectory[i] / divisor + }`; } } }; diff --git a/src/draw.tsx b/src/draw.tsx index 53624c1..57b89d9 100644 --- a/src/draw.tsx +++ b/src/draw.tsx @@ -7,7 +7,7 @@ import { PADDING_TOP, drawTrajectory, } from "./draw-trajectory"; -import { DraggedConfig } from "./App"; +import { DraggedConfig, ExtendedSpringConfig } from "./App"; import { measureText } from "@remotion/layout-utils"; export let stopDrawing = () => {}; @@ -16,8 +16,8 @@ export const draw = ({ ref, duration, fps, - config, - draggedConfig, + springConfigs, + draggedConfigs, draggedDuration, height, width, @@ -26,26 +26,38 @@ export const draw = ({ ref: HTMLCanvasElement; duration: number; fps: number; - config: DraggedConfig; - draggedConfig: DraggedConfig | null; + springConfigs: ExtendedSpringConfig[]; + draggedConfigs: DraggedConfig; draggedDuration: number | null; width: number; height: number; labelText: string; }) => { const context = ref.getContext("2d"); - if (!context) { return; } context.clearRect(0, 0, width, height); - const trajectory = getTrajectory(duration, fps, config); - const draggedTrajectory = draggedConfig - ? getTrajectory(draggedDuration ?? duration, fps, draggedConfig) - : []; + const trajectory = getTrajectory(duration, fps, springConfigs); + + const hasSomeDragged = draggedConfigs.config !== null; + + const currentIdx = draggedConfigs.index; + const draggedConfigsToDraw = [ + ...springConfigs.slice(0, currentIdx), + draggedConfigs.config, + ...springConfigs.slice(currentIdx + 1), + ]; - const max = draggedConfig + const draggedTrajectory = hasSomeDragged + ? getTrajectory( + draggedDuration ?? duration, + fps, + draggedConfigsToDraw as ExtendedSpringConfig[] + ) + : []; + const max = hasSomeDragged ? Math.max(...draggedTrajectory) : Math.max(...trajectory); @@ -84,20 +96,20 @@ export const draw = ({ context.closePath(); const toStop: (() => void)[] = []; - const stopPrimary = drawTrajectory({ springTrajectory: trajectory, canvasHeight: height, canvasWidth: width, context, max, - primary: !draggedConfig, - animate: !draggedConfig, + primary: !hasSomeDragged, // Used to be !draggedConfig + animate: !hasSomeDragged, fps, }); + toStop.push(stopPrimary); - if (draggedConfig) { + if (hasSomeDragged) { toStop.push( drawTrajectory({ springTrajectory: draggedTrajectory, diff --git a/src/get-trajectory.ts b/src/get-trajectory.ts index fc2b038..9230af1 100644 --- a/src/get-trajectory.ts +++ b/src/get-trajectory.ts @@ -1,19 +1,26 @@ import { spring } from "remotion"; -import { DraggedConfig } from "./App"; +import { ExtendedSpringConfig } from "./App"; export const getTrajectory = ( durationInFrames: number, fps: number, - { reverse, ...config }: DraggedConfig + springConfigs: (ExtendedSpringConfig | null)[] ) => { return new Array(durationInFrames).fill(true).map((_, i) => { - return spring({ - fps, - frame: i, - config, - reverse, - durationInFrames: config.durationInFrames ?? undefined, - delay: config.delay ?? undefined, + let totalValue = 0; + springConfigs.forEach((config) => { + if (!config) { + return; + } + totalValue += spring({ + fps, + frame: i, + config, + reverse: config.reverse, + durationInFrames: config.durationInFrames ?? undefined, + delay: config.delay ?? undefined, + }); }); + return totalValue; }); }; diff --git a/src/index.css b/src/index.css index a881767..a332c44 100644 --- a/src/index.css +++ b/src/index.css @@ -1,7 +1,7 @@ @tailwind base; @tailwind components; @tailwind utilities; - + @layer base { :root { --background: 0 0% 100%; @@ -9,63 +9,63 @@ --card: 0 0% 100%; --card-foreground: 222.2 84% 4.9%; - + --popover: 0 0% 100%; --popover-foreground: 222.2 84% 4.9%; - + --primary: 222.2 47.4% 11.2%; --primary-foreground: 210 40% 98%; - + --secondary: 210 40% 96.1%; --secondary-foreground: 222.2 47.4% 11.2%; - + --muted: 210 40% 96.1%; --muted-foreground: 215.4 16.3% 46.9%; - + --accent: 210 40% 96.1%; --accent-foreground: 222.2 47.4% 11.2%; - + --destructive: 0 84.2% 60.2%; --destructive-foreground: 210 40% 98%; --border: 214.3 31.8% 91.4%; --input: 214.3 31.8% 91.4%; --ring: 222.2 84% 4.9%; - + --radius: 0.5rem; } - + .dark { --background: 240 3.6% 11%; --foreground: 210 40% 98%; - + --card: 222.2 84% 4.9%; --card-foreground: 210 40% 98%; - + --popover: 222.2 84% 4.9%; --popover-foreground: 210 40% 98%; - + --primary: 210 40% 98%; --primary-foreground: 222.2 47.4% 11.2%; - + --secondary: 217.2 32.6% 17.5%; --secondary-foreground: 210 40% 98%; - + --muted: 217.2 32.6% 17.5%; --muted-foreground: 215 20.2% 65.1%; - + --accent: 217.2 32.6% 17.5%; --accent-foreground: 210 40% 98%; - + --destructive: 0 62.8% 30.6%; --destructive-foreground: 210 40% 98%; - + --border: 217.2 32.6% 17.5%; --input: 217.2 32.6% 17.5%; --ring: 212.7 26.8% 83.9%; } } - + @layer base { * { @apply border-border; @@ -106,7 +106,7 @@ #animation-preview { display: flex; flex-direction: row; - justify-content: center; + justify-content: flex-start; } @media screen and (max-width: 900px) {