From e5c4db86b8ac8acf3dd60e2bcbbbba1414da65a8 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Mon, 27 Jan 2025 10:28:47 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=8E:=20Scaffolding=20for=20a=20benchma?= =?UTF-8?q?rking=20tool=20(#805)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/typegpu-docs/package.json | 6 +- .../src/components/design/DeleteIcon.tsx | 84 +++++++ .../typegpu-docs/src/layouts/PageLayout.astro | 12 +- .../src/pages/benchmark/atom-with-url.ts | 67 ++++++ .../src/pages/benchmark/benchmark-app.tsx | 223 ++++++++++++++++++ .../src/pages/benchmark/index.astro | 19 ++ .../src/pages/benchmark/modules.ts | 35 +++ .../src/pages/benchmark/parameter-set-row.tsx | 85 +++++++ .../src/pages/benchmark/parameter-set.ts | 64 +++++ apps/typegpu-docs/tailwind.config.mjs | 12 +- pnpm-lock.yaml | 108 +++++++-- 11 files changed, 687 insertions(+), 28 deletions(-) create mode 100644 apps/typegpu-docs/src/components/design/DeleteIcon.tsx create mode 100644 apps/typegpu-docs/src/pages/benchmark/atom-with-url.ts create mode 100644 apps/typegpu-docs/src/pages/benchmark/benchmark-app.tsx create mode 100644 apps/typegpu-docs/src/pages/benchmark/index.astro create mode 100644 apps/typegpu-docs/src/pages/benchmark/modules.ts create mode 100644 apps/typegpu-docs/src/pages/benchmark/parameter-set-row.tsx create mode 100644 apps/typegpu-docs/src/pages/benchmark/parameter-set.ts diff --git a/apps/typegpu-docs/package.json b/apps/typegpu-docs/package.json index 6642eccb9..a47ac1826 100644 --- a/apps/typegpu-docs/package.json +++ b/apps/typegpu-docs/package.json @@ -27,8 +27,10 @@ "classnames": "^2.5.1", "jotai": "^2.8.4", "jotai-location": "^0.5.5", + "lucide-react": "^0.474.0", "lz-string": "^1.5.0", "monaco-editor": "^0.50.0", + "motion": "^12.0.5", "react": "^18.3.1", "react-dom": "^18.3.1", "remeda": "^2.3.0", @@ -37,6 +39,7 @@ "starlight-blog": "^0.12.0", "starlight-typedoc": "^0.17.0", "tailwindcss": "^3.4.6", + "tinybench": "^3.1.0", "typed-binary": "^4.0.0", "typedoc": "^0.27.1", "typedoc-plugin-markdown": "^4.3.0", @@ -49,6 +52,7 @@ "@types/babel__template": "^7.4.4", "@types/babel__traverse": "^7.20.6", "@webgpu/types": "^0.1.43", - "astro-vtbot": "^1.8.2" + "astro-vtbot": "^1.8.2", + "tailwindcss-motion": "^1.0.1" } } diff --git a/apps/typegpu-docs/src/components/design/DeleteIcon.tsx b/apps/typegpu-docs/src/components/design/DeleteIcon.tsx new file mode 100644 index 000000000..ddb4486fe --- /dev/null +++ b/apps/typegpu-docs/src/components/design/DeleteIcon.tsx @@ -0,0 +1,84 @@ +'use client'; + +import type { Variants } from 'motion/react'; +import { motion, useAnimation } from 'motion/react'; + +const lidVariants: Variants = { + normal: { y: 0 }, + animate: { y: -1.1 }, +}; + +const springTransition = { + type: 'spring', + stiffness: 500, + damping: 30, +}; + +const DeleteIcon = () => { + const controls = useAnimation(); + + return ( +
controls.start('animate')} + onMouseLeave={() => controls.start('normal')} + > + + Delete + + + + + + + + +
+ ); +}; + +export { DeleteIcon }; diff --git a/apps/typegpu-docs/src/layouts/PageLayout.astro b/apps/typegpu-docs/src/layouts/PageLayout.astro index e3c6785e9..00c672b5a 100644 --- a/apps/typegpu-docs/src/layouts/PageLayout.astro +++ b/apps/typegpu-docs/src/layouts/PageLayout.astro @@ -1,10 +1,10 @@ --- import '../tailwind.css'; import '../fonts/font-face.css'; -const { title } = Astro.props; +const { title, theme = 'light' } = Astro.props; --- - + {title ? `${title} |` : ''} TypeGPU @@ -49,6 +49,14 @@ const { title } = Astro.props; margin: 0; } + [data-theme='dark'] body { + background-color: #171724; + color: white; + } + + [data-theme='light'] body { + background-color: white; + } diff --git a/apps/typegpu-docs/src/pages/benchmark/atom-with-url.ts b/apps/typegpu-docs/src/pages/benchmark/atom-with-url.ts new file mode 100644 index 000000000..776dd40de --- /dev/null +++ b/apps/typegpu-docs/src/pages/benchmark/atom-with-url.ts @@ -0,0 +1,67 @@ +import { atom } from 'jotai'; +import { atomWithLocation } from 'jotai-location'; + +const locationAtom = atomWithLocation(); + +export const stringParam = { + encode: (val: string) => val, + decode: (val: string) => val, +}; + +export const boolParam = { + encode: (val: boolean) => (val ? '1' : '0'), + decode: (val: string) => val === '1', +}; + +export const numberParam = { + encode: (val: number) => String(val), + decode: (val: string) => Number.parseFloat(val), +}; + +export const objParam = { + encode: JSON.stringify, + decode: JSON.parse, +}; + +export const typeToParam = { + string: stringParam, + boolean: boolParam, + number: numberParam, + object: objParam, +}; + +export const atomWithUrl = ( + key: string, + defaultValue: T, + options?: { encode: (val: T) => string; decode: (val: string) => unknown }, +) => { + const optionsOrInferred = + options ?? + typeToParam[typeof defaultValue as keyof typeof typeToParam] ?? + objParam; + + const { encode, decode } = optionsOrInferred; + + return atom( + (get) => { + const location = get(locationAtom); + return location.searchParams?.has(key) + ? (decode(location.searchParams.get(key) ?? '') as T) + : defaultValue; + }, + (get, set, newValue: T) => { + const prev = get(locationAtom); + const searchParams = new URLSearchParams(prev.searchParams); + searchParams.set(key, encode(newValue)); + + set( + locationAtom, + { + ...prev, + searchParams: searchParams, + }, + { replace: true }, + ); + }, + ); +}; diff --git a/apps/typegpu-docs/src/pages/benchmark/benchmark-app.tsx b/apps/typegpu-docs/src/pages/benchmark/benchmark-app.tsx new file mode 100644 index 000000000..fd71a53de --- /dev/null +++ b/apps/typegpu-docs/src/pages/benchmark/benchmark-app.tsx @@ -0,0 +1,223 @@ +import { useAtomValue, useSetAtom } from 'jotai/react'; +import { atom } from 'jotai/vanilla'; +import { CirclePlus } from 'lucide-react'; +import { Suspense, useMemo } from 'react'; +import { Bench } from 'tinybench'; +import { importTypeGPU, importTypeGPUData } from './modules.js'; +import { ParameterSetRow } from './parameter-set-row.js'; +import { + type BenchParameterSet, + createParameterSetAtom, + parameterSetAtomsAtom, + parameterSetsAtom, + stringifyLocator, +} from './parameter-set.js'; + +interface BenchResults { + parameterSet: BenchParameterSet; + bench: Bench; +} + +async function runBench(params: BenchParameterSet): Promise { + const { tgpu } = await importTypeGPU(params.typegpu); + const d = await importTypeGPUData(params.typegpu); + + const bench = new Bench({ + name: stringifyLocator('typegpu', params.typegpu), + time: 1000, + }); + + const amountOfBoids = 10000; + + bench + .add('mass boid transfer', async () => { + const root = await tgpu.init(); + + const Boid = d.struct({ + pos: d.vec3f, + vel: d.vec3f, + }); + + const BoidArray = d.arrayOf(Boid, amountOfBoids); + + const buffer = root.createBuffer(BoidArray); + + buffer.write( + Array.from({ length: amountOfBoids }).map(() => ({ + pos: d.vec3f(1, 2, 3), + vel: d.vec3f(4, 5, 6), + })), + ); + + root.destroy(); + }) + .add('mass boid transfer (manual reference)', async () => { + const root = await tgpu.init(); + + const Boid = d.struct({ + pos: d.vec3f, + vel: d.vec3f, + }); + + const BoidArray = d.arrayOf(Boid, amountOfBoids); + + const buffer = root.createBuffer(BoidArray); + + const data = new ArrayBuffer(d.sizeOf(BoidArray)); + const fView = new Float32Array(data); + + for (let i = 0; i < amountOfBoids; ++i) { + fView[i * 8 + 0] = 1; + fView[i * 8 + 1] = 2; + fView[i * 8 + 2] = 3; + + fView[i * 8 + 4] = 4; + fView[i * 8 + 5] = 5; + fView[i * 8 + 6] = 6; + } + + root.device.queue.writeBuffer(root.unwrap(buffer), 0, data); + + root.destroy(); + }); + + await bench.run(); + + return { parameterSet: params, bench }; +} + +const benchResultsAtom = atom | null>(null); + +const runBenchmarksAtom = atom(null, async (get, set) => { + const parameterSets = get(parameterSetsAtom); + + set( + benchResultsAtom, + (async () => { + const results: BenchResults[] = []; + + // Running each benchmark in sequence + for (const params of parameterSets) { + results.push(await runBench(params)); + } + + return results; + })(), + ); +}); + +function SingleBenchResults(props: { results: BenchResults }) { + const { bench } = props.results; + const results = useMemo(() => bench.table(), [bench]); + const columns = Object.keys(results?.[0] ?? {}); + + return ( + + + + + {columns.map((columnKey) => ( + + ))} + + + + {results.map((task) => ( + + {columns.map((columnKey) => ( + + ))} + + ))} + +
{bench.name}
+ {columnKey} +
+ {task?.[columnKey]} +
+ ); +} + +function BenchmarkResults() { + const benchResults = useAtomValue(benchResultsAtom); + + return benchResults?.map((results) => ( + + )); +} + +function BenchmarkFallback() { + return ( +
+
+ +

Running...

+
+
+ ); +} + +export default function BenchmarkApp() { + const parameterSetAtoms = useAtomValue(parameterSetAtomsAtom); + const runBenchmarks = useSetAtom(runBenchmarksAtom); + const createParameterSet = useSetAtom(createParameterSetAtom); + + return ( +
+
+

Versions to compare:

+
    + {parameterSetAtoms.map((paramsAtom, index) => ( +
  • + key={`${index}`} + className="w-full" + > + +
  • + ))} +
+
+ +
+ +
+ }> + + +
+ ); +} diff --git a/apps/typegpu-docs/src/pages/benchmark/index.astro b/apps/typegpu-docs/src/pages/benchmark/index.astro new file mode 100644 index 000000000..ae2082dd7 --- /dev/null +++ b/apps/typegpu-docs/src/pages/benchmark/index.astro @@ -0,0 +1,19 @@ +--- +import { Image } from 'astro:assets'; +import PageLayout from '../../layouts/PageLayout.astro'; +import BenchmarkApp from './benchmark-app.tsx'; +import TypeGPULogoDark from '../../assets/typegpu-logo-dark.svg'; +--- + + +

+ TypeGPU Logo +

— benchmark

+

+ + +
diff --git a/apps/typegpu-docs/src/pages/benchmark/modules.ts b/apps/typegpu-docs/src/pages/benchmark/modules.ts new file mode 100644 index 000000000..32e84cccf --- /dev/null +++ b/apps/typegpu-docs/src/pages/benchmark/modules.ts @@ -0,0 +1,35 @@ +import type { PackageLocator } from './parameter-set.js'; + +export type TypeGPUModule = typeof import('typegpu'); +export type TypeGPUDataModule = typeof import('typegpu/data'); +export type TypeGPUStdModule = typeof import('typegpu/std'); + +export function importTypeGPU(locator: PackageLocator): Promise { + if (locator.type === 'local') { + return import('typegpu'); + } + + if (locator.type === 'npm') { + return import( + /* @vite-ignore */ `https://esm.sh/typegpu@${locator.version}/` + ); + } + + throw new Error('Unsupported import of `typegpu`'); +} + +export function importTypeGPUData( + locator: PackageLocator, +): Promise { + if (locator.type === 'local') { + return import('typegpu/data'); + } + + if (locator.type === 'npm') { + return import( + /* @vite-ignore */ `https://esm.sh/typegpu@${locator.version}/data` + ); + } + + throw new Error('Unsupported import of `typegpu/data`'); +} diff --git a/apps/typegpu-docs/src/pages/benchmark/parameter-set-row.tsx b/apps/typegpu-docs/src/pages/benchmark/parameter-set-row.tsx new file mode 100644 index 000000000..172d60956 --- /dev/null +++ b/apps/typegpu-docs/src/pages/benchmark/parameter-set-row.tsx @@ -0,0 +1,85 @@ +import { type PrimitiveAtom, useAtom, useSetAtom } from 'jotai'; +import { useCallback } from 'react'; +import { DeleteIcon } from '../../components/design/DeleteIcon.js'; +import { + type BenchParameterSet, + deleteParameterSetAtom, +} from './parameter-set.js'; + +function NpmParameters(props: { + parameterSetAtom: PrimitiveAtom; +}) { + const [parameterSet, setParameterSet] = useAtom(props.parameterSetAtom); + + const version = + parameterSet.typegpu.type === 'npm' ? parameterSet.typegpu.version : ''; + + const setVersion = useCallback( + (version: string) => { + setParameterSet((prev) => ({ + ...prev, + typegpu: { ...prev.typegpu, version }, + })); + }, + [setParameterSet], + ); + + return ( + <> +

typegpu@

+ setVersion(e.target.value)} + placeholder="0.0.0" + /> + + ); +} + +export function ParameterSetRow(props: { + parameterSetAtom: PrimitiveAtom; +}) { + const [parameterSet, setParameterSet] = useAtom(props.parameterSetAtom); + const deleteParameterSet = useSetAtom(deleteParameterSetAtom); + + const typeValue = parameterSet.typegpu.type; + + const setType = useCallback( + (type: 'local' | 'npm') => { + setParameterSet((prev) => ({ + ...prev, + typegpu: { type }, + })); + }, + [setParameterSet], + ); + + return ( +
+ + +
+ {typeValue === 'local' &&

typegpu

} + {typeValue === 'npm' && ( + + )} +
+
+ ); +} diff --git a/apps/typegpu-docs/src/pages/benchmark/parameter-set.ts b/apps/typegpu-docs/src/pages/benchmark/parameter-set.ts new file mode 100644 index 000000000..7d624e3c1 --- /dev/null +++ b/apps/typegpu-docs/src/pages/benchmark/parameter-set.ts @@ -0,0 +1,64 @@ +import { type Getter, atom } from 'jotai'; +import { splitAtom } from 'jotai/utils'; +import { atomWithUrl } from './atom-with-url.js'; + +export type PackageLocator = + | { + type: 'npm'; + version?: string; + } + | { + type: 'local'; + }; + +export interface BenchParameterSet { + key: number; + typegpu: PackageLocator; +} + +export function stringifyLocator( + name: string, + locator: PackageLocator, +): string { + if (locator.type === 'npm') { + return `${name}@${locator.version}`; + } + + if (locator.type === 'local') { + return `${name}:local`; + } + + return name; +} + +function getFreeKey(get: Getter): number { + return Math.max(...get(parameterSetsAtom).map((params) => params.key)) + 1; +} + +export const parameterSetsAtom = atomWithUrl('p', [ + { key: 1, typegpu: { type: 'local' } }, + { key: 2, typegpu: { type: 'npm', version: 'latest' } }, +]); + +export const parameterSetAtomsAtom = splitAtom( + parameterSetsAtom, + (params) => params.key, +); + +export const createParameterSetAtom = atom(null, (get, set) => { + const prev = get(parameterSetsAtom); + const key = getFreeKey(get); + + set(parameterSetsAtom, [ + ...prev, + { key, typegpu: { type: 'npm', version: '' } }, + ]); +}); + +export const deleteParameterSetAtom = atom(null, (get, set, key: number) => { + const prev = get(parameterSetsAtom); + set( + parameterSetsAtom, + prev.filter((params) => params.key !== key), + ); +}); diff --git a/apps/typegpu-docs/tailwind.config.mjs b/apps/typegpu-docs/tailwind.config.mjs index 03cbb7216..4d57be12d 100644 --- a/apps/typegpu-docs/tailwind.config.mjs +++ b/apps/typegpu-docs/tailwind.config.mjs @@ -1,4 +1,7 @@ import starlightPlugin from '@astrojs/starlight-tailwind'; +import tailwindcssMotion from 'tailwindcss-motion'; +// @ts-check +import plugin from 'tailwindcss/plugin'; const accent = { 200: '#c3c4f1', @@ -17,6 +20,7 @@ const gray = { 900: '#171724', }; +/** @type {import('tailwindcss').Config} */ export default { content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'], theme: { @@ -70,5 +74,11 @@ export default { lg: '1441px', }, }, - plugins: [starlightPlugin()], + plugins: [ + starlightPlugin(), + tailwindcssMotion, + plugin(({ addVariant }) => { + addVariant('starting', '@starting-style'); + }), + ], }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 327d09471..386c49476 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,12 +83,18 @@ importers: jotai-location: specifier: ^0.5.5 version: 0.5.5(jotai@2.8.4) + lucide-react: + specifier: ^0.474.0 + version: 0.474.0(react@18.3.1) lz-string: specifier: ^1.5.0 version: 1.5.0 monaco-editor: specifier: ^0.50.0 version: 0.50.0 + motion: + specifier: ^12.0.5 + version: 12.0.5(react-dom@18.3.1)(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -113,6 +119,9 @@ importers: tailwindcss: specifier: ^3.4.6 version: 3.4.6 + tinybench: + specifier: ^3.1.0 + version: 3.1.0 typed-binary: specifier: ^4.0.0 version: 4.0.0 @@ -147,6 +156,9 @@ importers: astro-vtbot: specifier: ^1.8.2 version: 1.10.7 + tailwindcss-motion: + specifier: ^1.0.1 + version: 1.0.1(tailwindcss@3.4.6) packages/rollup-plugin: dependencies: @@ -336,7 +348,6 @@ packages: /@alloc/quick-lru@5.2.0: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - dev: false /@ampproject/remapping@2.2.1: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} @@ -2856,7 +2867,6 @@ packages: /arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - dev: false /argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -3174,7 +3184,6 @@ packages: /camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - dev: false /camelcase@7.0.1: resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} @@ -3397,7 +3406,6 @@ packages: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} hasBin: true - dev: false /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -3487,7 +3495,6 @@ packages: /didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - dev: false /diff@5.2.0: resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} @@ -3512,7 +3519,6 @@ packages: /dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - dev: false /dpdm@3.14.0: resolution: {integrity: sha512-YJzsFSyEtj88q5eTELg3UWU7TVZkG1dpbF4JDQ3t1b07xuzXmdoGeSz9TKOke1mUuOpWlk4q+pBh+aHzD6GBTg==} @@ -3826,6 +3832,27 @@ packages: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} dev: false + /framer-motion@12.0.5(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-OgfUHfL+u80uCf6VzkV7j3+H2W9zPMdV+40bug4ftB0C2+0FpfNca2rbH2Fouq2R7//bjw6tJhOwXsrsM4+LKw==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + dependencies: + motion-dom: 12.0.0 + motion-utils: 12.0.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.6.3 + dev: false + /fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} dev: false @@ -3848,7 +3875,6 @@ packages: /function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - dev: false /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} @@ -3904,7 +3930,6 @@ packages: engines: {node: '>=10.13.0'} dependencies: is-glob: 4.0.3 - dev: false /glob@10.3.10: resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} @@ -3975,7 +4000,6 @@ packages: engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 - dev: false /hast-util-embedded@3.0.0: resolution: {integrity: sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==} @@ -4280,7 +4304,6 @@ packages: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} dependencies: hasown: 2.0.1 - dev: false /is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} @@ -4401,7 +4424,6 @@ packages: /jiti@1.21.6: resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} hasBin: true - dev: false /jotai-location@0.5.5(jotai@2.8.4): resolution: {integrity: sha512-6QW/7W9IJHjhbn7gRgAw4sC30k0/G6JiC4uPlKi8ZPZGYk7R7r9PyMD2eVhL4XSxxag89JxS1iSyr6BIXsB4Sw==} @@ -4493,7 +4515,6 @@ packages: /lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} - dev: false /lilconfig@3.1.0: resolution: {integrity: sha512-p3cz0JV5vw/XeouBU3Ldnp+ZkBjE+n8ydJ4mcwBrOiXXPqNlrzGBqWs9X4MWF7f+iKUBu794Y8Hh8yawiJbCjw==} @@ -4581,6 +4602,14 @@ packages: yallist: 3.1.1 dev: false + /lucide-react@0.474.0(react@18.3.1): + resolution: {integrity: sha512-CmghgHkh0OJNmxGKWc0qfPJCYHASPMVSyGY8fj3xgk4v84ItqDg64JNKFZn5hC6E0vHi6gxnbCgwhyVB09wQtA==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + dependencies: + react: 18.3.1 + dev: false + /lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} dev: false @@ -5300,6 +5329,36 @@ packages: /moo@0.5.2: resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} + /motion-dom@12.0.0: + resolution: {integrity: sha512-CvYd15OeIR6kHgMdonCc1ihsaUG4MYh/wrkz8gZ3hBX/uamyZCXN9S9qJoYF03GqfTt7thTV/dxnHYX4+55vDg==} + dependencies: + motion-utils: 12.0.0 + dev: false + + /motion-utils@12.0.0: + resolution: {integrity: sha512-MNFiBKbbqnmvOjkPyOKgHUp3Q6oiokLkI1bEwm5QA28cxMZrv0CbbBGDNmhF6DIXsi1pCQBSs0dX8xjeER1tmA==} + dev: false + + /motion@12.0.5(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-AFCMJWX7xtd2V4PEuL+FnVscw++SaMZuFXrm3kjN1sIqrNDJdJrFbZ2B+pfq6CKlcTor/vVJcmrc2pJqF3EByw==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + dependencies: + framer-motion: 12.0.5(react-dom@18.3.1)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.6.3 + dev: false + /mrmime@2.0.0: resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} engines: {node: '>=10'} @@ -5402,7 +5461,6 @@ packages: /object-hash@3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - dev: false /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -5557,7 +5615,6 @@ packages: /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - dev: false /path-scurry@1.10.1: resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} @@ -5614,7 +5671,6 @@ packages: /pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} - dev: false /pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} @@ -5642,7 +5698,6 @@ packages: postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - dev: false /postcss-js@4.0.1(postcss@8.4.39): resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} @@ -5652,7 +5707,6 @@ packages: dependencies: camelcase-css: 2.0.1 postcss: 8.4.39 - dev: false /postcss-load-config@4.0.2(postcss@8.4.39): resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} @@ -5678,7 +5732,6 @@ packages: dependencies: postcss: 8.4.39 postcss-selector-parser: 6.1.0 - dev: false /postcss-selector-parser@6.1.0: resolution: {integrity: sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==} @@ -5686,11 +5739,9 @@ packages: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 - dev: false /postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - dev: false /postcss@8.4.39: resolution: {integrity: sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==} @@ -5869,7 +5920,6 @@ packages: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} dependencies: pify: 2.3.0 - dev: false /readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} @@ -6054,7 +6104,6 @@ packages: is-core-module: 2.13.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - dev: false /restore-cursor@3.1.0: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} @@ -6522,7 +6571,14 @@ packages: /supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - dev: false + + /tailwindcss-motion@1.0.1(tailwindcss@3.4.6): + resolution: {integrity: sha512-ZqarvI3XSclT46Dq1wOo9qG9Yx0TIyEtkMfTo8l0JuVKvO1WiSbvAECuTqdYHpC9Ml7abuJIk7u907SBHYprwQ==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + dependencies: + tailwindcss: 3.4.6 + dev: true /tailwindcss@3.4.6: resolution: {integrity: sha512-1uRHzPB+Vzu57ocybfZ4jh5Q3SdlH7XW23J5sQoM9LhE9eIOlzxer/3XPSsycvih3rboRsvt0QCmzSrqyOYUIA==} @@ -6553,7 +6609,6 @@ packages: sucrase: 3.35.0 transitivePeerDependencies: - ts-node - dev: false /tar-fs@2.1.1: resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} @@ -6614,6 +6669,11 @@ packages: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} dev: true + /tinybench@3.1.0: + resolution: {integrity: sha512-Km+oMh2xqNCxuyoUsqbRmHgFSd8sATh7v7xreP+kHN6x67w28Pawr83WmBxcaORvxkc0Ex6zgqK951yBnTFaaQ==} + engines: {node: '>=18.0.0'} + dev: false + /tinyexec@0.3.1: resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} dev: true