From f2e9f37471c6b651f74c096c9657e6006b42b2df Mon Sep 17 00:00:00 2001 From: Paulo Ferreira Jorge Date: Thu, 28 May 2026 17:21:41 +0100 Subject: [PATCH 01/20] Setup --- dataweaver/.env.example | 1 + dataweaver/.gitignore | 11 + dataweaver/.husky/pre-push | 1 + dataweaver/.nvmrc | 1 + dataweaver/README.md | 66 +- dataweaver/apps/web/.gitignore | 6 + dataweaver/apps/web/next.config.ts | 38 + dataweaver/apps/web/package.json | 27 + dataweaver/apps/web/src/app/layout.tsx | 22 + dataweaver/apps/web/src/app/not-found.tsx | 3 + dataweaver/apps/web/src/app/page.tsx | 3 + .../web/src/components/primitives/link.tsx | 52 + .../web/src/components/scopes/page-404.tsx | 7 + .../web/src/components/scopes/page-home.tsx | 5 + .../src/components/scopes/tldraw.module.scss | 15 + .../apps/web/src/components/scopes/tldraw.tsx | 8 + .../apps/web/src/functions/match-media.ts | 70 + .../web/src/functions/merge-class-names.ts | 4 + .../apps/web/src/hooks/use-match-media.ts | 173 + dataweaver/apps/web/src/styles/breakpoints.ts | 22 + dataweaver/apps/web/src/styles/core.scss | 3 + .../apps/web/src/styles/core/_base.scss | 12 + .../apps/web/src/styles/core/_reset.scss | 122 + .../apps/web/src/styles/core/_root.scss | 9 + dataweaver/apps/web/src/styles/includes.scss | 4 + .../styles/includes/_breakpoints.module.scss | 24 + .../src/styles/includes/_helpers.module.scss | 61 + .../styles/includes/_z-indices.module.scss | 11 + dataweaver/apps/web/src/styles/layers.css | 1 + .../src/styles/tokens/_generated.module.scss | 21 + .../apps/web/src/styles/tokens/generated.ts | 20 + dataweaver/apps/web/tsconfig.json | 50 + dataweaver/biome.json | 91 + dataweaver/package.json | 40 + dataweaver/packages/tokens/package.json | 12 + dataweaver/packages/tokens/src/colors.json | 9 + dataweaver/packages/tokens/src/eases.json | 6 + dataweaver/packages/tokens/src/generate.ts | 104 + dataweaver/packages/tokens/src/variables.json | 5 + dataweaver/packages/tokens/tsconfig.json | 5 + dataweaver/packages/tsconfig.base.json | 15 + dataweaver/pnpm-lock.yaml | 4705 +++++++++++++++++ dataweaver/pnpm-workspace.yaml | 3 + dataweaver/stylelint.config.mjs | 50 + 44 files changed, 5917 insertions(+), 1 deletion(-) create mode 100644 dataweaver/.env.example create mode 100644 dataweaver/.gitignore create mode 100755 dataweaver/.husky/pre-push create mode 100644 dataweaver/.nvmrc create mode 100644 dataweaver/apps/web/.gitignore create mode 100644 dataweaver/apps/web/next.config.ts create mode 100644 dataweaver/apps/web/package.json create mode 100644 dataweaver/apps/web/src/app/layout.tsx create mode 100644 dataweaver/apps/web/src/app/not-found.tsx create mode 100644 dataweaver/apps/web/src/app/page.tsx create mode 100755 dataweaver/apps/web/src/components/primitives/link.tsx create mode 100644 dataweaver/apps/web/src/components/scopes/page-404.tsx create mode 100644 dataweaver/apps/web/src/components/scopes/page-home.tsx create mode 100644 dataweaver/apps/web/src/components/scopes/tldraw.module.scss create mode 100644 dataweaver/apps/web/src/components/scopes/tldraw.tsx create mode 100644 dataweaver/apps/web/src/functions/match-media.ts create mode 100755 dataweaver/apps/web/src/functions/merge-class-names.ts create mode 100644 dataweaver/apps/web/src/hooks/use-match-media.ts create mode 100644 dataweaver/apps/web/src/styles/breakpoints.ts create mode 100644 dataweaver/apps/web/src/styles/core.scss create mode 100644 dataweaver/apps/web/src/styles/core/_base.scss create mode 100644 dataweaver/apps/web/src/styles/core/_reset.scss create mode 100644 dataweaver/apps/web/src/styles/core/_root.scss create mode 100644 dataweaver/apps/web/src/styles/includes.scss create mode 100644 dataweaver/apps/web/src/styles/includes/_breakpoints.module.scss create mode 100755 dataweaver/apps/web/src/styles/includes/_helpers.module.scss create mode 100755 dataweaver/apps/web/src/styles/includes/_z-indices.module.scss create mode 100644 dataweaver/apps/web/src/styles/layers.css create mode 100644 dataweaver/apps/web/src/styles/tokens/_generated.module.scss create mode 100644 dataweaver/apps/web/src/styles/tokens/generated.ts create mode 100644 dataweaver/apps/web/tsconfig.json create mode 100644 dataweaver/biome.json create mode 100644 dataweaver/package.json create mode 100644 dataweaver/packages/tokens/package.json create mode 100644 dataweaver/packages/tokens/src/colors.json create mode 100644 dataweaver/packages/tokens/src/eases.json create mode 100644 dataweaver/packages/tokens/src/generate.ts create mode 100644 dataweaver/packages/tokens/src/variables.json create mode 100644 dataweaver/packages/tokens/tsconfig.json create mode 100644 dataweaver/packages/tsconfig.base.json create mode 100644 dataweaver/pnpm-lock.yaml create mode 100644 dataweaver/pnpm-workspace.yaml create mode 100644 dataweaver/stylelint.config.mjs diff --git a/dataweaver/.env.example b/dataweaver/.env.example new file mode 100644 index 00000000..ec7a8791 --- /dev/null +++ b/dataweaver/.env.example @@ -0,0 +1 @@ +NEXT_PUBLIC_SITE_TITLE="Dataweaver" diff --git a/dataweaver/.gitignore b/dataweaver/.gitignore new file mode 100644 index 00000000..81d1f062 --- /dev/null +++ b/dataweaver/.gitignore @@ -0,0 +1,11 @@ +# Dependencies +node_modules/ + +# Environment variables +.env.local + +# Misc +.DS_Store + +# Typescript +*.tsbuildinfo diff --git a/dataweaver/.husky/pre-push b/dataweaver/.husky/pre-push new file mode 100755 index 00000000..6ca46a75 --- /dev/null +++ b/dataweaver/.husky/pre-push @@ -0,0 +1 @@ +pnpm run lint diff --git a/dataweaver/.nvmrc b/dataweaver/.nvmrc new file mode 100644 index 00000000..c6a66a6e --- /dev/null +++ b/dataweaver/.nvmrc @@ -0,0 +1 @@ +v22.21.1 diff --git a/dataweaver/README.md b/dataweaver/README.md index 9d345a06..347a02f2 100644 --- a/dataweaver/README.md +++ b/dataweaver/README.md @@ -1 +1,65 @@ -# Data Weaver Experiment +# dataweaver + +Monorepo for Dataweaver. Managed with [pnpm workspaces](https://pnpm.io/workspaces). + +## Workspace layout + +``` +apps/ + web/ Next.js 15 app (App Router) +packages/ + tokens/ Design tokens (JSON → generated .scss / .ts) +``` + +## Installation + +All commands run from the root of the monorepo. + +### Node.js version + +Install [nvm](https://github.com/nvm-sh/nvm) and run: + +```bash +nvm use +``` + +### Dependencies + +```bash +pnpm i +``` + +## Scripts + +| Command | What it does | +| --- | --- | +| `pnpm dev` | Run all apps in dev mode | +| `pnpm build` | Build all apps | +| `pnpm preview` | Serve the built apps | +| `pnpm generate:tokens` | Regenerate `_generated.module.scss` + `generated.ts` from `packages/tokens/src/*.json` | + +## Linting + +Linting by Biome, TypeScript and Stylelint. To check, run: + +```bash +pnpm run lint +``` + +To automatically fix _most_ linting errors, run: + +```bash +pnpm run fix +``` + +## Dependency Check + +To run a dependency check and update interactively, run the following command from the root of the monorepo: + +```bash +pnpm up --latest --recursive --interactive +``` + +This recursively checks all packages in the repo for outdated dependencies and lets you select which ones to update. + +Select the packages you want to update (using the `space` key), then press `enter` to update the selected ones. diff --git a/dataweaver/apps/web/.gitignore b/dataweaver/apps/web/.gitignore new file mode 100644 index 00000000..3465f99e --- /dev/null +++ b/dataweaver/apps/web/.gitignore @@ -0,0 +1,6 @@ +# Next.js +.next/ +next-env.d.ts + +# Generated +src/server/storyblok/generated/globals.json \ No newline at end of file diff --git a/dataweaver/apps/web/next.config.ts b/dataweaver/apps/web/next.config.ts new file mode 100644 index 00000000..1832de41 --- /dev/null +++ b/dataweaver/apps/web/next.config.ts @@ -0,0 +1,38 @@ +import type { NextConfig } from 'next'; + +const NEXT_CONFIG: NextConfig = { + reactStrictMode: true, + reactCompiler: true, + devIndicators: false, + + // Auto-inject the shared Sass includes (breakpoints, helpers, keyframes, + // typography, z-indices, tokens) into every Sass entry module. Includes + // only '@forwards' definitions, so this emits no CSS; it removes the need to + // repeat `@use "~/styles/includes" as *;` at the top of each component file + sassOptions: { + additionalData: '@use "~/styles/includes" as *;\n', + }, + + compiler: { + removeConsole: + // Only allow the following console calls in production + process.env.NODE_ENV === 'production' + ? { exclude: ['warn', 'error', 'info'] } + : false, + }, + + images: { + imageSizes: [384], + deviceSizes: [768, 1280, 1600], + qualities: [90], + remotePatterns: [ + { + protocol: 'https', + hostname: 'fonts.googleapis.com', + pathname: '/**', + }, + ], + }, +}; + +export default NEXT_CONFIG; diff --git a/dataweaver/apps/web/package.json b/dataweaver/apps/web/package.json new file mode 100644 index 00000000..ecb46945 --- /dev/null +++ b/dataweaver/apps/web/package.json @@ -0,0 +1,27 @@ +{ + "name": "@app/web", + "type": "module", + "scripts": { + "dev": "next dev", + "build": "next build", + "preview": "next start", + "lint:type-check": "tsc --noEmit", + "generate:tokens": "pnpm --filter @package/tokens generate -- \"$PWD/src/styles/tokens\"" + }, + "dependencies": { + "clsx": "^2.1.1", + "motion": "^12.38.0", + "next": "^16.2.6", + "react-dom": "^19.2.6", + "react": "^19.2.6", + "tldraw": "^5.0.1" + }, + "devDependencies": { + "@package/tokens": "workspace:tokens", + "@types/node": "^25.9.1", + "@types/react-dom": "^19.2.3", + "@types/react": "^19.2.15", + "babel-plugin-react-compiler": "^1.0.0", + "sass": "^1.97.3" + } +} diff --git a/dataweaver/apps/web/src/app/layout.tsx b/dataweaver/apps/web/src/app/layout.tsx new file mode 100644 index 00000000..b028d782 --- /dev/null +++ b/dataweaver/apps/web/src/app/layout.tsx @@ -0,0 +1,22 @@ +import '~/styles/layers.css'; +import '~/styles/core.scss'; +import { domMax, LazyMotion } from 'motion/react'; +import type { ReactNode } from 'react'; + +type Props = { + children: ReactNode; +}; + +const RootLayout = ({ children }: Props) => { + return ( + + + +
{children}
+
+ + + ); +}; + +export default RootLayout; diff --git a/dataweaver/apps/web/src/app/not-found.tsx b/dataweaver/apps/web/src/app/not-found.tsx new file mode 100644 index 00000000..82f661ff --- /dev/null +++ b/dataweaver/apps/web/src/app/not-found.tsx @@ -0,0 +1,3 @@ +import { Page404 } from '~/components/scopes/page-404'; + +export default Page404; diff --git a/dataweaver/apps/web/src/app/page.tsx b/dataweaver/apps/web/src/app/page.tsx new file mode 100644 index 00000000..cedf9acc --- /dev/null +++ b/dataweaver/apps/web/src/app/page.tsx @@ -0,0 +1,3 @@ +import { PageHome } from '~/components/scopes/page-home'; + +export default PageHome; diff --git a/dataweaver/apps/web/src/components/primitives/link.tsx b/dataweaver/apps/web/src/components/primitives/link.tsx new file mode 100755 index 00000000..fd96355c --- /dev/null +++ b/dataweaver/apps/web/src/components/primitives/link.tsx @@ -0,0 +1,52 @@ +import type { LinkProps } from 'next/link'; +import NextLink from 'next/link'; +import type { ComponentPropsWithRef, Ref } from 'react'; + +type Props = { + ref?: Ref; + href: string | undefined; + + /** @default false */ + isExternal?: boolean; +} & ComponentPropsWithRef<'a'> & + Pick; + +export const Link = ({ + ref, + href, + isExternal = false, + replace, + children, + ...rest +}: Props) => { + // If theres no 'href' render this without it (this is equivalent to a span) + if (!href) { + return ( + + {children} + + ); + } + + // If external link, render standard anchor (open in new tab) + if (isExternal) { + return ( + + {children} + + ); + } + + // If internal link, render Next/Link + return ( + + {children} + + ); +}; diff --git a/dataweaver/apps/web/src/components/scopes/page-404.tsx b/dataweaver/apps/web/src/components/scopes/page-404.tsx new file mode 100644 index 00000000..792fef58 --- /dev/null +++ b/dataweaver/apps/web/src/components/scopes/page-404.tsx @@ -0,0 +1,7 @@ +export const Page404 = () => { + return ( +
+

404!

+
+ ); +}; diff --git a/dataweaver/apps/web/src/components/scopes/page-home.tsx b/dataweaver/apps/web/src/components/scopes/page-home.tsx new file mode 100644 index 00000000..ddec8c68 --- /dev/null +++ b/dataweaver/apps/web/src/components/scopes/page-home.tsx @@ -0,0 +1,5 @@ +import { Tldraw } from './tldraw'; + +export const PageHome = () => { + return ; +}; diff --git a/dataweaver/apps/web/src/components/scopes/tldraw.module.scss b/dataweaver/apps/web/src/components/scopes/tldraw.module.scss new file mode 100644 index 00000000..e7e16f55 --- /dev/null +++ b/dataweaver/apps/web/src/components/scopes/tldraw.module.scss @@ -0,0 +1,15 @@ +@import "tldraw/tldraw.css" layer(primitive); + +.canvas { + --tl-color-background: transparent; + + position: fixed; + inset: 0; + + // Ensure all nested layers appear from a base of 0 and not above app content + z-index: $z-index-content; + background-color: $color-blue-light; + background-image: radial-gradient($color-grey-light 1px, transparent 1px); + background-position: center center; + background-size: 20px 20px; +} diff --git a/dataweaver/apps/web/src/components/scopes/tldraw.tsx b/dataweaver/apps/web/src/components/scopes/tldraw.tsx new file mode 100644 index 00000000..bcb7d18b --- /dev/null +++ b/dataweaver/apps/web/src/components/scopes/tldraw.tsx @@ -0,0 +1,8 @@ +'use client'; + +import { Tldraw as PrimitiveTldraw } from 'tldraw'; +import s from './tldraw.module.scss'; + +export const Tldraw = () => { + return ; +}; diff --git a/dataweaver/apps/web/src/functions/match-media.ts b/dataweaver/apps/web/src/functions/match-media.ts new file mode 100644 index 00000000..e945a133 --- /dev/null +++ b/dataweaver/apps/web/src/functions/match-media.ts @@ -0,0 +1,70 @@ +import { BREAKPOINTS, type Target } from '~/styles/breakpoints'; + +/** List of media query keys with their custom query. */ +const MISC_QUERIES = { + coarse: '(hover: none) and (pointer: coarse)', + fine: '(hover: hover) and (pointer: fine)', + 'prefers-motion': '(prefers-reduced-motion: no-preference)', + 'prefers-reduced-motion': '(prefers-reduced-motion: reduce)', +}; + +export type MatchMediaKey = Target | keyof typeof MISC_QUERIES | (string & {}); + +/** + * Helper function to get CSS media query for given shorthand `key`. + * + * @param key - The shorthand key to match for. + * + * @example + * getMatchMediaQuery('coarse') -> Checks if the browser matches coarse; device + * of limited accuracy (touch). + * + * @example + * getMatchMediaQuery('fine') -> Checks if the browser matches fine; accurate + * pointing device (mouse). + * + * @example + * getMatchMediaQuery('prefers-motion') -> Checks if the browser has + * `no-preference` for 'prefers-reduced-motion'. + * + * @example + * getMatchMediaQuery('tablet') -> Checks if the browser is at or above our + * tablet `min-width` breakpoint. + * + * @example + * getMatchMediaQuery('laptop') -> Checks if the browser is at or above our + * laptop `min-width` breakpoint. + */ +export const getMatchMediaQuery = (key: MatchMediaKey) => { + // If the key is mobile, return 'max-width' query instead. Note: We use the + // tablet breakpoint as the 'max-width' for mobile, as we don't have a mobile + // breakpoint + the negative 1 ensures 'max-width' fires at same point as + // 'min-width' breakpoint + if (key === 'mobile') { + const tabletMinWidth = BREAKPOINTS.tablet.minWidth; + return `(max-width: ${tabletMinWidth - 1}px)`; + } + + // If the key is a valid breakpoint, return the min-width query + if (key in BREAKPOINTS) { + const minWidth = BREAKPOINTS[key as Target]?.minWidth; + if (minWidth) return `(min-width: ${minWidth}px)`; + } + + // If key in misc queries - return the corresponding query + if (key in MISC_QUERIES) { + return MISC_QUERIES[key as keyof typeof MISC_QUERIES]; + } + + return key; +}; + +/** + * Helper function to get CSS media query result for given shorthand `key`. + * + * @param key - The shorthand key to match for (see `getMatchMediaQuery`). + */ +export const getMatchMediaMatch = (key: MatchMediaKey) => { + const query = getMatchMediaQuery(key); + return window.matchMedia(query).matches; +}; diff --git a/dataweaver/apps/web/src/functions/merge-class-names.ts b/dataweaver/apps/web/src/functions/merge-class-names.ts new file mode 100755 index 00000000..6159b2aa --- /dev/null +++ b/dataweaver/apps/web/src/functions/merge-class-names.ts @@ -0,0 +1,4 @@ +import mergeClassNames from 'clsx'; + +// Name isn't very memorable, so we export it in this file to alias it +export { mergeClassNames }; diff --git a/dataweaver/apps/web/src/hooks/use-match-media.ts b/dataweaver/apps/web/src/hooks/use-match-media.ts new file mode 100644 index 00000000..0db3ddd3 --- /dev/null +++ b/dataweaver/apps/web/src/hooks/use-match-media.ts @@ -0,0 +1,173 @@ +import { useIsomorphicLayoutEffect } from 'motion/react'; +import { useCallback, useState } from 'react'; +import { + getMatchMediaQuery, + type MatchMediaKey, +} from '~/functions/match-media'; + +type Config = { + /** + * Fallback value that is returned in SSR and first match of any unique query. + * + * @default false + */ + defaultValue?: boolean | null; + + /** + * Whether or not to listen to events. + * + * @default true + */ + isEnabled?: boolean; +}; + +type EventHandler = (event: MediaQueryListEvent) => void; + +type Query = { + matchMedia: MediaQueryList; + existingListeners: EventHandler[]; + eventHandler: EventHandler; +}; + +const QUERIES = new Map(); + +/** + * Stateful hook that uses the matchMedia API. + * + * @param query - The query to match for. + * @param config - _Optional_ configuration object. + * @param config.defaultValue - _Optional_ fallback value that is returned in + * SSR and first match of any unique query. Defaults to `false`. + * @param config.isEnabled - _Optional_ whether or not to listen to events. + * Defaults to `true`. + * @param config.layoutEffect - _Optional_ whether or not to use + * `useLayoutEffect` instead of `useEffect` on client. Defaults to `false`. + * + * @example + * useMatchMedia("(pointer: coarse)") -> Checks if the browser matches coarse; + * device of limited accuracy (touch). + * + * @example useMatchMedia("(min-width: 600px)") -> Checks if the browser + * matches a minimum width of 600px. + * + * @returns matches - `true` / `false` if the device matches the query, or + * `defaultValue` as fallback. + */ +export const useMatchMedia = < + TConfig extends Config = Config, + TDefaultValue extends + | TConfig['defaultValue'] + | never = TConfig['defaultValue'] extends undefined + ? undefined + : TConfig['defaultValue'] extends null + ? null + : never, +>( + query: MatchMediaKey, + config: Partial = {}, +) => { + const getExistingMatch = useCallback(() => { + const matchedQuery = QUERIES.get(query); + + // If query already exists, return its matched value + if (matchedQuery) return matchedQuery.matchMedia.matches; + + // Else, return config default value if it exists or fallback (false) + return config.defaultValue === undefined ? false : config.defaultValue; + }, [config.defaultValue, query]); + + const createEventHandler = useCallback( + (query: string) => (event: MediaQueryListEvent) => { + const matchedQuery = QUERIES.get(query); + + if (matchedQuery) { + matchedQuery.existingListeners.forEach((listener) => { + listener(event); + }); + } + }, + [], + ); + + const addListener = useCallback( + (query: string, listener: EventHandler) => { + const matchedQuery = QUERIES.get(query); + + // If query already exists, add this new listener to existing array + if (matchedQuery) { + matchedQuery.existingListeners.push(listener); + return matchedQuery.matchMedia.matches; + } + + // Else, first query, so create it... + const newQuery = { + matchMedia: window.matchMedia(query), + existingListeners: [listener], + eventHandler: createEventHandler(query), + }; + QUERIES.set(query, newQuery); + + // Listen to changes with fallback + const currentMatchMedia = newQuery.matchMedia; + if (!currentMatchMedia.addEventListener) { + currentMatchMedia.addListener(newQuery.eventHandler); + } else { + currentMatchMedia.addEventListener('change', newQuery.eventHandler); + } + + return newQuery.matchMedia.matches; + }, + [createEventHandler], + ); + + const removeListener = useCallback( + (query: string, listener: EventHandler) => { + const matchedQuery = QUERIES.get(query); + + // If this matches, filter out this listener from existing array + if (matchedQuery) { + matchedQuery.existingListeners = matchedQuery.existingListeners.filter( + (l) => l !== listener, + ); + } + + // Ignore unsubscribe below if theres any more existing listeners + if (!matchedQuery || matchedQuery.existingListeners.length > 0) return; + + // Unsubscribe from changes with fallback + const currentMatchMedia = matchedQuery.matchMedia; + if (!currentMatchMedia.removeEventListener) { + currentMatchMedia.removeListener(matchedQuery.eventHandler); + } else { + currentMatchMedia.removeEventListener( + 'change', + matchedQuery.eventHandler, + ); + } + + QUERIES.delete(query); + }, + [], + ); + + const [matches, setMatches] = useState( + getExistingMatch, + ); + + useIsomorphicLayoutEffect(() => { + if (config.isEnabled ?? true) { + const listener = (event: MediaQueryListEvent) => { + setMatches(event.matches); + }; + + // On mount we only checked if there was an existing match. So here we + // set matches again in case this is the first of this query + const matches = addListener(getMatchMediaQuery(query), listener); + setMatches(matches); + + return () => removeListener(query, listener); + } + }, [config.isEnabled, query, addListener, removeListener]); + + return matches; +}; diff --git a/dataweaver/apps/web/src/styles/breakpoints.ts b/dataweaver/apps/web/src/styles/breakpoints.ts new file mode 100644 index 00000000..db55a55f --- /dev/null +++ b/dataweaver/apps/web/src/styles/breakpoints.ts @@ -0,0 +1,22 @@ +import { + BREAKPOINT_DESKTOP, + BREAKPOINT_LAPTOP, + BREAKPOINT_TABLET, +} from './tokens/generated'; + +export type Target = 'mobile' | 'tablet' | 'laptop' | 'desktop'; + +type Breakpoint = { + minWidth: number; +}; + +type Breakpoints = { + [key in Target]: key extends 'mobile' ? undefined : Breakpoint; +}; + +export const BREAKPOINTS: Breakpoints = { + mobile: undefined, + tablet: { minWidth: BREAKPOINT_TABLET }, + laptop: { minWidth: BREAKPOINT_LAPTOP }, + desktop: { minWidth: BREAKPOINT_DESKTOP }, +} as const; diff --git a/dataweaver/apps/web/src/styles/core.scss b/dataweaver/apps/web/src/styles/core.scss new file mode 100644 index 00000000..b49db7c0 --- /dev/null +++ b/dataweaver/apps/web/src/styles/core.scss @@ -0,0 +1,3 @@ +@use "./core/reset"; +@use "./core/root"; +@use "./core/base"; diff --git a/dataweaver/apps/web/src/styles/core/_base.scss b/dataweaver/apps/web/src/styles/core/_base.scss new file mode 100644 index 00000000..da6539ee --- /dev/null +++ b/dataweaver/apps/web/src/styles/core/_base.scss @@ -0,0 +1,12 @@ +@use "~/styles/includes"; + +@layer base { + *:focus { + outline: none; + } + + *:focus-visible:focus { + outline: 1px dashed currentcolor; + outline-offset: var(--outline-offset); + } +} diff --git a/dataweaver/apps/web/src/styles/core/_reset.scss b/dataweaver/apps/web/src/styles/core/_reset.scss new file mode 100644 index 00000000..3599eb36 --- /dev/null +++ b/dataweaver/apps/web/src/styles/core/_reset.scss @@ -0,0 +1,122 @@ +@layer reset { + html { + box-sizing: border-box; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-synthesis: none; + } + + body { + margin: unset; + } + + *, + *::before, + *::after { + box-sizing: inherit; + min-width: 0; + min-height: 0; + } + + h1, + h2, + h3, + h4, + h5, + h6, + p { + margin: unset; + font: inherit; + font-weight: inherit; + line-height: inherit; + letter-spacing: inherit; + } + + button { + padding: unset; + font: inherit; + font-weight: inherit; + line-height: inherit; + color: inherit; + text-align: unset; + text-transform: inherit; + letter-spacing: inherit; + cursor: pointer; + background: unset; + border: unset; + } + + a { + color: inherit; + text-decoration: unset; + + &[href] { + cursor: pointer; + } + } + + ul, + ol { + padding: unset; + margin: unset; + list-style: none; + } + + figure { + margin: unset; + } + + svg { + // Avoid cropping issues caused by sub-pixel rendering + overflow: unset; + } + + dialog { + inset: 0; + width: unset; + max-width: unset; + height: unset; + max-height: unset; + padding: unset; + margin: unset; + background: unset; + border: unset; + + &::backdrop { + display: none; + } + } + + table { + border-spacing: unset; + border-collapse: collapse; + } + + th, + td { + padding: unset; + } + + fieldset { + padding: unset; + margin: unset; + border: unset; + } + + dl, + dd { + margin: unset; + } + + input { + padding: unset; + color: inherit; + outline: unset; + background: unset; + border: unset; + } + + iframe { + border: unset; + } +} diff --git a/dataweaver/apps/web/src/styles/core/_root.scss b/dataweaver/apps/web/src/styles/core/_root.scss new file mode 100644 index 00000000..e655ab0f --- /dev/null +++ b/dataweaver/apps/web/src/styles/core/_root.scss @@ -0,0 +1,9 @@ +@use "~/styles/includes"; + +@layer root { + :root { + --outline-offset-default: 2px; + --outline-offset-inset: -3px; + --outline-offset: var(--outline-offset-default); + } +} diff --git a/dataweaver/apps/web/src/styles/includes.scss b/dataweaver/apps/web/src/styles/includes.scss new file mode 100644 index 00000000..49311761 --- /dev/null +++ b/dataweaver/apps/web/src/styles/includes.scss @@ -0,0 +1,4 @@ +@forward "./includes/breakpoints.module"; +@forward "./includes/helpers.module"; +@forward "./includes/z-indices.module"; +@forward "./tokens/generated.module"; diff --git a/dataweaver/apps/web/src/styles/includes/_breakpoints.module.scss b/dataweaver/apps/web/src/styles/includes/_breakpoints.module.scss new file mode 100644 index 00000000..239298d0 --- /dev/null +++ b/dataweaver/apps/web/src/styles/includes/_breakpoints.module.scss @@ -0,0 +1,24 @@ +@use "sass:map"; +@use "../tokens/generated.module" as tokens; + +$-breakpoints: ( + "tablet": tokens.$breakpoint-tablet, + "laptop": tokens.$breakpoint-laptop, + "desktop": tokens.$breakpoint-desktop, +); + +@mixin breakpoint($breakpoint) { + @if map.has-key($-breakpoints, $breakpoint) { + @media only screen and (min-width: #{map.get($-breakpoints, $breakpoint)}px) { + @content; + } + } +} + +@mixin breakpoint-below($breakpoint) { + @if map.has-key($-breakpoints, $breakpoint) { + @media only screen and (max-width: #{map.get($-breakpoints, $breakpoint) - 1}px) { + @content; + } + } +} diff --git a/dataweaver/apps/web/src/styles/includes/_helpers.module.scss b/dataweaver/apps/web/src/styles/includes/_helpers.module.scss new file mode 100755 index 00000000..219cf027 --- /dev/null +++ b/dataweaver/apps/web/src/styles/includes/_helpers.module.scss @@ -0,0 +1,61 @@ +@use "sass:math"; +@use "sass:meta"; +@use "sass:list"; +@use "sass:string"; + +/// Mixin to apply styles only when the pointer is coarse (e.g. touch) +@mixin is-pointer-coarse() { + @media (hover: none) and (pointer: coarse) { + @content; + } +} + +/// Mixin to apply styles only when the pointer is fine (e.g. mouse) +@mixin is-pointer-fine() { + @media (hover: hover) and (pointer: fine) { + @content; + } +} + +/// Mixin to apply styles only when the user hasn't requested a reduced motion +/// experience. This can be used to apply animations or transitions only when +/// the user prefers motion +@mixin prefers-motion() { + @media (prefers-reduced-motion: no-preference) { + @content; + } +} + +/// Mixin to apply styles only when the user has requested a reduced motion +/// experience. This can be used to apply animations or transitions only when +/// the user prefers reduced motion +@mixin prefers-reduced-motion() { + @media (prefers-reduced-motion: reduce) { + @content; + } +} + +/// Mixin to apply hover styles only when the device supports hover and +/// the primary input mechanism is a fine pointer (e.g. mouse). This helps +/// to avoid hover styles being applied on touch devices +@mixin hover() { + @include is-pointer-fine { + &:hover { + @content; + } + } +} + +/// Mixin to make element visually hidden but still accessible to screen readers +@mixin screen-reader-only() { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + overflow-wrap: normal; + white-space: nowrap; + border: 0; + clip-path: inset(50%); +} diff --git a/dataweaver/apps/web/src/styles/includes/_z-indices.module.scss b/dataweaver/apps/web/src/styles/includes/_z-indices.module.scss new file mode 100755 index 00000000..d5907aef --- /dev/null +++ b/dataweaver/apps/web/src/styles/includes/_z-indices.module.scss @@ -0,0 +1,11 @@ +$-next-z-index: 0; + +@function -get-next-z-index() { + $-next-z-index: $-next-z-index + 1 !global; + + @return $-next-z-index; +} + +$z-index-behind-content: -1; +$z-index-content: 0; +$z-index-above-content: -get-next-z-index(); diff --git a/dataweaver/apps/web/src/styles/layers.css b/dataweaver/apps/web/src/styles/layers.css new file mode 100644 index 00000000..f5075878 --- /dev/null +++ b/dataweaver/apps/web/src/styles/layers.css @@ -0,0 +1 @@ +@layer reset, root, base, primitive; diff --git a/dataweaver/apps/web/src/styles/tokens/_generated.module.scss b/dataweaver/apps/web/src/styles/tokens/_generated.module.scss new file mode 100644 index 00000000..e5e32781 --- /dev/null +++ b/dataweaver/apps/web/src/styles/tokens/_generated.module.scss @@ -0,0 +1,21 @@ +// ⚠️ AUTO-GENERATED — Do not edit. Modify the JSON tokens in 'packages/tokens/src/' and run 'pnpm generate:tokens' + +// Variables +$breakpoint-tablet: 768; +$breakpoint-laptop: 1280; +$breakpoint-desktop: 1600; + +// Colors +$color-black: rgb(0 0 0); +$color-grey-almost-black: rgb(32 33 36); +$color-grey-muted: rgb(68 71 70); +$color-grey-light: rgb(227 231 237); +$color-white: rgb(255 255 255); +$color-blue: rgb(11 87 208); +$color-blue-light: rgb(247 252 255); + +// Eases +$ease-linear: cubic-bezier(0, 0, 1, 1); +$ease-out: cubic-bezier(0.26, 1, 0.48, 1); +$ease-in: cubic-bezier(0.52, 0, 0.74, 0); +$ease-in-out: cubic-bezier(0.76, 0, 0.24, 1); diff --git a/dataweaver/apps/web/src/styles/tokens/generated.ts b/dataweaver/apps/web/src/styles/tokens/generated.ts new file mode 100644 index 00000000..b873c3e4 --- /dev/null +++ b/dataweaver/apps/web/src/styles/tokens/generated.ts @@ -0,0 +1,20 @@ +// ⚠️ AUTO-GENERATED — Do not edit. Modify the JSON tokens in 'packages/tokens/src/' and run 'pnpm generate:tokens' + +export const BREAKPOINT_TABLET = 768; +export const BREAKPOINT_LAPTOP = 1280; +export const BREAKPOINT_DESKTOP = 1600; + +export const COLORS = { + black: "0, 0, 0", + "grey-almost-black": "32, 33, 36", + "grey-muted": "68, 71, 70", + "grey-light": "227, 231, 237", + white: "255, 255, 255", + blue: "11, 87, 208", + "blue-light": "247, 252, 255", +} as const; + +export const EASE_LINEAR = [0, 0, 1, 1]; +export const EASE_OUT = [0.26, 1, 0.48, 1]; +export const EASE_IN = [0.52, 0, 0.74, 0]; +export const EASE_IN_OUT = [0.76, 0, 0.24, 1]; diff --git a/dataweaver/apps/web/tsconfig.json b/dataweaver/apps/web/tsconfig.json new file mode 100644 index 00000000..ec658c11 --- /dev/null +++ b/dataweaver/apps/web/tsconfig.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + // Options largely based on - https://www.totaltypescript.com/tsconfig-cheat-sheet + "compilerOptions": { + // Next.js plugin for TypeScript + "plugins": [ + { + "name": "next" + } + ], + // Base Options + "allowJs": true, + "esModuleInterop": true, + "isolatedModules": true, + "moduleDetection": "force", + "resolveJsonModule": true, + "skipLibCheck": true, + "target": "es2022", + "verbatimModuleSyntax": true, + "jsx": "react-jsx", + "incremental": false, + // Strictness + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "strict": true, + // TS -> JS Compilation + "module": "preserve", + "moduleResolution": "bundler", + "noEmit": true, + // For the DOM + "lib": ["es2022", "dom", "dom.iterable"], + // Aliases + "paths": { + "~/public/*": ["./public/*"], + "~/*": ["./src/*"] + } + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "next-env.d.ts", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": ["node_modules", ".next"] +} diff --git a/dataweaver/biome.json b/dataweaver/biome.json new file mode 100644 index 00000000..c909fa49 --- /dev/null +++ b/dataweaver/biome.json @@ -0,0 +1,91 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.7/schema.json", + + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + + "files": { + "ignoreUnknown": true + }, + + "formatter": { + "enabled": true, + "indentStyle": "tab" + }, + + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noConfusingVoidType": "off", + "noArrayIndexKey": "off", + "noConsole": { + "level": "error", + "options": { "allow": ["warn", "error", "info"] } + } + }, + "correctness": { + "noUnusedImports": "error", + "noUnusedFunctionParameters": "error", + "useExhaustiveDependencies": "error" + }, + "complexity": { + "useOptionalChain": "off" + }, + "style": { + "noDefaultExport": "error", + "useImportType": "error", + "useConsistentTypeDefinitions": { + "level": "error", + "options": { "style": "type" } + }, + "noRestrictedImports": { + "level": "error", + "options": { + "patterns": [ + { + "group": ["../**"], + "message": "Relative imports are not allowed." + } + ] + } + } + }, + "a11y": { + "noStaticElementInteractions": "off" + } + } + }, + + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + }, + + "javascript": { + "formatter": { + "quoteStyle": "single" + } + }, + + "overrides": [ + { + "includes": ["**/*.scss"], + "formatter": { "enabled": false }, + "linter": { "enabled": false }, + "assist": { "enabled": false } + }, + { + "includes": ["**/src/app/**", "**/*.config.*"], + "linter": { "rules": { "style": { "noDefaultExport": "off" } } } + } + ] +} diff --git a/dataweaver/package.json b/dataweaver/package.json new file mode 100644 index 00000000..49d8fb6e --- /dev/null +++ b/dataweaver/package.json @@ -0,0 +1,40 @@ +{ + "name": "dataweaver", + "description": "Monorepo for Dataweaver", + "type": "module", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "pnpm -r --parallel dev", + "build": "pnpm -r build", + "preview": "pnpm -r --parallel preview", + "lint": "pnpm -r run lint:type-check && pnpm run lint:biome && pnpm run lint:styles", + "lint:biome": "biome check", + "lint:styles": "stylelint \"**/*.scss\"", + "fix": "pnpm run fix:biome && pnpm run fix:styles", + "fix:biome": "biome check --write --unsafe", + "fix:styles": "stylelint \"**/*.scss\" --fix", + "generate:tokens": "pnpm --filter @app/web generate:tokens" + }, + "devDependencies": { + "@biomejs/biome": "2.4.7", + "prettier": "^3.8.1", + "stylelint-config-css-modules": "^4.6.0", + "stylelint-config-recess-order": "^7.7.0", + "stylelint-config-standard-scss": "^17.0.0", + "stylelint-declaration-block-no-ignored-properties": "^3.0.0", + "stylelint-prettier": "^5.0.3", + "stylelint": "^17.5.0", + "typescript": "5.9.3", + "husky": "^9.1.7" + }, + "packageManager": "pnpm@10.20.0", + "engines": { + "node": ">=22", + "pnpm": ">=10" + }, + "browserslist": [ + "> 0.2%", + "not dead" + ] +} diff --git a/dataweaver/packages/tokens/package.json b/dataweaver/packages/tokens/package.json new file mode 100644 index 00000000..256a0c69 --- /dev/null +++ b/dataweaver/packages/tokens/package.json @@ -0,0 +1,12 @@ +{ + "name": "@package/tokens", + "type": "module", + "scripts": { + "generate": "tsx src/generate.ts", + "lint:type-check": "tsc --noEmit" + }, + "devDependencies": { + "@types/node": "^25.5.0", + "tsx": "^4.21.0" + } +} diff --git a/dataweaver/packages/tokens/src/colors.json b/dataweaver/packages/tokens/src/colors.json new file mode 100644 index 00000000..a5b6fb4d --- /dev/null +++ b/dataweaver/packages/tokens/src/colors.json @@ -0,0 +1,9 @@ +{ + "black": [0, 0, 0], + "grey-almost-black": [32, 33, 36], + "grey-muted": [68, 71, 70], + "grey-light": [227, 231, 237], + "white": [255, 255, 255], + "blue": [11, 87, 208], + "blue-light": [247, 252, 255] +} diff --git a/dataweaver/packages/tokens/src/eases.json b/dataweaver/packages/tokens/src/eases.json new file mode 100644 index 00000000..960a8f09 --- /dev/null +++ b/dataweaver/packages/tokens/src/eases.json @@ -0,0 +1,6 @@ +{ + "linear": [0, 0, 1, 1], + "out": [0.26, 1.0, 0.48, 1.0], + "in": [0.52, 0.0, 0.74, 0.0], + "in-out": [0.76, 0.0, 0.24, 1.0] +} diff --git a/dataweaver/packages/tokens/src/generate.ts b/dataweaver/packages/tokens/src/generate.ts new file mode 100644 index 00000000..454d88af --- /dev/null +++ b/dataweaver/packages/tokens/src/generate.ts @@ -0,0 +1,104 @@ +/** + * Generates _generated.module.scss and generated.ts from JSON token files. + * + * Usage: tsx src/generate.ts + */ +import { mkdirSync, writeFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +import COLORS from './colors.json' with { type: 'json' }; +import EASES from './eases.json' with { type: 'json' }; +import VARIABLES from './variables.json' with { type: 'json' }; + +const [OUTPUT_DIRECTORY] = process.argv + .slice(2) + .filter((argument) => argument !== '--'); + +if (!OUTPUT_DIRECTORY) { + console.error( + '❌ Output directory argument is required. Usage: tsx src/generate.ts ', + ); + process.exit(1); +} + +const RESOLVED_OUTPUT_DIRECTORY = resolve(process.cwd(), OUTPUT_DIRECTORY); + +const DO_NOT_EDIT_COMMENT_BANNER = + "// ⚠️ AUTO-GENERATED — Do not edit. Modify the JSON tokens in 'packages/tokens/src/' and run 'pnpm generate:tokens'"; + +const generateScss = (): string => { + const lines: string[] = [DO_NOT_EDIT_COMMENT_BANNER]; + + // Variables + lines.push(''); + lines.push('// Variables'); + for (const [name, value] of Object.entries(VARIABLES)) { + const formattedValue = name.startsWith('gutter-') ? `${value}px` : value; + lines.push(`$${name}: ${formattedValue};`); + } + + // Colors + lines.push(''); + lines.push('// Colors'); + for (const [name, values] of Object.entries(COLORS)) { + const formattedValue = values.join(' '); + lines.push(`$color-${name}: rgb(${formattedValue});`); + } + + // Eases + lines.push(''); + lines.push('// Eases'); + for (const [name, values] of Object.entries(EASES)) { + const formattedValue = values.join(', '); + lines.push(`$ease-${name}: cubic-bezier(${formattedValue});`); + } + + lines.push(''); + return lines.join('\n'); +}; + +const generateTypeScript = (): string => { + const lines: string[] = [DO_NOT_EDIT_COMMENT_BANNER]; + + // Variables + lines.push(''); + for (const [name, value] of Object.entries(VARIABLES)) { + const constName = name.replaceAll('-', '_').toUpperCase(); + lines.push(`export const ${constName} = ${value};`); + } + + // Colors + lines.push(''); + lines.push('export const COLORS = {'); + for (const [name, values] of Object.entries(COLORS)) { + const constName = name.includes('-') ? `"${name}"` : name; + const formattedValue = values.join(', '); + lines.push(`\t${constName}: "${formattedValue}",`); + } + lines.push('} as const;'); + + // Eases + lines.push(''); + for (const [name, values] of Object.entries(EASES)) { + const constName = `EASE_${name.toUpperCase().replaceAll('-', '_')}`; + const formattedValue = values.join(', '); + lines.push(`export const ${constName} = [${formattedValue}];`); + } + + lines.push(''); + return lines.join('\n'); +}; + +console.info(`🟦 Starting token generations...`); + +mkdirSync(RESOLVED_OUTPUT_DIRECTORY, { recursive: true }); +writeFileSync( + resolve(RESOLVED_OUTPUT_DIRECTORY, '_generated.module.scss'), + generateScss(), +); +writeFileSync( + resolve(RESOLVED_OUTPUT_DIRECTORY, 'generated.ts'), + generateTypeScript(), +); + +console.info('✅ Generated _generated.module.scss and generated.ts! \n'); diff --git a/dataweaver/packages/tokens/src/variables.json b/dataweaver/packages/tokens/src/variables.json new file mode 100644 index 00000000..5c9a2d27 --- /dev/null +++ b/dataweaver/packages/tokens/src/variables.json @@ -0,0 +1,5 @@ +{ + "breakpoint-tablet": 768, + "breakpoint-laptop": 1280, + "breakpoint-desktop": 1600 +} diff --git a/dataweaver/packages/tokens/tsconfig.json b/dataweaver/packages/tokens/tsconfig.json new file mode 100644 index 00000000..ebc5f351 --- /dev/null +++ b/dataweaver/packages/tokens/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { "types": ["node"] }, + "include": ["src"] +} diff --git a/dataweaver/packages/tsconfig.base.json b/dataweaver/packages/tsconfig.base.json new file mode 100644 index 00000000..70b1aacb --- /dev/null +++ b/dataweaver/packages/tsconfig.base.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "module": "preserve", + "moduleResolution": "bundler", + "moduleDetection": "force", + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "allowImportingTsExtensions": true, + "noUncheckedIndexedAccess": true, + "noEmit": true + } +} diff --git a/dataweaver/pnpm-lock.yaml b/dataweaver/pnpm-lock.yaml new file mode 100644 index 00000000..252f1ad0 --- /dev/null +++ b/dataweaver/pnpm-lock.yaml @@ -0,0 +1,4705 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@biomejs/biome': + specifier: 2.4.7 + version: 2.4.7 + husky: + specifier: ^9.1.7 + version: 9.1.7 + prettier: + specifier: ^3.8.1 + version: 3.8.3 + stylelint: + specifier: ^17.5.0 + version: 17.12.0(typescript@5.9.3) + stylelint-config-css-modules: + specifier: ^4.6.0 + version: 4.6.0(stylelint@17.12.0(typescript@5.9.3)) + stylelint-config-recess-order: + specifier: ^7.7.0 + version: 7.7.0(stylelint-order@8.1.1(stylelint@17.12.0(typescript@5.9.3)))(stylelint@17.12.0(typescript@5.9.3)) + stylelint-config-standard-scss: + specifier: ^17.0.0 + version: 17.0.0(postcss@8.5.15)(stylelint@17.12.0(typescript@5.9.3)) + stylelint-declaration-block-no-ignored-properties: + specifier: ^3.0.0 + version: 3.0.0(stylelint@17.12.0(typescript@5.9.3)) + stylelint-prettier: + specifier: ^5.0.3 + version: 5.0.3(prettier@3.8.3)(stylelint@17.12.0(typescript@5.9.3)) + typescript: + specifier: 5.9.3 + version: 5.9.3 + + apps/web: + dependencies: + clsx: + specifier: ^2.1.1 + version: 2.1.1 + motion: + specifier: ^12.38.0 + version: 12.40.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + next: + specifier: ^16.2.6 + version: 16.2.6(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.100.0) + react: + specifier: ^19.2.6 + version: 19.2.6 + react-dom: + specifier: ^19.2.6 + version: 19.2.6(react@19.2.6) + tldraw: + specifier: ^5.0.1 + version: 5.0.1(@floating-ui/dom@1.7.6)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + devDependencies: + '@package/tokens': + specifier: workspace:tokens + version: link:../../packages/tokens + '@types/node': + specifier: ^25.9.1 + version: 25.9.1 + '@types/react': + specifier: ^19.2.15 + version: 19.2.15 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.15) + babel-plugin-react-compiler: + specifier: ^1.0.0 + version: 1.0.0 + sass: + specifier: ^1.97.3 + version: 1.100.0 + + packages/tokens: + devDependencies: + '@types/node': + specifier: ^25.5.0 + version: 25.9.1 + tsx: + specifier: ^4.21.0 + version: 4.22.3 + +packages: + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@biomejs/biome@2.4.7': + resolution: {integrity: sha512-vXrgcmNGZ4lpdwZSpMf1hWw1aWS6B+SyeSYKTLrNsiUsAdSRN0J4d/7mF3ogJFbIwFFSOL3wT92Zzxia/d5/ng==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.4.7': + resolution: {integrity: sha512-Oo0cF5mHzmvDmTXw8XSjhCia8K6YrZnk7aCS54+/HxyMdZMruMO3nfpDsrlar/EQWe41r1qrwKiCa2QDYHDzWA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.4.7': + resolution: {integrity: sha512-I+cOG3sd/7HdFtvDSnF9QQPrWguUH7zrkIMMykM3PtfWU9soTcS2yRb9Myq6MHmzbeCT08D1UmY+BaiMl5CcoQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.4.7': + resolution: {integrity: sha512-I2NvM9KPb09jWml93O2/5WMfNR7Lee5Latag1JThDRMURVhPX74p9UDnyTw3Ae6cE1DgXfw7sqQgX7rkvpc0vw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@2.4.7': + resolution: {integrity: sha512-om6FugwmibzfP/6ALj5WRDVSND4H2G9X0nkI1HZpp2ySf9lW2j0X68oQSaHEnls6666oy4KDsc5RFjT4m0kV0w==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@2.4.7': + resolution: {integrity: sha512-00kx4YrBMU8374zd2wHuRV5wseh0rom5HqRND+vDldJPrWwQw+mzd/d8byI9hPx926CG+vWzq6AeiT7Yi5y59g==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@2.4.7': + resolution: {integrity: sha512-bV8/uo2Tj+gumnk4sUdkerWyCPRabaZdv88IpbmDWARQQoA/Q0YaqPz1a+LSEDIL7OfrnPi9Hq1Llz4ZIGyIQQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@2.4.7': + resolution: {integrity: sha512-hOUHBMlFCvDhu3WCq6vaBoG0dp0LkWxSEnEEsxxXvOa9TfT6ZBnbh72A/xBM7CBYB7WgwqboetzFEVDnMxelyw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.4.7': + resolution: {integrity: sha512-qEpGjSkPC3qX4ycbMUthXvi9CkRq7kZpkqMY1OyhmYlYLnANnooDQ7hDerM8+0NJ+DZKVnsIc07h30XOpt7LtQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + + '@cacheable/memory@2.0.9': + resolution: {integrity: sha512-HdMx6DoGywB30vacDbBsITbIX4pgFqj1zsrV58jZBUw3klzkNoXhj7qOqAgledhxG7YZI5rBSJg7Zp8/VG0DuA==} + + '@cacheable/utils@2.4.1': + resolution: {integrity: sha512-eiFgzCbIneyMlLOmNG4g9xzF7Hv3Mga4LjxjcSC/ues6VYq2+gUbQI8JqNuw/ZM8tJIeIaBGpswAsqV2V7ApgA==} + + '@csstools/css-calc@3.2.1': + resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.4': + resolution: {integrity: sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + + '@csstools/media-query-list-parser@5.0.0': + resolution: {integrity: sha512-T9lXmZOfnam3eMERPsszjY5NK0jX8RmThmmm99FZ8b7z8yMaFZWKwLWGZuTwdO3ddRY5fy13GmmEYZXB4I98Eg==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/selector-resolve-nested@4.0.0': + resolution: {integrity: sha512-9vAPxmp+Dx3wQBIUwc1v7Mdisw1kbbaGqXUM8QLTgWg7SoPGYtXBsMXvsFs/0Bn5yoFhcktzxNZGNaUt0VjgjA==} + engines: {node: '>=20.19.0'} + peerDependencies: + postcss-selector-parser: ^7.1.1 + + '@csstools/selector-specificity@6.0.0': + resolution: {integrity: sha512-4sSgl78OtOXEX/2d++8A83zHNTgwCJMaR24FvsYL7Uf/VS8HZk9PTwR51elTbGqMuwH3szLvvOXEaVnqn0Z3zA==} + engines: {node: '>=20.19.0'} + peerDependencies: + postcss-selector-parser: ^7.1.1 + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@esbuild/aix-ppc64@0.28.0': + resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.0': + resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.0': + resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.0': + resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.0': + resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.0': + resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.0': + resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.0': + resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.0': + resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.0': + resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.0': + resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.0': + resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.0': + resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.0': + resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.0': + resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.0': + resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.0': + resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.0': + resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.0': + resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.0': + resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.0': + resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.0': + resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.0': + resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.0': + resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.0': + resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.0': + resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@keyv/bigmap@1.3.1': + resolution: {integrity: sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==} + engines: {node: '>= 18'} + peerDependencies: + keyv: ^5.6.0 + + '@keyv/serialize@1.1.1': + resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} + + '@next/env@16.2.6': + resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==} + + '@next/swc-darwin-arm64@16.2.6': + resolution: {integrity: sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@16.2.6': + resolution: {integrity: sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@16.2.6': + resolution: {integrity: sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-arm64-musl@16.2.6': + resolution: {integrity: sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-x64-gnu@16.2.6': + resolution: {integrity: sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-linux-x64-musl@16.2.6': + resolution: {integrity: sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-win32-arm64-msvc@16.2.6': + resolution: {integrity: sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-x64-msvc@16.2.6': + resolution: {integrity: sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@parcel/watcher-android-arm64@2.5.6': + resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.6': + resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.6': + resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.6': + resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.6': + resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm-musl@2.5.6': + resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm64-glibc@2.5.6': + resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.5.6': + resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.5.6': + resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.5.6': + resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-win32-arm64@2.5.6': + resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.6': + resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.6': + resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.6': + resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} + engines: {node: '>= 10.0.0'} + + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-accessible-icon@1.1.7': + resolution: {integrity: sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-accordion@1.2.12': + resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-alert-dialog@1.1.15': + resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-aspect-ratio@1.1.7': + resolution: {integrity: sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-avatar@1.1.10': + resolution: {integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-checkbox@1.3.3': + resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collapsible@1.1.12': + resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context-menu@2.2.16': + resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-form@0.1.8': + resolution: {integrity: sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-hover-card@1.1.15': + resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.7': + resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menubar@1.1.16': + resolution: {integrity: sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-navigation-menu@1.2.14': + resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-one-time-password-field@0.1.8': + resolution: {integrity: sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-password-toggle-field@0.1.3': + resolution: {integrity: sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popover@1.1.15': + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-progress@1.1.7': + resolution: {integrity: sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-radio-group@1.3.8': + resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-scroll-area@1.2.10': + resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.2.6': + resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-separator@1.1.7': + resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slider@1.3.6': + resolution: {integrity: sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-switch@1.2.6': + resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tabs@1.1.13': + resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toast@1.2.15': + resolution: {integrity: sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle-group@1.1.11': + resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle@1.1.10': + resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toolbar@1.1.11': + resolution: {integrity: sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.2.8': + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-is-hydrated@0.1.0': + resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + + '@tiptap/core@3.23.6': + resolution: {integrity: sha512-MRB3pHz4Oxqmcawh0cQ5iOGdY5xtNYp/1CoK7hdTLzw5K0C6/gTC2VvanB1R4INaB6EpBkxG/GiWkVirDRnuXw==} + peerDependencies: + '@tiptap/pm': 3.23.6 + + '@tiptap/extension-blockquote@3.23.6': + resolution: {integrity: sha512-2RmnqNqTltZ2k1F7IfjoDNs935Uq4rRDR7d98mqkg3OlDktcQIyBpv0t9dTay6H5bkQeZUuS8ogK2S1E8Edjug==} + peerDependencies: + '@tiptap/core': 3.23.6 + + '@tiptap/extension-bold@3.23.6': + resolution: {integrity: sha512-1LMhjnytdbbhWHSoOwnLxZAOQZWPkKyXVCNmaIk0Mhi4tLPUXptG4qKS5sVYTCveE5H6IBPFrbgBFi5dMI6krA==} + peerDependencies: + '@tiptap/core': 3.23.6 + + '@tiptap/extension-bubble-menu@3.23.6': + resolution: {integrity: sha512-Mwkyp9LkDHFbqmWRIkp63FinRxFu3ajC4qSb9t4mnHsb4kAdbNLLsGtbFg+le0SWk4CxGwAOwM7SzeJ+6UGqCA==} + peerDependencies: + '@tiptap/core': 3.23.6 + '@tiptap/pm': 3.23.6 + + '@tiptap/extension-bullet-list@3.23.6': + resolution: {integrity: sha512-RMRgfXZykr/13X8UBOwvpgysVOo9KchwqMoEbvqQSj4YFfU56iIn59C8sbxiQ1sKfeltUf0wH4fPc0I4iwKqAA==} + peerDependencies: + '@tiptap/extension-list': 3.23.6 + + '@tiptap/extension-code-block@3.23.6': + resolution: {integrity: sha512-4kccgcn5yHThxrzsIhJny3EwfEZYIk+BjUCL4uIuzOyWvExtGhZ6JMHVCZeMhI8D1/bX1LNkkAKN5DXPzH4lXQ==} + peerDependencies: + '@tiptap/core': 3.23.6 + '@tiptap/pm': 3.23.6 + + '@tiptap/extension-code@3.23.6': + resolution: {integrity: sha512-KG8KXFYyLrtYvT7AZ1WGV61ofx8pDe5g9pH658MERxqQGii+Pyfc6xkz04l7XeBts/7+571UQp/0O7i/z560TA==} + peerDependencies: + '@tiptap/core': 3.23.6 + + '@tiptap/extension-document@3.23.6': + resolution: {integrity: sha512-XDAIgG9KcKumFM9KJWUEUhXPbFIhhl47bfy5GknareWTRKke85rcoj/oxKKO9ihLZr8JfpbXjqnS4SCm5yhYPw==} + peerDependencies: + '@tiptap/core': 3.23.6 + + '@tiptap/extension-dropcursor@3.23.6': + resolution: {integrity: sha512-+XWEoRKf3lXxi7Le1aOM2xU1XHwxICGpXjT3m4QaYqUgIpsq8gQEuso6kVg8DnTD7biKQs6+oIQ0o2b/gTW9WA==} + peerDependencies: + '@tiptap/extensions': 3.23.6 + + '@tiptap/extension-floating-menu@3.23.6': + resolution: {integrity: sha512-2kjuDcEq69lEcECl75xqY5MyzUSh2zcC5aLrpwP1WwhJz5bxsIFHiaps5AP6h9R4A+ZBj5b2haay2Y1wDUU3VA==} + peerDependencies: + '@floating-ui/dom': ^1.0.0 + '@tiptap/core': 3.23.6 + '@tiptap/pm': 3.23.6 + + '@tiptap/extension-gapcursor@3.23.6': + resolution: {integrity: sha512-wbKmxXsszxWacEkrHucRpSQbiKjz4fmOebD6OVyL9AcrmlbxNk8vcM3iyh/8cVeRy09XY+morM165t/u7/z4IQ==} + peerDependencies: + '@tiptap/extensions': 3.23.6 + + '@tiptap/extension-hard-break@3.23.6': + resolution: {integrity: sha512-KeUm+tkUfIVSX9QM9XOIhaay0Fn36sLKUo5NVYjN3uJaxFvaZXZmTlxdO85OTdgF2P5sqh9LomrIgliaFRGk4w==} + peerDependencies: + '@tiptap/core': 3.23.6 + + '@tiptap/extension-heading@3.23.6': + resolution: {integrity: sha512-A/0jPhxnUh9THSZymlu0OGPZe1wdFdwHAXnRCmqvYUCwJjrG7LCC/ahzmcj1tcNzI9hgHyuYPSfev8RXYrNu/w==} + peerDependencies: + '@tiptap/core': 3.23.6 + + '@tiptap/extension-highlight@3.23.6': + resolution: {integrity: sha512-GWv6R4iZzVrNeB33qtJDBt/BfVjuc5S/sUF/VDFAtK8h86bg4cEm1tGLEsui4T3+vij/t+P+eoGhkzF6NGogdg==} + peerDependencies: + '@tiptap/core': 3.23.6 + + '@tiptap/extension-horizontal-rule@3.23.6': + resolution: {integrity: sha512-hEUlz4H+I64r+TH6LCuNCRgO7JTHncXGmx9+WbU69EOfY8O0ZurcgeJc8HeiAKL+r9YuC1e5YHfFxgCaaC0jlg==} + peerDependencies: + '@tiptap/core': 3.23.6 + '@tiptap/pm': 3.23.6 + + '@tiptap/extension-italic@3.23.6': + resolution: {integrity: sha512-wol5KdwCPAvpiYhH9PLlvO8ZnJHwZtIboVevrfOGgBcKlXRA3dedR4OAMXHnUtkkzu9KtliLg1+TYzEx4JZG9Q==} + peerDependencies: + '@tiptap/core': 3.23.6 + + '@tiptap/extension-link@3.23.6': + resolution: {integrity: sha512-KNZz7z7P2/qbQsx5bPAbSPjrKDg1VHsedGlLHJCr8U2VRD5VgmDLkMpkouP1CsDg15qgyUKv/nDib5KgPpLNWA==} + peerDependencies: + '@tiptap/core': 3.23.6 + '@tiptap/pm': 3.23.6 + + '@tiptap/extension-list-item@3.23.6': + resolution: {integrity: sha512-3zzyhdkUWcHVpXuvy6KiIwjh29rbH6gEDEqPQqHLrl1XGnO9pnShC7pSHctlCDjmcx3O4n9cd4QMtVBlUerbiA==} + peerDependencies: + '@tiptap/extension-list': 3.23.6 + + '@tiptap/extension-list-keymap@3.23.6': + resolution: {integrity: sha512-x8bPcLViGzg/RAmQM/XtmfqIwQ/Pv9Q8mkd+OgfUiTqjeJqKwVQmiqbLFNa7zw81+H61M+HDU+qGAaQ3vRIMjw==} + peerDependencies: + '@tiptap/extension-list': 3.23.6 + + '@tiptap/extension-list@3.23.6': + resolution: {integrity: sha512-z6vj9+Qht2sjdQkyyHcUpsC/yCIZqTrQiyHDhs/HGKrfvoANyAZGpqdNeKf1wSyjIso+27tQuIH5NDfk8ygyNw==} + peerDependencies: + '@tiptap/core': 3.23.6 + '@tiptap/pm': 3.23.6 + + '@tiptap/extension-ordered-list@3.23.6': + resolution: {integrity: sha512-1m/wWB/ZtXcmG2vNdiUkCqsOgqv5vBjCv/mVaHhF9OvV+zQS8YDjoWE7zEuT/GgELdT77Xq8lHrn4nCDudB3/A==} + peerDependencies: + '@tiptap/extension-list': 3.23.6 + + '@tiptap/extension-paragraph@3.23.6': + resolution: {integrity: sha512-+7m58LUSncodjrIyXks4RZ3tLNYrvgT77wRR4l3HnM5OABY3GDsDTqi7c1t1yI29NVOSk/DUacqy6UwYAj1DGg==} + peerDependencies: + '@tiptap/core': 3.23.6 + + '@tiptap/extension-strike@3.23.6': + resolution: {integrity: sha512-oF7FEZ37f15aCe5kPgzGDYf/m+hr7VdQ/Ko/Hds/UM9pX7AG1fdtmRrl6wqkRqDM/incZaC/AQR2/Dpo2VCNGQ==} + peerDependencies: + '@tiptap/core': 3.23.6 + + '@tiptap/extension-text@3.23.6': + resolution: {integrity: sha512-ipoC2TkIAIOTiF5ByiGgvQB1DqDyfP90wrUB3mohBcgvp7lQnwHszCDGv8dNnmcUek8uXV/uoLu2VXeVQlxjPA==} + peerDependencies: + '@tiptap/core': 3.23.6 + + '@tiptap/extension-underline@3.23.6': + resolution: {integrity: sha512-P55wGIZGYTVH92Fq0cgI4/O9AhLCaJC3hhxg15RSERP5/YegM9eJHDK/GQ1EE/DvYA+xpYGOV6agKwAUqfA/Iw==} + peerDependencies: + '@tiptap/core': 3.23.6 + + '@tiptap/extensions@3.23.6': + resolution: {integrity: sha512-X09/Db1teB+ifXzDGVVFmOeQRx7wTAayE9/280spxpsHkHZvJ5bHRvWIzUzviMIjbBz+NPDIKYPK7gMfh9iaig==} + peerDependencies: + '@tiptap/core': 3.23.6 + '@tiptap/pm': 3.23.6 + + '@tiptap/pm@3.23.6': + resolution: {integrity: sha512-in5CaMaWlJcH2A1q6GJKFtrodE8WLS3M9tIi/f89jPmIVHJShpodC0KZDNyJkrVBQomYk0DEh86Utm6ASXzQww==} + + '@tiptap/react@3.23.6': + resolution: {integrity: sha512-Tw9KZkYqFMk3vaJAEQKqEYIO/iq3cSJe7OUEGBul4k4GaMQeLItLf5EYhUd0GIPXci1WVVPNntKJsHfX25M37w==} + peerDependencies: + '@tiptap/core': 3.23.6 + '@tiptap/pm': 3.23.6 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tiptap/starter-kit@3.23.6': + resolution: {integrity: sha512-gykwtGWrnWCmtql1hid3opac/KV8zQvOAnu3bTqIqcHrn1FusbUwKmNzavSbfGvcktHM3hFjb35W48JyVLyu/A==} + + '@tldraw/driver@5.0.1': + resolution: {integrity: sha512-C862NIO++TJyzNB2nww5u/yNUEz1EKriSKy85KJZB4RtzK9cLDLOHpiNYkpzVbmU7d5a4wUDYItbULf2W9vNrA==} + + '@tldraw/editor@5.0.1': + resolution: {integrity: sha512-Lou9t1EFquy2CtpZOhEzC5vjVx7E6RdEv9hvOMm4X4RS+7hQv3wcXTlAngLMzxcxWRHDh1mD+Fv5IKXJUT0jgQ==} + peerDependencies: + react: ^18.2.0 || ^19.2.1 + react-dom: ^18.2.0 || ^19.2.1 + + '@tldraw/state-react@5.0.1': + resolution: {integrity: sha512-DDF7dCSRXeHRcQdlNBp2cl0PEy2MMYxvKcsvpCgMMSu9m775RNQ/smMFq7QNtUht9aWGruQKByf4WGLXqTe4CA==} + peerDependencies: + react: ^18.2.0 || ^19.2.1 + react-dom: ^18.2.0 || ^19.2.1 + + '@tldraw/state@5.0.1': + resolution: {integrity: sha512-P3tz5BCYfn7s9O0N8qsDv2LNhvbmEX+rcbGJM/QSyMdoBwIUoQILSK3zrq8k3TIOIj2+Wy2H7qBZM9U+6vrRwg==} + + '@tldraw/store@5.0.1': + resolution: {integrity: sha512-FMn38Aqg/0Xqp+kJ+5VR511THI+0QBJ1gHRBtHPS4B2ao5WOXNQa9JVNnoEA/Td03chpvUegifksK6puzOZ8mg==} + peerDependencies: + react: ^18.2.0 || ^19.2.1 + + '@tldraw/tlschema@5.0.1': + resolution: {integrity: sha512-IZc4H+BG7SrBcrE51hQPbzCgtuZen0I4MAkFB9QaNRbqUouNXqBXaHyy6dHQ3xziPuM/gxBignEMqL6ERPgu4Q==} + peerDependencies: + react: ^18.2.0 || ^19.2.1 + react-dom: ^18.2.0 || ^19.2.1 + + '@tldraw/utils@5.0.1': + resolution: {integrity: sha512-YjzJrygF6NYqbmL6l6lTtqViL7i8jMPpYwqY3K9e9w2TiqrHgswj1cDytZ0B3xfB4TIWX9HYj3zLL2qm0L9zEg==} + + '@tldraw/validate@5.0.1': + resolution: {integrity: sha512-iahdvUjHa3ONn6/xHdsb5yhIpcA26Ge6zuBQqRhOJGikU353mW4B6jmQTU8B/bEsEiH1SMs/Gm+IJxqGPDP29Q==} + + '@types/node@25.9.1': + resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.15': + resolution: {integrity: sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==} + + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + + babel-plugin-react-compiler@1.0.0: + resolution: {integrity: sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==} + + baseline-browser-mapping@2.10.32: + resolution: {integrity: sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==} + engines: {node: '>=6.0.0'} + hasBin: true + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + cacheable@2.3.5: + resolution: {integrity: sha512-EQfaKe09tl615iNvq/TBRWTFf1AKJNXYQSsMx0Z3EI0nA+pVsVPS8wJhnRlkbdacKPh1d0qVIhwTc2zsQNFEEg==} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001793: + resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colord@2.9.3: + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + + cosmiconfig@9.0.1: + resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + css-functions-list@3.3.3: + resolution: {integrity: sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg==} + engines: {node: '>=12'} + + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + esbuild@0.28.0: + resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} + engines: {node: '>=18'} + hasBin: true + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-equals@5.4.0: + resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==} + engines: {node: '>=6.0.0'} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + + fastest-levenshtein@1.0.16: + resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} + engines: {node: '>= 4.9.1'} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + file-entry-cache@11.1.3: + resolution: {integrity: sha512-oMbq0PD6VIiIwMF6LIa7MEwd/l9huKwmqRKXqmrkqIZv8CvRbfowL+L0ryAl8h//HfAS0zS+4SbYoRyAoA6BJA==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + flat-cache@6.1.22: + resolution: {integrity: sha512-N2dnzVJIphnNsjHcrxGW7DePckJ6haPrSFqpsBUhHYgwtKGVq4JrBGielEGD2fCVnsGm1zlBVZ8wGhkyuetgug==} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + fractional-indexing@3.2.0: + resolution: {integrity: sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ==} + engines: {node: ^14.13.1 || >=16.0.0} + + framer-motion@12.40.0: + resolution: {integrity: sha512-uaBd3qC1v3KQqBEjwTUd183K6PbS+j0yR9w9VmEOLWA/tnUcSn8Xa3uck7t4dgpDoUss8xQTcj8W2L07lrnLFg==} + 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 + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + global-modules@2.0.0: + resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} + engines: {node: '>=6'} + + global-prefix@3.0.0: + resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} + engines: {node: '>=6'} + + globby@16.2.0: + resolution: {integrity: sha512-QrJia2qDf5BB/V6HYlDTs0I0lBahyjLzpGQg3KT7FnCdTonAyPy2RtY802m2k4ALx6Dp752f82WsOczEVr3l6Q==} + engines: {node: '>=20'} + + globjoin@0.1.4: + resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} + + has-flag@5.0.1: + resolution: {integrity: sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==} + engines: {node: '>=12'} + + hashery@1.5.1: + resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==} + engines: {node: '>=20'} + + hookified@1.15.1: + resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==} + + hookified@2.2.0: + resolution: {integrity: sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==} + + html-tags@5.1.0: + resolution: {integrity: sha512-n6l5uca7/y5joxZ3LUePhzmBFUJ+U2YWzhMa8XUTecSeSlQiZdF5XAd/Q3/WUl0VsXgUwWi8I7CNIwdI5WN1SQ==} + engines: {node: '>=20.10'} + + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + + idb@7.1.1: + resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + immutable@5.1.5: + resolution: {integrity: sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@4.0.0: + resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} + engines: {node: '>=12'} + + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jittered-fractional-indexing@1.0.1: + resolution: {integrity: sha512-OpKFkVr4hU5ivd1ZCjZfHvVpWekraJvcePcMusBmgBmCVQK5JiRCA+4TT1vAUTLqGD9MkhqFwO0l3QspvlZgzw==} + engines: {node: '>=18.0.0'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + keyv@5.6.0: + resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + known-css-properties@0.37.0: + resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + linkifyjs@4.3.3: + resolution: {integrity: sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==} + + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + + lodash.isequalwith@4.4.0: + resolution: {integrity: sha512-dcZON0IalGBpRmJBmMkaoV7d3I80R2O+FrzsZyHdNSFrANq/cgDqKQNmAHE8UEj4+QYWwwhkQOVdLHiAopzlsQ==} + + lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + + lodash.truncate@4.4.2: + resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} + + lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + mathml-tag-names@4.0.0: + resolution: {integrity: sha512-aa6AU2Pcx0VP/XWnh8IGL0SYSgQHDT6Ucror2j2mXeFAlN3ahaNs8EZtG1YiticMkSLj3Gt6VPFfZogt7G5iFQ==} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + meow@14.1.0: + resolution: {integrity: sha512-EDYo6VlmtnumlcBCbh1gLJ//9jvM/ndXHfVXIFrZVr6fGcwTUyCTFNTLCKuY3ffbK8L/+3Mzqnd58RojiZqHVw==} + engines: {node: '>=20'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + motion-dom@12.40.0: + resolution: {integrity: sha512-HxU3ZaBwNPVQUBQf1xxgq+7JrPNZvjLVxgbpEZL7RrWJnsxOf0/OM+yrHG9ogLQ31Do/r57Oz2gQWPK+6q62mg==} + + motion-utils@12.39.0: + resolution: {integrity: sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==} + + motion@12.40.0: + resolution: {integrity: sha512-yjrHUrBFW6kQvjJwRsoiPSAhC5tRwRqNGJWmiJ4CrGnbKp0V88AdzkhBmDoqIsIPfarOe0Uddd37Xq43/gIocA==} + 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 + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + next@16.2.6: + resolution: {integrity: sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==} + engines: {node: '>=20.9.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + orderedmap@2.1.1: + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + postcss-media-query-parser@0.2.3: + resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} + + postcss-resolve-nested-selector@0.1.6: + resolution: {integrity: sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==} + + postcss-safe-parser@7.0.1: + resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} + engines: {node: '>=18.0'} + peerDependencies: + postcss: ^8.4.31 + + postcss-scss@4.0.9: + resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.4.29 + + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} + engines: {node: '>=4'} + + postcss-sorting@10.0.0: + resolution: {integrity: sha512-TXbU+h6vVRW+86c/+ewhWq9k7pr7ijASTnepVhCQiC87zAOTkvB1v2dHyWP+ggstSTX/PNvjzS+IOqzejndz9w==} + peerDependencies: + postcss: ^8.4.20 + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + prettier-linter-helpers@1.0.1: + resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} + engines: {node: '>=6.0.0'} + + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + engines: {node: '>=14'} + hasBin: true + + prosemirror-changeset@2.4.1: + resolution: {integrity: sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==} + + prosemirror-commands@1.7.1: + resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==} + + prosemirror-dropcursor@1.8.2: + resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==} + + prosemirror-gapcursor@1.4.1: + resolution: {integrity: sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==} + + prosemirror-history@1.5.0: + resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==} + + prosemirror-keymap@1.2.3: + resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} + + prosemirror-model@1.25.7: + resolution: {integrity: sha512-A79aN8QEFUwI6cax8Yq4Rpcx1TJZ3Kagn+ii7qLo4/V8H3mMiHrhFyhTyHHvpSnOgMPpWiDGSwM3etwrxE50ug==} + + prosemirror-schema-list@1.5.1: + resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} + + prosemirror-state@1.4.4: + resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==} + + prosemirror-tables@1.8.5: + resolution: {integrity: sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==} + + prosemirror-transform@1.12.0: + resolution: {integrity: sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==} + + prosemirror-view@1.41.8: + resolution: {integrity: sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==} + + qified@0.10.1: + resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==} + engines: {node: '>=20'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + quickselect@2.0.0: + resolution: {integrity: sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==} + + radix-ui@1.4.3: + resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + rbush@3.0.1: + resolution: {integrity: sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==} + + react-dom@19.2.6: + resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} + peerDependencies: + react: ^19.2.6 + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react@19.2.6: + resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} + engines: {node: '>=0.10.0'} + + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + sass@1.100.0: + resolution: {integrity: sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==} + engines: {node: '>=20.19.0'} + hasBin: true + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@7.8.1: + resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@5.1.0: + resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} + engines: {node: '>=14.16'} + + slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@8.2.1: + resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==} + engines: {node: '>=20'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + stylelint-config-css-modules@4.6.0: + resolution: {integrity: sha512-LIgfzNmpiwf/eDxQva2g1mj+mqDpvP1XwOKJC7DSWY/iCQAZdIKnY42jbXuY8thcFN4yM/uF1xfCyfd7fJKh6w==} + peerDependencies: + stylelint: ^14.5.1 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + stylelint-config-recess-order@7.7.0: + resolution: {integrity: sha512-TWRkg+BrwHOki4pi9y1emWgx6pFwZXOYZhNsbEObke/mzYUJVJvDEzJJQXhH7ajslQJGcrExy8ZvJqDiUbhFpA==} + peerDependencies: + stylelint: ^16.18.0 || ^17.0.0 + stylelint-order: ^7.0.0 || ^8.0.0 + + stylelint-config-recommended-scss@17.0.1: + resolution: {integrity: sha512-x5DVehzJudcwF0od3sGpgkln2PLLranFE7twwbp7dqDINCyZvwzFkMc6TLhNOvazRiVBJYATQLouJY0xPGB8WA==} + engines: {node: '>=20'} + peerDependencies: + postcss: ^8.3.3 + stylelint: ^17.0.0 + peerDependenciesMeta: + postcss: + optional: true + + stylelint-config-recommended@18.0.0: + resolution: {integrity: sha512-mxgT2XY6YZ3HWWe3Di8umG6aBmWmHTblTgu/f10rqFXnyWxjKWwNdjSWkgkwCtxIKnqjSJzvFmPT5yabVIRxZg==} + engines: {node: '>=20.19.0'} + peerDependencies: + stylelint: ^17.0.0 + + stylelint-config-standard-scss@17.0.0: + resolution: {integrity: sha512-uLJS6xgOCBw5EMsDW7Ukji8l28qRoMnkRch15s0qwZpskXvWt9oPzMmcYM307m9GN4MxuWLsQh4I6hU9yI53cQ==} + engines: {node: '>=20'} + peerDependencies: + postcss: ^8.3.3 + stylelint: ^17.0.0 + peerDependenciesMeta: + postcss: + optional: true + + stylelint-config-standard@40.0.0: + resolution: {integrity: sha512-EznGJxOUhtWck2r6dJpbgAdPATIzvpLdK9+i5qPd4Lx70es66TkBPljSg4wN3Qnc6c4h2n+WbUrUynQ3fanjHw==} + engines: {node: '>=20.19.0'} + peerDependencies: + stylelint: ^17.0.0 + + stylelint-declaration-block-no-ignored-properties@3.0.0: + resolution: {integrity: sha512-3ml4NgSW6nkHQrk+/ounU7Qljfb7e7FayHzU7Mry6rF9X28RXyPLD2bNn4QVOO7t98d5EGCCVkNbHCZSx+bNUQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + stylelint: ^17.0.0 + + stylelint-order@8.1.1: + resolution: {integrity: sha512-LqsEB6VggJuu5v10RtkrQsBObcdwBE7GuAOlwfc/LR3VL/w8UqKX2BOLIjhyGt0Gne/njo7gRNGiJAKhfmPMNw==} + engines: {node: '>=20.19.0'} + peerDependencies: + stylelint: ^16.18.0 || ^17.0.0 + + stylelint-prettier@5.0.3: + resolution: {integrity: sha512-B6V0oa35ekRrKZlf+6+jA+i50C4GXJ7X1PPmoCqSUoXN6BrNF6NhqqhanvkLjqw2qgvrS0wjdpeC+Tn06KN3jw==} + engines: {node: '>=18.12.0'} + peerDependencies: + prettier: '>=3.0.0' + stylelint: '>=16.0.0' + + stylelint-scss@7.1.1: + resolution: {integrity: sha512-pLPXJZ7RtAFNLXe8gqarf3B56ScVTd1vPiL9IFgcJkIZsYPzAgLJPh2h9NHrp3BFeKrtAkIgwQ08QSmhvlv1gA==} + engines: {node: '>=20.19.0'} + peerDependencies: + stylelint: ^16.8.2 || ^17.0.0 + + stylelint@17.12.0: + resolution: {integrity: sha512-KIlzWXMHUvgfPUR0R7TK3H80yCIi0uoivUwf+6Az4yrHJD1Q3c1qIkh/H5Z0i/K3QXgtq/UMEkWyBUSUwnpnOg==} + engines: {node: '>=20.19.0'} + hasBin: true + + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + + supports-hyperlinks@4.4.0: + resolution: {integrity: sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg==} + engines: {node: '>=20'} + + svg-tags@1.0.0: + resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} + + table@6.9.0: + resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} + engines: {node: '>=10.0.0'} + + tldraw@5.0.1: + resolution: {integrity: sha512-WPmjov5uKU27ybP0abttWYtDBXR2BHBEYRTqSeMOnJs78OHD+JmLlVCOtl826pqsrMrwZzahMcGwRY9Qs3jzHg==} + peerDependencies: + react: ^18.2.0 || ^19.2.1 + react-dom: ^18.2.0 || ^19.2.1 + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.22.3: + resolution: {integrity: sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==} + engines: {node: '>=18.0.0'} + hasBin: true + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + + unicorn-magic@0.4.0: + resolution: {integrity: sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==} + engines: {node: '>=20'} + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + + write-file-atomic@7.0.1: + resolution: {integrity: sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==} + engines: {node: ^20.17.0 || >=22.9.0} + +snapshots: + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@biomejs/biome@2.4.7': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.4.7 + '@biomejs/cli-darwin-x64': 2.4.7 + '@biomejs/cli-linux-arm64': 2.4.7 + '@biomejs/cli-linux-arm64-musl': 2.4.7 + '@biomejs/cli-linux-x64': 2.4.7 + '@biomejs/cli-linux-x64-musl': 2.4.7 + '@biomejs/cli-win32-arm64': 2.4.7 + '@biomejs/cli-win32-x64': 2.4.7 + + '@biomejs/cli-darwin-arm64@2.4.7': + optional: true + + '@biomejs/cli-darwin-x64@2.4.7': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.4.7': + optional: true + + '@biomejs/cli-linux-arm64@2.4.7': + optional: true + + '@biomejs/cli-linux-x64-musl@2.4.7': + optional: true + + '@biomejs/cli-linux-x64@2.4.7': + optional: true + + '@biomejs/cli-win32-arm64@2.4.7': + optional: true + + '@biomejs/cli-win32-x64@2.4.7': + optional: true + + '@cacheable/memory@2.0.9': + dependencies: + '@cacheable/utils': 2.4.1 + '@keyv/bigmap': 1.3.1(keyv@5.6.0) + hookified: 1.15.1 + keyv: 5.6.0 + + '@cacheable/utils@2.4.1': + dependencies: + hashery: 1.5.1 + keyv: 5.6.0 + + '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.4(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + + '@csstools/media-query-list-parser@5.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/selector-resolve-nested@4.0.0(postcss-selector-parser@7.1.1)': + dependencies: + postcss-selector-parser: 7.1.1 + + '@csstools/selector-specificity@6.0.0(postcss-selector-parser@7.1.1)': + dependencies: + postcss-selector-parser: 7.1.1 + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.28.0': + optional: true + + '@esbuild/android-arm64@0.28.0': + optional: true + + '@esbuild/android-arm@0.28.0': + optional: true + + '@esbuild/android-x64@0.28.0': + optional: true + + '@esbuild/darwin-arm64@0.28.0': + optional: true + + '@esbuild/darwin-x64@0.28.0': + optional: true + + '@esbuild/freebsd-arm64@0.28.0': + optional: true + + '@esbuild/freebsd-x64@0.28.0': + optional: true + + '@esbuild/linux-arm64@0.28.0': + optional: true + + '@esbuild/linux-arm@0.28.0': + optional: true + + '@esbuild/linux-ia32@0.28.0': + optional: true + + '@esbuild/linux-loong64@0.28.0': + optional: true + + '@esbuild/linux-mips64el@0.28.0': + optional: true + + '@esbuild/linux-ppc64@0.28.0': + optional: true + + '@esbuild/linux-riscv64@0.28.0': + optional: true + + '@esbuild/linux-s390x@0.28.0': + optional: true + + '@esbuild/linux-x64@0.28.0': + optional: true + + '@esbuild/netbsd-arm64@0.28.0': + optional: true + + '@esbuild/netbsd-x64@0.28.0': + optional: true + + '@esbuild/openbsd-arm64@0.28.0': + optional: true + + '@esbuild/openbsd-x64@0.28.0': + optional: true + + '@esbuild/openharmony-arm64@0.28.0': + optional: true + + '@esbuild/sunos-x64@0.28.0': + optional: true + + '@esbuild/win32-arm64@0.28.0': + optional: true + + '@esbuild/win32-ia32@0.28.0': + optional: true + + '@esbuild/win32-x64@0.28.0': + optional: true + + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/react-dom@2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@floating-ui/dom': 1.7.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + + '@floating-ui/utils@0.2.11': {} + + '@img/colour@1.1.0': + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.10.0 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@keyv/bigmap@1.3.1(keyv@5.6.0)': + dependencies: + hashery: 1.5.1 + hookified: 1.15.1 + keyv: 5.6.0 + + '@keyv/serialize@1.1.1': {} + + '@next/env@16.2.6': {} + + '@next/swc-darwin-arm64@16.2.6': + optional: true + + '@next/swc-darwin-x64@16.2.6': + optional: true + + '@next/swc-linux-arm64-gnu@16.2.6': + optional: true + + '@next/swc-linux-arm64-musl@16.2.6': + optional: true + + '@next/swc-linux-x64-gnu@16.2.6': + optional: true + + '@next/swc-linux-x64-musl@16.2.6': + optional: true + + '@next/swc-win32-arm64-msvc@16.2.6': + optional: true + + '@next/swc-win32-x64-msvc@16.2.6': + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@parcel/watcher-android-arm64@2.5.6': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.6': + optional: true + + '@parcel/watcher-darwin-x64@2.5.6': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.6': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.6': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.6': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.6': + optional: true + + '@parcel/watcher-win32-arm64@2.5.6': + optional: true + + '@parcel/watcher-win32-ia32@2.5.6': + optional: true + + '@parcel/watcher-win32-x64@2.5.6': + optional: true + + '@parcel/watcher@2.5.6': + dependencies: + detect-libc: 2.1.2 + is-glob: 4.0.3 + node-addon-api: 7.1.1 + picomatch: 4.0.4 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.6 + '@parcel/watcher-darwin-arm64': 2.5.6 + '@parcel/watcher-darwin-x64': 2.5.6 + '@parcel/watcher-freebsd-x64': 2.5.6 + '@parcel/watcher-linux-arm-glibc': 2.5.6 + '@parcel/watcher-linux-arm-musl': 2.5.6 + '@parcel/watcher-linux-arm64-glibc': 2.5.6 + '@parcel/watcher-linux-arm64-musl': 2.5.6 + '@parcel/watcher-linux-x64-glibc': 2.5.6 + '@parcel/watcher-linux-x64-musl': 2.5.6 + '@parcel/watcher-win32-arm64': 2.5.6 + '@parcel/watcher-win32-ia32': 2.5.6 + '@parcel/watcher-win32-x64': 2.5.6 + optional: true + + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.15)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-context@1.1.2(@types/react@19.2.15)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + aria-hidden: 1.2.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.2(@types/react@19.2.15)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-direction@1.1.1(@types/react@19.2.15)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.15)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-form@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-id@1.1.1(@types/react@19.2.15)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-label@2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + aria-hidden: 1.2.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.2(@types/react@19.2.15)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-menubar@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-one-time-password-field@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-password-toggle-field@0.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + aria-hidden: 1.2.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.2(@types/react@19.2.15)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/rect': 1.1.1 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-progress@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + aria-hidden: 1.2.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.2(@types/react@19.2.15)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-separator@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-slider@1.3.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-slot@1.2.3(@types/react@19.2.15)(react@19.2.6)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-toast@1.2.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-toolbar@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.15)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.15)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.15)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.15)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.2.15)(react@19.2.6)': + dependencies: + react: 19.2.6 + use-sync-external-store: 1.6.0(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.15)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.15)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.15)(react@19.2.6)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.15)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/rect@1.1.1': {} + + '@sindresorhus/merge-streams@4.0.0': {} + + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + + '@tiptap/core@3.23.6(@tiptap/pm@3.23.6)': + dependencies: + '@tiptap/pm': 3.23.6 + + '@tiptap/extension-blockquote@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))': + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + + '@tiptap/extension-bold@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))': + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + + '@tiptap/extension-bubble-menu@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6)': + dependencies: + '@floating-ui/dom': 1.7.6 + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + '@tiptap/pm': 3.23.6 + optional: true + + '@tiptap/extension-bullet-list@3.23.6(@tiptap/extension-list@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6))': + dependencies: + '@tiptap/extension-list': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6) + + '@tiptap/extension-code-block@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6)': + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + '@tiptap/pm': 3.23.6 + + '@tiptap/extension-code@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))': + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + + '@tiptap/extension-document@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))': + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + + '@tiptap/extension-dropcursor@3.23.6(@tiptap/extensions@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6))': + dependencies: + '@tiptap/extensions': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6) + + '@tiptap/extension-floating-menu@3.23.6(@floating-ui/dom@1.7.6)(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6)': + dependencies: + '@floating-ui/dom': 1.7.6 + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + '@tiptap/pm': 3.23.6 + optional: true + + '@tiptap/extension-gapcursor@3.23.6(@tiptap/extensions@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6))': + dependencies: + '@tiptap/extensions': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6) + + '@tiptap/extension-hard-break@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))': + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + + '@tiptap/extension-heading@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))': + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + + '@tiptap/extension-highlight@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))': + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + + '@tiptap/extension-horizontal-rule@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6)': + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + '@tiptap/pm': 3.23.6 + + '@tiptap/extension-italic@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))': + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + + '@tiptap/extension-link@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6)': + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + '@tiptap/pm': 3.23.6 + linkifyjs: 4.3.3 + + '@tiptap/extension-list-item@3.23.6(@tiptap/extension-list@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6))': + dependencies: + '@tiptap/extension-list': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6) + + '@tiptap/extension-list-keymap@3.23.6(@tiptap/extension-list@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6))': + dependencies: + '@tiptap/extension-list': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6) + + '@tiptap/extension-list@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6)': + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + '@tiptap/pm': 3.23.6 + + '@tiptap/extension-ordered-list@3.23.6(@tiptap/extension-list@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6))': + dependencies: + '@tiptap/extension-list': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6) + + '@tiptap/extension-paragraph@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))': + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + + '@tiptap/extension-strike@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))': + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + + '@tiptap/extension-text@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))': + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + + '@tiptap/extension-underline@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))': + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + + '@tiptap/extensions@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6)': + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + '@tiptap/pm': 3.23.6 + + '@tiptap/pm@3.23.6': + dependencies: + prosemirror-changeset: 2.4.1 + prosemirror-commands: 1.7.1 + prosemirror-dropcursor: 1.8.2 + prosemirror-gapcursor: 1.4.1 + prosemirror-history: 1.5.0 + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.7 + prosemirror-schema-list: 1.5.1 + prosemirror-state: 1.4.4 + prosemirror-tables: 1.8.5 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.41.8 + + '@tiptap/react@3.23.6(@floating-ui/dom@1.7.6)(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + '@tiptap/pm': 3.23.6 + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + '@types/use-sync-external-store': 0.0.6 + fast-equals: 5.4.0 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + use-sync-external-store: 1.6.0(react@19.2.6) + optionalDependencies: + '@tiptap/extension-bubble-menu': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6) + '@tiptap/extension-floating-menu': 3.23.6(@floating-ui/dom@1.7.6)(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6) + transitivePeerDependencies: + - '@floating-ui/dom' + + '@tiptap/starter-kit@3.23.6': + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + '@tiptap/extension-blockquote': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6)) + '@tiptap/extension-bold': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6)) + '@tiptap/extension-bullet-list': 3.23.6(@tiptap/extension-list@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6)) + '@tiptap/extension-code': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6)) + '@tiptap/extension-code-block': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6) + '@tiptap/extension-document': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6)) + '@tiptap/extension-dropcursor': 3.23.6(@tiptap/extensions@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6)) + '@tiptap/extension-gapcursor': 3.23.6(@tiptap/extensions@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6)) + '@tiptap/extension-hard-break': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6)) + '@tiptap/extension-heading': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6)) + '@tiptap/extension-horizontal-rule': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6) + '@tiptap/extension-italic': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6)) + '@tiptap/extension-link': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6) + '@tiptap/extension-list': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6) + '@tiptap/extension-list-item': 3.23.6(@tiptap/extension-list@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6)) + '@tiptap/extension-list-keymap': 3.23.6(@tiptap/extension-list@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6)) + '@tiptap/extension-ordered-list': 3.23.6(@tiptap/extension-list@3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6)) + '@tiptap/extension-paragraph': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6)) + '@tiptap/extension-strike': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6)) + '@tiptap/extension-text': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6)) + '@tiptap/extension-underline': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6)) + '@tiptap/extensions': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6) + '@tiptap/pm': 3.23.6 + + '@tldraw/driver@5.0.1(@floating-ui/dom@1.7.6)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@tldraw/editor': 5.0.1(@floating-ui/dom@1.7.6)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@tldraw/utils': 5.0.1 + transitivePeerDependencies: + - '@floating-ui/dom' + - '@types/react' + - '@types/react-dom' + - react + - react-dom + + '@tldraw/editor@5.0.1(@floating-ui/dom@1.7.6)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + '@tiptap/pm': 3.23.6 + '@tiptap/react': 3.23.6(@floating-ui/dom@1.7.6)(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@tldraw/state': 5.0.1 + '@tldraw/state-react': 5.0.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@tldraw/store': 5.0.1(react@19.2.6) + '@tldraw/tlschema': 5.0.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@tldraw/utils': 5.0.1 + '@tldraw/validate': 5.0.1 + classnames: 2.5.1 + eventemitter3: 4.0.7 + idb: 7.1.1 + is-plain-object: 5.0.0 + rbush: 3.0.1 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + transitivePeerDependencies: + - '@floating-ui/dom' + - '@types/react' + - '@types/react-dom' + + '@tldraw/state-react@5.0.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@tldraw/state': 5.0.1 + '@tldraw/utils': 5.0.1 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + + '@tldraw/state@5.0.1': + dependencies: + '@tldraw/utils': 5.0.1 + + '@tldraw/store@5.0.1(react@19.2.6)': + dependencies: + '@tldraw/state': 5.0.1 + '@tldraw/utils': 5.0.1 + react: 19.2.6 + + '@tldraw/tlschema@5.0.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@tldraw/state': 5.0.1 + '@tldraw/store': 5.0.1(react@19.2.6) + '@tldraw/utils': 5.0.1 + '@tldraw/validate': 5.0.1 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + + '@tldraw/utils@5.0.1': + dependencies: + jittered-fractional-indexing: 1.0.1 + lodash.isequal: 4.5.0 + lodash.isequalwith: 4.4.0 + lodash.throttle: 4.1.1 + lodash.uniq: 4.5.0 + + '@tldraw/validate@5.0.1': + dependencies: + '@tldraw/utils': 5.0.1 + + '@types/node@25.9.1': + dependencies: + undici-types: 7.24.6 + + '@types/react-dom@19.2.3(@types/react@19.2.15)': + dependencies: + '@types/react': 19.2.15 + + '@types/react@19.2.15': + dependencies: + csstype: 3.2.3 + + '@types/use-sync-external-store@0.0.6': {} + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + astral-regex@2.0.0: {} + + babel-plugin-react-compiler@1.0.0: + dependencies: + '@babel/types': 7.29.7 + + baseline-browser-mapping@2.10.32: {} + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + cacheable@2.3.5: + dependencies: + '@cacheable/memory': 2.0.9 + '@cacheable/utils': 2.4.1 + hookified: 1.15.1 + keyv: 5.6.0 + qified: 0.10.1 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001793: {} + + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + + classnames@2.5.1: {} + + client-only@0.0.1: {} + + clsx@2.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colord@2.9.3: {} + + cosmiconfig@9.0.1(typescript@5.9.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.9.3 + + css-functions-list@3.3.3: {} + + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + detect-libc@2.1.2: + optional: true + + detect-node-es@1.1.0: {} + + emoji-regex@8.0.0: {} + + env-paths@2.2.1: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + esbuild@0.28.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.0 + '@esbuild/android-arm': 0.28.0 + '@esbuild/android-arm64': 0.28.0 + '@esbuild/android-x64': 0.28.0 + '@esbuild/darwin-arm64': 0.28.0 + '@esbuild/darwin-x64': 0.28.0 + '@esbuild/freebsd-arm64': 0.28.0 + '@esbuild/freebsd-x64': 0.28.0 + '@esbuild/linux-arm': 0.28.0 + '@esbuild/linux-arm64': 0.28.0 + '@esbuild/linux-ia32': 0.28.0 + '@esbuild/linux-loong64': 0.28.0 + '@esbuild/linux-mips64el': 0.28.0 + '@esbuild/linux-ppc64': 0.28.0 + '@esbuild/linux-riscv64': 0.28.0 + '@esbuild/linux-s390x': 0.28.0 + '@esbuild/linux-x64': 0.28.0 + '@esbuild/netbsd-arm64': 0.28.0 + '@esbuild/netbsd-x64': 0.28.0 + '@esbuild/openbsd-arm64': 0.28.0 + '@esbuild/openbsd-x64': 0.28.0 + '@esbuild/openharmony-arm64': 0.28.0 + '@esbuild/sunos-x64': 0.28.0 + '@esbuild/win32-arm64': 0.28.0 + '@esbuild/win32-ia32': 0.28.0 + '@esbuild/win32-x64': 0.28.0 + + eventemitter3@4.0.7: {} + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-equals@5.4.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-uri@3.1.2: {} + + fastest-levenshtein@1.0.16: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + file-entry-cache@11.1.3: + dependencies: + flat-cache: 6.1.22 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + flat-cache@6.1.22: + dependencies: + cacheable: 2.3.5 + flatted: 3.4.2 + hookified: 1.15.1 + + flatted@3.4.2: {} + + fractional-indexing@3.2.0: {} + + framer-motion@12.40.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + motion-dom: 12.40.0 + motion-utils: 12.39.0 + tslib: 2.8.1 + optionalDependencies: + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + + fsevents@2.3.3: + optional: true + + get-east-asian-width@1.6.0: {} + + get-nonce@1.0.1: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + global-modules@2.0.0: + dependencies: + global-prefix: 3.0.0 + + global-prefix@3.0.0: + dependencies: + ini: 1.3.8 + kind-of: 6.0.3 + which: 1.3.1 + + globby@16.2.0: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + fast-glob: 3.3.3 + ignore: 7.0.5 + is-path-inside: 4.0.0 + slash: 5.1.0 + unicorn-magic: 0.4.0 + + globjoin@0.1.4: {} + + has-flag@5.0.1: {} + + hashery@1.5.1: + dependencies: + hookified: 1.15.1 + + hookified@1.15.1: {} + + hookified@2.2.0: {} + + html-tags@5.1.0: {} + + husky@9.1.7: {} + + idb@7.1.1: {} + + ignore@7.0.5: {} + + immutable@5.1.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-meta-resolve@4.2.0: {} + + ini@1.3.8: {} + + is-arrayish@0.2.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-path-inside@4.0.0: {} + + is-plain-object@5.0.0: {} + + isexe@2.0.0: {} + + jittered-fractional-indexing@1.0.1: + dependencies: + fractional-indexing: 3.2.0 + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@1.0.0: {} + + keyv@5.6.0: + dependencies: + '@keyv/serialize': 1.1.1 + + kind-of@6.0.3: {} + + known-css-properties@0.37.0: {} + + lines-and-columns@1.2.4: {} + + linkifyjs@4.3.3: {} + + lodash.isequal@4.5.0: {} + + lodash.isequalwith@4.4.0: {} + + lodash.throttle@4.1.1: {} + + lodash.truncate@4.4.2: {} + + lodash.uniq@4.5.0: {} + + lz-string@1.5.0: {} + + mathml-tag-names@4.0.0: {} + + mdn-data@2.27.1: {} + + meow@14.1.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + motion-dom@12.40.0: + dependencies: + motion-utils: 12.39.0 + + motion-utils@12.39.0: {} + + motion@12.40.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + framer-motion: 12.40.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + tslib: 2.8.1 + optionalDependencies: + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + + ms@2.1.3: {} + + nanoid@3.3.12: {} + + next@16.2.6(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.100.0): + dependencies: + '@next/env': 16.2.6 + '@swc/helpers': 0.5.15 + baseline-browser-mapping: 2.10.32 + caniuse-lite: 1.0.30001793 + postcss: 8.4.31 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + styled-jsx: 5.1.6(react@19.2.6) + optionalDependencies: + '@next/swc-darwin-arm64': 16.2.6 + '@next/swc-darwin-x64': 16.2.6 + '@next/swc-linux-arm64-gnu': 16.2.6 + '@next/swc-linux-arm64-musl': 16.2.6 + '@next/swc-linux-x64-gnu': 16.2.6 + '@next/swc-linux-x64-musl': 16.2.6 + '@next/swc-win32-arm64-msvc': 16.2.6 + '@next/swc-win32-x64-msvc': 16.2.6 + babel-plugin-react-compiler: 1.0.0 + sass: 1.100.0 + sharp: 0.34.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + node-addon-api@7.1.1: + optional: true + + normalize-path@3.0.0: {} + + orderedmap@2.1.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.7 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: + optional: true + + postcss-media-query-parser@0.2.3: {} + + postcss-resolve-nested-selector@0.1.6: {} + + postcss-safe-parser@7.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + + postcss-scss@4.0.9(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + + postcss-selector-parser@7.1.1: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-sorting@10.0.0(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + + postcss-value-parser@4.2.0: {} + + postcss@8.4.31: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier-linter-helpers@1.0.1: + dependencies: + fast-diff: 1.3.0 + + prettier@3.8.3: {} + + prosemirror-changeset@2.4.1: + dependencies: + prosemirror-transform: 1.12.0 + + prosemirror-commands@1.7.1: + dependencies: + prosemirror-model: 1.25.7 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + prosemirror-dropcursor@1.8.2: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.41.8 + + prosemirror-gapcursor@1.4.1: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.7 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.8 + + prosemirror-history@1.5.0: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.41.8 + rope-sequence: 1.3.4 + + prosemirror-keymap@1.2.3: + dependencies: + prosemirror-state: 1.4.4 + w3c-keyname: 2.2.8 + + prosemirror-model@1.25.7: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-list@1.5.1: + dependencies: + prosemirror-model: 1.25.7 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + prosemirror-state@1.4.4: + dependencies: + prosemirror-model: 1.25.7 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.41.8 + + prosemirror-tables@1.8.5: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.7 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.41.8 + + prosemirror-transform@1.12.0: + dependencies: + prosemirror-model: 1.25.7 + + prosemirror-view@1.41.8: + dependencies: + prosemirror-model: 1.25.7 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + qified@0.10.1: + dependencies: + hookified: 2.2.0 + + queue-microtask@1.2.3: {} + + quickselect@2.0.0: {} + + radix-ui@1.4.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-accessible-icon': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-alert-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-aspect-ratio': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-avatar': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context-menu': 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-form': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-hover-card': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-menubar': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-one-time-password-field': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-password-toggle-field': 0.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popover': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-progress': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-radio-group': 1.3.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-select': 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slider': 1.3.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-switch': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toast': 1.2.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toolbar': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + rbush@3.0.1: + dependencies: + quickselect: 2.0.0 + + react-dom@19.2.6(react@19.2.6): + dependencies: + react: 19.2.6 + scheduler: 0.27.0 + + react-remove-scroll-bar@2.3.8(@types/react@19.2.15)(react@19.2.6): + dependencies: + react: 19.2.6 + react-style-singleton: 2.2.3(@types/react@19.2.15)(react@19.2.6) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.15 + + react-remove-scroll@2.7.2(@types/react@19.2.15)(react@19.2.6): + dependencies: + react: 19.2.6 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.15)(react@19.2.6) + react-style-singleton: 2.2.3(@types/react@19.2.15)(react@19.2.6) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.15)(react@19.2.6) + use-sidecar: 1.1.3(@types/react@19.2.15)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + + react-style-singleton@2.2.3(@types/react@19.2.15)(react@19.2.6): + dependencies: + get-nonce: 1.0.1 + react: 19.2.6 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.15 + + react@19.2.6: {} + + readdirp@5.0.0: {} + + require-from-string@2.0.2: {} + + resolve-from@4.0.0: {} + + reusify@1.1.0: {} + + rope-sequence@1.3.4: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + sass@1.100.0: + dependencies: + chokidar: 5.0.0 + immutable: 5.1.5 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.6 + + scheduler@0.27.0: {} + + semver@7.8.1: + optional: true + + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.1 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + optional: true + + signal-exit@4.1.0: {} + + slash@5.1.0: {} + + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + + source-map-js@1.2.1: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@8.2.1: + dependencies: + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + styled-jsx@5.1.6(react@19.2.6): + dependencies: + client-only: 0.0.1 + react: 19.2.6 + + stylelint-config-css-modules@4.6.0(stylelint@17.12.0(typescript@5.9.3)): + dependencies: + stylelint: 17.12.0(typescript@5.9.3) + optionalDependencies: + stylelint-scss: 7.1.1(stylelint@17.12.0(typescript@5.9.3)) + + stylelint-config-recess-order@7.7.0(stylelint-order@8.1.1(stylelint@17.12.0(typescript@5.9.3)))(stylelint@17.12.0(typescript@5.9.3)): + dependencies: + stylelint: 17.12.0(typescript@5.9.3) + stylelint-order: 8.1.1(stylelint@17.12.0(typescript@5.9.3)) + + stylelint-config-recommended-scss@17.0.1(postcss@8.5.15)(stylelint@17.12.0(typescript@5.9.3)): + dependencies: + postcss-scss: 4.0.9(postcss@8.5.15) + stylelint: 17.12.0(typescript@5.9.3) + stylelint-config-recommended: 18.0.0(stylelint@17.12.0(typescript@5.9.3)) + stylelint-scss: 7.1.1(stylelint@17.12.0(typescript@5.9.3)) + optionalDependencies: + postcss: 8.5.15 + + stylelint-config-recommended@18.0.0(stylelint@17.12.0(typescript@5.9.3)): + dependencies: + stylelint: 17.12.0(typescript@5.9.3) + + stylelint-config-standard-scss@17.0.0(postcss@8.5.15)(stylelint@17.12.0(typescript@5.9.3)): + dependencies: + stylelint: 17.12.0(typescript@5.9.3) + stylelint-config-recommended-scss: 17.0.1(postcss@8.5.15)(stylelint@17.12.0(typescript@5.9.3)) + stylelint-config-standard: 40.0.0(stylelint@17.12.0(typescript@5.9.3)) + optionalDependencies: + postcss: 8.5.15 + + stylelint-config-standard@40.0.0(stylelint@17.12.0(typescript@5.9.3)): + dependencies: + stylelint: 17.12.0(typescript@5.9.3) + stylelint-config-recommended: 18.0.0(stylelint@17.12.0(typescript@5.9.3)) + + stylelint-declaration-block-no-ignored-properties@3.0.0(stylelint@17.12.0(typescript@5.9.3)): + dependencies: + stylelint: 17.12.0(typescript@5.9.3) + + stylelint-order@8.1.1(stylelint@17.12.0(typescript@5.9.3)): + dependencies: + postcss: 8.5.15 + postcss-sorting: 10.0.0(postcss@8.5.15) + stylelint: 17.12.0(typescript@5.9.3) + + stylelint-prettier@5.0.3(prettier@3.8.3)(stylelint@17.12.0(typescript@5.9.3)): + dependencies: + prettier: 3.8.3 + prettier-linter-helpers: 1.0.1 + stylelint: 17.12.0(typescript@5.9.3) + + stylelint-scss@7.1.1(stylelint@17.12.0(typescript@5.9.3)): + dependencies: + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-syntax-patches-for-csstree': 1.1.4(css-tree@3.2.1) + '@csstools/css-tokenizer': 4.0.0 + css-tree: 3.2.1 + is-plain-object: 5.0.0 + known-css-properties: 0.37.0 + postcss-media-query-parser: 0.2.3 + postcss-resolve-nested-selector: 0.1.6 + postcss-selector-parser: 7.1.1 + postcss-value-parser: 4.2.0 + stylelint: 17.12.0(typescript@5.9.3) + + stylelint@17.12.0(typescript@5.9.3): + dependencies: + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-syntax-patches-for-csstree': 1.1.4(css-tree@3.2.1) + '@csstools/css-tokenizer': 4.0.0 + '@csstools/media-query-list-parser': 5.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/selector-resolve-nested': 4.0.0(postcss-selector-parser@7.1.1) + '@csstools/selector-specificity': 6.0.0(postcss-selector-parser@7.1.1) + colord: 2.9.3 + cosmiconfig: 9.0.1(typescript@5.9.3) + css-functions-list: 3.3.3 + css-tree: 3.2.1 + debug: 4.4.3 + fast-glob: 3.3.3 + fastest-levenshtein: 1.0.16 + file-entry-cache: 11.1.3 + global-modules: 2.0.0 + globby: 16.2.0 + globjoin: 0.1.4 + html-tags: 5.1.0 + ignore: 7.0.5 + import-meta-resolve: 4.2.0 + mathml-tag-names: 4.0.0 + meow: 14.1.0 + micromatch: 4.0.8 + normalize-path: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.15 + postcss-safe-parser: 7.0.1(postcss@8.5.15) + postcss-selector-parser: 7.1.1 + postcss-value-parser: 4.2.0 + string-width: 8.2.1 + supports-hyperlinks: 4.4.0 + svg-tags: 1.0.0 + table: 6.9.0 + write-file-atomic: 7.0.1 + transitivePeerDependencies: + - supports-color + - typescript + + supports-color@10.2.2: {} + + supports-hyperlinks@4.4.0: + dependencies: + has-flag: 5.0.1 + supports-color: 10.2.2 + + svg-tags@1.0.0: {} + + table@6.9.0: + dependencies: + ajv: 8.20.0 + lodash.truncate: 4.4.2 + slice-ansi: 4.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + tldraw@5.0.1(@floating-ui/dom@1.7.6)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) + '@tiptap/extension-code': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6)) + '@tiptap/extension-highlight': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6)) + '@tiptap/extension-list': 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6) + '@tiptap/pm': 3.23.6 + '@tiptap/react': 3.23.6(@floating-ui/dom@1.7.6)(@tiptap/core@3.23.6(@tiptap/pm@3.23.6))(@tiptap/pm@3.23.6)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@tiptap/starter-kit': 3.23.6 + '@tldraw/driver': 5.0.1(@floating-ui/dom@1.7.6)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@tldraw/editor': 5.0.1(@floating-ui/dom@1.7.6)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@tldraw/store': 5.0.1(react@19.2.6) + classnames: 2.5.1 + idb: 7.1.1 + lz-string: 1.5.0 + radix-ui: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + transitivePeerDependencies: + - '@floating-ui/dom' + - '@types/react' + - '@types/react-dom' + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tslib@2.8.1: {} + + tsx@4.22.3: + dependencies: + esbuild: 0.28.0 + optionalDependencies: + fsevents: 2.3.3 + + typescript@5.9.3: {} + + undici-types@7.24.6: {} + + unicorn-magic@0.4.0: {} + + use-callback-ref@1.3.3(@types/react@19.2.15)(react@19.2.6): + dependencies: + react: 19.2.6 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.15 + + use-sidecar@1.1.3(@types/react@19.2.15)(react@19.2.6): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.6 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.15 + + use-sync-external-store@1.6.0(react@19.2.6): + dependencies: + react: 19.2.6 + + util-deprecate@1.0.2: {} + + w3c-keyname@2.2.8: {} + + which@1.3.1: + dependencies: + isexe: 2.0.0 + + write-file-atomic@7.0.1: + dependencies: + signal-exit: 4.1.0 diff --git a/dataweaver/pnpm-workspace.yaml b/dataweaver/pnpm-workspace.yaml new file mode 100644 index 00000000..3ff5faaa --- /dev/null +++ b/dataweaver/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - "apps/*" + - "packages/*" diff --git a/dataweaver/stylelint.config.mjs b/dataweaver/stylelint.config.mjs new file mode 100644 index 00000000..c07d594d --- /dev/null +++ b/dataweaver/stylelint.config.mjs @@ -0,0 +1,50 @@ +/** @type {import('stylelint').Config} */ + +const STYLELINT_CONFIG = { + extends: [ + // Base rules. Note: This installs and uses 'postcss-scs' out of the box + 'stylelint-config-standard-scss', + + // CSS modules syntax support + 'stylelint-config-css-modules', + + // Enforce a logical order for CSS properties + 'stylelint-config-recess-order', + ], + + plugins: [ + // Block ignored properties in CSS + 'stylelint-declaration-block-no-ignored-properties', + + // Prettier integration + 'stylelint-prettier', + ], + + rules: { + // Enable prettier (inherits config from .prettierrc.json) + 'prettier/prettier': true, + + // Enable plugin + 'plugin/declaration-block-no-ignored-properties': true, + + // Ignore select properties in the redundant longhand properties rule + 'declaration-block-no-redundant-longhand-properties': [ + true, + { ignoreShorthands: ['/flex/', '/grid/'] }, + ], + + // Prevent redundant nesting selectors + 'scss/selector-no-redundant-nesting-selector': true, + + // Ignore position declarations inside '@supports' rules - it was causing + // issues that IMO weren't valid concerns + 'no-invalid-position-declaration': [true, { ignoreAtRules: ['supports'] }], + + // Disable some default rules + 'selector-id-pattern': null, + 'selector-class-pattern': null, + 'scss/operator-no-newline-after': null, + }, +}; + +export default STYLELINT_CONFIG; From 0e35e0d148a8e0bf46af62c0956b151131910f18 Mon Sep 17 00:00:00 2001 From: Paulo Ferreira Jorge Date: Thu, 28 May 2026 17:26:04 +0100 Subject: [PATCH 02/20] Drop Husky --- .gitignore | 5 ++++- dataweaver/.gitignore | 3 --- dataweaver/.husky/pre-push | 1 - dataweaver/package.json | 3 +-- dataweaver/pnpm-lock.yaml | 10 ---------- 5 files changed, 5 insertions(+), 17 deletions(-) delete mode 100755 dataweaver/.husky/pre-push diff --git a/.gitignore b/.gitignore index 1cf5edd1..8bf50bfa 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,7 @@ bigtable_automation/terraform/source/bt_automation_go_source.zip # Local GCF generated config.textproto -gcf/custom/local/config.textproto \ No newline at end of file +gcf/custom/local/config.textproto + +# Misc. +.DS_Store \ No newline at end of file diff --git a/dataweaver/.gitignore b/dataweaver/.gitignore index 81d1f062..71e1cecd 100644 --- a/dataweaver/.gitignore +++ b/dataweaver/.gitignore @@ -4,8 +4,5 @@ node_modules/ # Environment variables .env.local -# Misc -.DS_Store - # Typescript *.tsbuildinfo diff --git a/dataweaver/.husky/pre-push b/dataweaver/.husky/pre-push deleted file mode 100755 index 6ca46a75..00000000 --- a/dataweaver/.husky/pre-push +++ /dev/null @@ -1 +0,0 @@ -pnpm run lint diff --git a/dataweaver/package.json b/dataweaver/package.json index 49d8fb6e..bfb09911 100644 --- a/dataweaver/package.json +++ b/dataweaver/package.json @@ -25,8 +25,7 @@ "stylelint-declaration-block-no-ignored-properties": "^3.0.0", "stylelint-prettier": "^5.0.3", "stylelint": "^17.5.0", - "typescript": "5.9.3", - "husky": "^9.1.7" + "typescript": "5.9.3" }, "packageManager": "pnpm@10.20.0", "engines": { diff --git a/dataweaver/pnpm-lock.yaml b/dataweaver/pnpm-lock.yaml index 252f1ad0..841b0609 100644 --- a/dataweaver/pnpm-lock.yaml +++ b/dataweaver/pnpm-lock.yaml @@ -11,9 +11,6 @@ importers: '@biomejs/biome': specifier: 2.4.7 version: 2.4.7 - husky: - specifier: ^9.1.7 - version: 9.1.7 prettier: specifier: ^3.8.1 version: 3.8.3 @@ -1810,11 +1807,6 @@ packages: resolution: {integrity: sha512-n6l5uca7/y5joxZ3LUePhzmBFUJ+U2YWzhMa8XUTecSeSlQiZdF5XAd/Q3/WUl0VsXgUwWi8I7CNIwdI5WN1SQ==} engines: {node: '>=20.10'} - husky@9.1.7: - resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} - engines: {node: '>=18'} - hasBin: true - idb@7.1.1: resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} @@ -4037,8 +4029,6 @@ snapshots: html-tags@5.1.0: {} - husky@9.1.7: {} - idb@7.1.1: {} ignore@7.0.5: {} From 773af07e793f6c8ba9a20173b1ecf61119aa4a28 Mon Sep 17 00:00:00 2001 From: Paulo Ferreira Jorge Date: Thu, 28 May 2026 18:06:46 +0100 Subject: [PATCH 03/20] Implement Feedback --- dataweaver/apps/web/package.json | 2 +- .../apps/web/src/components/scopes/page-home.tsx | 9 ++++++++- .../apps/web/src/configs/environment-client.ts | 1 + dataweaver/apps/web/src/functions/match-media.ts | 2 ++ dataweaver/apps/web/src/hooks/use-match-media.ts | 8 +++++--- dataweaver/apps/web/src/styles/tokens/generated.ts | 10 ++++++---- dataweaver/apps/web/tsconfig.json | 12 +++++++----- dataweaver/packages/tokens/src/generate.ts | 10 ++++++++-- 8 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 dataweaver/apps/web/src/configs/environment-client.ts diff --git a/dataweaver/apps/web/package.json b/dataweaver/apps/web/package.json index ecb46945..92e11aa5 100644 --- a/dataweaver/apps/web/package.json +++ b/dataweaver/apps/web/package.json @@ -6,7 +6,7 @@ "build": "next build", "preview": "next start", "lint:type-check": "tsc --noEmit", - "generate:tokens": "pnpm --filter @package/tokens generate -- \"$PWD/src/styles/tokens\"" + "generate:tokens": "pnpm --filter @package/tokens generate -- \"../../apps/web/src/styles/tokens\"" }, "dependencies": { "clsx": "^2.1.1", diff --git a/dataweaver/apps/web/src/components/scopes/page-home.tsx b/dataweaver/apps/web/src/components/scopes/page-home.tsx index ddec8c68..dc292803 100644 --- a/dataweaver/apps/web/src/components/scopes/page-home.tsx +++ b/dataweaver/apps/web/src/components/scopes/page-home.tsx @@ -1,4 +1,11 @@ -import { Tldraw } from './tldraw'; +'use client'; + +import dynamic from 'next/dynamic'; + +const Tldraw = dynamic( + () => import('./tldraw').then((module) => module.Tldraw), + { ssr: false }, +); export const PageHome = () => { return ; diff --git a/dataweaver/apps/web/src/configs/environment-client.ts b/dataweaver/apps/web/src/configs/environment-client.ts new file mode 100644 index 00000000..6ec5664f --- /dev/null +++ b/dataweaver/apps/web/src/configs/environment-client.ts @@ -0,0 +1 @@ +export const IS_BROWSER = typeof window !== 'undefined'; diff --git a/dataweaver/apps/web/src/functions/match-media.ts b/dataweaver/apps/web/src/functions/match-media.ts index e945a133..5a8bef6d 100644 --- a/dataweaver/apps/web/src/functions/match-media.ts +++ b/dataweaver/apps/web/src/functions/match-media.ts @@ -1,3 +1,4 @@ +import { IS_BROWSER } from '~/configs/environment-client'; import { BREAKPOINTS, type Target } from '~/styles/breakpoints'; /** List of media query keys with their custom query. */ @@ -65,6 +66,7 @@ export const getMatchMediaQuery = (key: MatchMediaKey) => { * @param key - The shorthand key to match for (see `getMatchMediaQuery`). */ export const getMatchMediaMatch = (key: MatchMediaKey) => { + if (!IS_BROWSER) return false; const query = getMatchMediaQuery(key); return window.matchMedia(query).matches; }; diff --git a/dataweaver/apps/web/src/hooks/use-match-media.ts b/dataweaver/apps/web/src/hooks/use-match-media.ts index 0db3ddd3..9b344a54 100644 --- a/dataweaver/apps/web/src/hooks/use-match-media.ts +++ b/dataweaver/apps/web/src/hooks/use-match-media.ts @@ -34,7 +34,7 @@ const QUERIES = new Map(); /** * Stateful hook that uses the matchMedia API. * - * @param query - The query to match for. + * @param queryKey - The query key to match for. * @param config - _Optional_ configuration object. * @param config.defaultValue - _Optional_ fallback value that is returned in * SSR and first match of any unique query. Defaults to `false`. @@ -63,9 +63,11 @@ export const useMatchMedia = < ? null : never, >( - query: MatchMediaKey, + queryKey: MatchMediaKey, config: Partial = {}, ) => { + const query = getMatchMediaQuery(queryKey); + const getExistingMatch = useCallback(() => { const matchedQuery = QUERIES.get(query); @@ -162,7 +164,7 @@ export const useMatchMedia = < // On mount we only checked if there was an existing match. So here we // set matches again in case this is the first of this query - const matches = addListener(getMatchMediaQuery(query), listener); + const matches = addListener(query, listener); setMatches(matches); return () => removeListener(query, listener); diff --git a/dataweaver/apps/web/src/styles/tokens/generated.ts b/dataweaver/apps/web/src/styles/tokens/generated.ts index b873c3e4..74326604 100644 --- a/dataweaver/apps/web/src/styles/tokens/generated.ts +++ b/dataweaver/apps/web/src/styles/tokens/generated.ts @@ -1,5 +1,7 @@ // ⚠️ AUTO-GENERATED — Do not edit. Modify the JSON tokens in 'packages/tokens/src/' and run 'pnpm generate:tokens' +import type { BezierDefinition } from "motion/react"; + export const BREAKPOINT_TABLET = 768; export const BREAKPOINT_LAPTOP = 1280; export const BREAKPOINT_DESKTOP = 1600; @@ -14,7 +16,7 @@ export const COLORS = { "blue-light": "247, 252, 255", } as const; -export const EASE_LINEAR = [0, 0, 1, 1]; -export const EASE_OUT = [0.26, 1, 0.48, 1]; -export const EASE_IN = [0.52, 0, 0.74, 0]; -export const EASE_IN_OUT = [0.76, 0, 0.24, 1]; +export const EASE_LINEAR: BezierDefinition = [0, 0, 1, 1]; +export const EASE_OUT: BezierDefinition = [0.26, 1, 0.48, 1]; +export const EASE_IN: BezierDefinition = [0.52, 0, 0.74, 0]; +export const EASE_IN_OUT: BezierDefinition = [0.76, 0, 0.24, 1]; diff --git a/dataweaver/apps/web/tsconfig.json b/dataweaver/apps/web/tsconfig.json index ec658c11..c121d14d 100644 --- a/dataweaver/apps/web/tsconfig.json +++ b/dataweaver/apps/web/tsconfig.json @@ -1,13 +1,11 @@ { "$schema": "https://json.schemastore.org/tsconfig", + // Options largely based on - https://www.totaltypescript.com/tsconfig-cheat-sheet "compilerOptions": { // Next.js plugin for TypeScript - "plugins": [ - { - "name": "next" - } - ], + "plugins": [{ "name": "next" }], + // Base Options "allowJs": true, "esModuleInterop": true, @@ -19,6 +17,7 @@ "verbatimModuleSyntax": true, "jsx": "react-jsx", "incremental": false, + // Strictness "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, @@ -27,12 +26,15 @@ "noUnusedLocals": true, "noUnusedParameters": true, "strict": true, + // TS -> JS Compilation "module": "preserve", "moduleResolution": "bundler", "noEmit": true, + // For the DOM "lib": ["es2022", "dom", "dom.iterable"], + // Aliases "paths": { "~/public/*": ["./public/*"], diff --git a/dataweaver/packages/tokens/src/generate.ts b/dataweaver/packages/tokens/src/generate.ts index 454d88af..97a4ef2d 100644 --- a/dataweaver/packages/tokens/src/generate.ts +++ b/dataweaver/packages/tokens/src/generate.ts @@ -58,7 +58,11 @@ const generateScss = (): string => { }; const generateTypeScript = (): string => { - const lines: string[] = [DO_NOT_EDIT_COMMENT_BANNER]; + const lines: string[] = [ + DO_NOT_EDIT_COMMENT_BANNER, + '', + 'import type { BezierDefinition } from "motion/react";', + ]; // Variables lines.push(''); @@ -82,7 +86,9 @@ const generateTypeScript = (): string => { for (const [name, values] of Object.entries(EASES)) { const constName = `EASE_${name.toUpperCase().replaceAll('-', '_')}`; const formattedValue = values.join(', '); - lines.push(`export const ${constName} = [${formattedValue}];`); + lines.push( + `export const ${constName}: BezierDefinition = [${formattedValue}];`, + ); } lines.push(''); From b62782c4c619b4d52cdd660e6dea6afe6e021db6 Mon Sep 17 00:00:00 2001 From: Paulo Ferreira Jorge Date: Thu, 28 May 2026 18:22:53 +0100 Subject: [PATCH 04/20] Update README --- dataweaver/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dataweaver/README.md b/dataweaver/README.md index 347a02f2..963662e3 100644 --- a/dataweaver/README.md +++ b/dataweaver/README.md @@ -6,7 +6,7 @@ Monorepo for Dataweaver. Managed with [pnpm workspaces](https://pnpm.io/workspac ``` apps/ - web/ Next.js 15 app (App Router) + web/ Next.js app (App Router) packages/ tokens/ Design tokens (JSON → generated .scss / .ts) ``` From 8ea093761dfaa857a17baa5f270c1ff67f0acfc7 Mon Sep 17 00:00:00 2001 From: Paulo Ferreira Jorge Date: Fri, 29 May 2026 11:50:08 +0100 Subject: [PATCH 05/20] Address Feedback --- dataweaver/README.md | 12 +- dataweaver/apps/web/next.config.ts | 65 +++-- dataweaver/apps/web/package.json | 49 ++-- dataweaver/apps/web/src/app/layout.tsx | 26 +- .../web/src/components/primitives/link.tsx | 87 +++--- .../web/src/components/scopes/page-404.tsx | 10 +- .../web/src/components/scopes/page-home.tsx | 6 +- .../src/components/scopes/tldraw.module.scss | 7 +- .../apps/web/src/components/scopes/tldraw.tsx | 2 +- .../apps/web/src/functions/match-media.ts | 50 ++-- .../apps/web/src/hooks/use-match-media.ts | 276 +++++++++--------- dataweaver/apps/web/src/styles/breakpoints.ts | 24 +- dataweaver/apps/web/src/styles/core.scss | 1 + dataweaver/apps/web/src/styles/includes.scss | 1 - .../styles/includes/_breakpoints.module.scss | 2 +- .../src/styles/tokens/_generated.module.scss | 21 -- .../apps/web/src/styles/tokens/generated.ts | 22 -- dataweaver/apps/web/tsconfig.json | 88 +++--- dataweaver/biome.json | 201 +++++++------ dataweaver/package.json | 74 ++--- dataweaver/packages/tokens/dist/_tokens.scss | 5 + dataweaver/packages/tokens/dist/tokens.css | 11 + dataweaver/packages/tokens/dist/tokens.ts | 22 ++ dataweaver/packages/tokens/package.json | 25 +- dataweaver/packages/tokens/src/colors.json | 14 +- dataweaver/packages/tokens/src/eases.json | 8 +- dataweaver/packages/tokens/src/generate.ts | 163 +++++------ dataweaver/packages/tokens/src/variables.json | 6 +- dataweaver/packages/tokens/tsconfig.json | 6 +- dataweaver/packages/tsconfig.base.json | 26 +- dataweaver/stylelint.config.mjs | 105 ++++--- 31 files changed, 736 insertions(+), 679 deletions(-) delete mode 100644 dataweaver/apps/web/src/styles/tokens/_generated.module.scss delete mode 100644 dataweaver/apps/web/src/styles/tokens/generated.ts create mode 100644 dataweaver/packages/tokens/dist/_tokens.scss create mode 100644 dataweaver/packages/tokens/dist/tokens.css create mode 100644 dataweaver/packages/tokens/dist/tokens.ts diff --git a/dataweaver/README.md b/dataweaver/README.md index 963662e3..dc65c797 100644 --- a/dataweaver/README.md +++ b/dataweaver/README.md @@ -8,7 +8,7 @@ Monorepo for Dataweaver. Managed with [pnpm workspaces](https://pnpm.io/workspac apps/ web/ Next.js app (App Router) packages/ - tokens/ Design tokens (JSON → generated .scss / .ts) + tokens/ Design tokens (JSON → generated tokens.css / .scss / .ts, consumed via @package/tokens) ``` ## Installation @@ -36,7 +36,15 @@ pnpm i | `pnpm dev` | Run all apps in dev mode | | `pnpm build` | Build all apps | | `pnpm preview` | Serve the built apps | -| `pnpm generate:tokens` | Regenerate `_generated.module.scss` + `generated.ts` from `packages/tokens/src/*.json` | +| `pnpm generate:tokens` | Regenerate `packages/tokens/dist/` (`tokens.css` + `_tokens.scss` + `tokens.ts`) from `packages/tokens/src/*.json` | + +## Code style + +TypeScript follows the [Google TypeScript Style Guide](https://google.github.io/styleguide/tsguide.html), enforced as closely as possible through Biome (see `biome.json`). + +CSS and SCSS follow the [Google HTML/CSS Style Guide](https://google.github.io/styleguide/htmlcssguide.html), enforced through Stylelint for `.scss` (see `stylelint.config.mjs`) and Biome for plain `.css`. + +Where a guideline can't be linted automatically, please follow it by convention. ## Linting diff --git a/dataweaver/apps/web/next.config.ts b/dataweaver/apps/web/next.config.ts index 1832de41..f58e54e1 100644 --- a/dataweaver/apps/web/next.config.ts +++ b/dataweaver/apps/web/next.config.ts @@ -1,38 +1,45 @@ +import { join } from 'node:path'; import type { NextConfig } from 'next'; const NEXT_CONFIG: NextConfig = { - reactStrictMode: true, - reactCompiler: true, - devIndicators: false, + reactStrictMode: true, + reactCompiler: true, + devIndicators: false, - // Auto-inject the shared Sass includes (breakpoints, helpers, keyframes, - // typography, z-indices, tokens) into every Sass entry module. Includes - // only '@forwards' definitions, so this emits no CSS; it removes the need to - // repeat `@use "~/styles/includes" as *;` at the top of each component file - sassOptions: { - additionalData: '@use "~/styles/includes" as *;\n', - }, + // Transpile the workspace design-tokens package, which ships TypeScript + // (`@package/tokens` -> dist/tokens.ts) rather than pre-built JS. + transpilePackages: ['@package/tokens'], - compiler: { - removeConsole: - // Only allow the following console calls in production - process.env.NODE_ENV === 'production' - ? { exclude: ['warn', 'error', 'info'] } - : false, - }, + // Auto-inject the shared Sass includes (breakpoints, helpers, keyframes, + // typography, z-indices) into every Sass entry module. Includes only + // '@forwards' definitions, so this emits no CSS; it removes the need to + // repeat `@use "~/styles/includes" as *;` at the top of each component file. + // `loadPaths` lets Sass resolve `@use "@package/tokens/..."` from node_modules. + sassOptions: { + additionalData: '@use "~/styles/includes" as *;\n', + loadPaths: [join(import.meta.dirname, 'node_modules')], + }, - images: { - imageSizes: [384], - deviceSizes: [768, 1280, 1600], - qualities: [90], - remotePatterns: [ - { - protocol: 'https', - hostname: 'fonts.googleapis.com', - pathname: '/**', - }, - ], - }, + compiler: { + removeConsole: + // Only allow the following console calls in production + process.env.NODE_ENV === 'production' + ? { exclude: ['warn', 'error', 'info'] } + : false, + }, + + images: { + imageSizes: [384], + deviceSizes: [768, 1280, 1600], + qualities: [90], + remotePatterns: [ + { + protocol: 'https', + hostname: 'fonts.googleapis.com', + pathname: '/**', + }, + ], + }, }; export default NEXT_CONFIG; diff --git a/dataweaver/apps/web/package.json b/dataweaver/apps/web/package.json index 92e11aa5..a292ada4 100644 --- a/dataweaver/apps/web/package.json +++ b/dataweaver/apps/web/package.json @@ -1,27 +1,26 @@ { - "name": "@app/web", - "type": "module", - "scripts": { - "dev": "next dev", - "build": "next build", - "preview": "next start", - "lint:type-check": "tsc --noEmit", - "generate:tokens": "pnpm --filter @package/tokens generate -- \"../../apps/web/src/styles/tokens\"" - }, - "dependencies": { - "clsx": "^2.1.1", - "motion": "^12.38.0", - "next": "^16.2.6", - "react-dom": "^19.2.6", - "react": "^19.2.6", - "tldraw": "^5.0.1" - }, - "devDependencies": { - "@package/tokens": "workspace:tokens", - "@types/node": "^25.9.1", - "@types/react-dom": "^19.2.3", - "@types/react": "^19.2.15", - "babel-plugin-react-compiler": "^1.0.0", - "sass": "^1.97.3" - } + "name": "@app/web", + "type": "module", + "scripts": { + "dev": "next dev", + "build": "next build", + "preview": "next start", + "lint:type-check": "tsc --noEmit" + }, + "dependencies": { + "clsx": "^2.1.1", + "motion": "^12.38.0", + "next": "^16.2.6", + "react-dom": "^19.2.6", + "react": "^19.2.6", + "tldraw": "^5.0.1" + }, + "devDependencies": { + "@package/tokens": "workspace:tokens", + "@types/node": "^25.9.1", + "@types/react-dom": "^19.2.3", + "@types/react": "^19.2.15", + "babel-plugin-react-compiler": "^1.0.0", + "sass": "^1.97.3" + } } diff --git a/dataweaver/apps/web/src/app/layout.tsx b/dataweaver/apps/web/src/app/layout.tsx index b028d782..7117f49b 100644 --- a/dataweaver/apps/web/src/app/layout.tsx +++ b/dataweaver/apps/web/src/app/layout.tsx @@ -3,20 +3,20 @@ import '~/styles/core.scss'; import { domMax, LazyMotion } from 'motion/react'; import type { ReactNode } from 'react'; -type Props = { - children: ReactNode; -}; +interface LayoutProps { + children: ReactNode; +} -const RootLayout = ({ children }: Props) => { - return ( - - - -
{children}
-
- - - ); +const RootLayout = ({ children }: LayoutProps) => { + return ( + + + +
{children}
+
+ + + ); }; export default RootLayout; diff --git a/dataweaver/apps/web/src/components/primitives/link.tsx b/dataweaver/apps/web/src/components/primitives/link.tsx index fd96355c..ca7500b0 100755 --- a/dataweaver/apps/web/src/components/primitives/link.tsx +++ b/dataweaver/apps/web/src/components/primitives/link.tsx @@ -1,52 +1,53 @@ -import type { LinkProps } from 'next/link'; +import type { LinkProps as NextLinkProps } from 'next/link'; import NextLink from 'next/link'; import type { ComponentPropsWithRef, Ref } from 'react'; -type Props = { - ref?: Ref; - href: string | undefined; +interface LinkProps + extends ComponentPropsWithRef<'a'>, + Pick { + ref?: Ref; + href: string | undefined; - /** @default false */ - isExternal?: boolean; -} & ComponentPropsWithRef<'a'> & - Pick; + /** @default false */ + isExternal?: boolean; +} export const Link = ({ - ref, - href, - isExternal = false, - replace, - children, - ...rest -}: Props) => { - // If theres no 'href' render this without it (this is equivalent to a span) - if (!href) { - return ( - - {children} - - ); - } + ref, + href, + isExternal = false, + replace, + children, + ...rest +}: LinkProps) => { + // If theres no 'href' render this without it (this is equivalent to a span) + if (!href) { + return ( + + {children} + + ); + } - // If external link, render standard anchor (open in new tab) - if (isExternal) { - return ( - - {children} - - ); - } + // If external link, render standard anchor (open in new tab) + if (isExternal) { + return ( + + {children} + + ); + } - // If internal link, render Next/Link - return ( - - {children} - - ); + // If internal link, render Next/Link + return ( + + {children} + + ); }; diff --git a/dataweaver/apps/web/src/components/scopes/page-404.tsx b/dataweaver/apps/web/src/components/scopes/page-404.tsx index 792fef58..c981b33c 100644 --- a/dataweaver/apps/web/src/components/scopes/page-404.tsx +++ b/dataweaver/apps/web/src/components/scopes/page-404.tsx @@ -1,7 +1,7 @@ export const Page404 = () => { - return ( -
-

404!

-
- ); + return ( +
+

404!

+
+ ); }; diff --git a/dataweaver/apps/web/src/components/scopes/page-home.tsx b/dataweaver/apps/web/src/components/scopes/page-home.tsx index dc292803..b715a35f 100644 --- a/dataweaver/apps/web/src/components/scopes/page-home.tsx +++ b/dataweaver/apps/web/src/components/scopes/page-home.tsx @@ -3,10 +3,10 @@ import dynamic from 'next/dynamic'; const Tldraw = dynamic( - () => import('./tldraw').then((module) => module.Tldraw), - { ssr: false }, + () => import('./tldraw').then((module) => module.Tldraw), + { ssr: false }, ); export const PageHome = () => { - return ; + return ; }; diff --git a/dataweaver/apps/web/src/components/scopes/tldraw.module.scss b/dataweaver/apps/web/src/components/scopes/tldraw.module.scss index e7e16f55..01f44296 100644 --- a/dataweaver/apps/web/src/components/scopes/tldraw.module.scss +++ b/dataweaver/apps/web/src/components/scopes/tldraw.module.scss @@ -8,8 +8,11 @@ // Ensure all nested layers appear from a base of 0 and not above app content z-index: $z-index-content; - background-color: $color-blue-light; - background-image: radial-gradient($color-grey-light 1px, transparent 1px); + background-color: rgb(var(--color-blue-light)); + background-image: radial-gradient( + rgb(var(--color-grey-light)) 1px, + transparent 1px + ); background-position: center center; background-size: 20px 20px; } diff --git a/dataweaver/apps/web/src/components/scopes/tldraw.tsx b/dataweaver/apps/web/src/components/scopes/tldraw.tsx index bcb7d18b..33e381c8 100644 --- a/dataweaver/apps/web/src/components/scopes/tldraw.tsx +++ b/dataweaver/apps/web/src/components/scopes/tldraw.tsx @@ -4,5 +4,5 @@ import { Tldraw as PrimitiveTldraw } from 'tldraw'; import s from './tldraw.module.scss'; export const Tldraw = () => { - return ; + return ; }; diff --git a/dataweaver/apps/web/src/functions/match-media.ts b/dataweaver/apps/web/src/functions/match-media.ts index 5a8bef6d..7fa8e4dc 100644 --- a/dataweaver/apps/web/src/functions/match-media.ts +++ b/dataweaver/apps/web/src/functions/match-media.ts @@ -3,10 +3,10 @@ import { BREAKPOINTS, type Target } from '~/styles/breakpoints'; /** List of media query keys with their custom query. */ const MISC_QUERIES = { - coarse: '(hover: none) and (pointer: coarse)', - fine: '(hover: hover) and (pointer: fine)', - 'prefers-motion': '(prefers-reduced-motion: no-preference)', - 'prefers-reduced-motion': '(prefers-reduced-motion: reduce)', + coarse: '(hover: none) and (pointer: coarse)', + fine: '(hover: hover) and (pointer: fine)', + 'prefers-motion': '(prefers-reduced-motion: no-preference)', + 'prefers-reduced-motion': '(prefers-reduced-motion: reduce)', }; export type MatchMediaKey = Target | keyof typeof MISC_QUERIES | (string & {}); @@ -37,27 +37,27 @@ export type MatchMediaKey = Target | keyof typeof MISC_QUERIES | (string & {}); * laptop `min-width` breakpoint. */ export const getMatchMediaQuery = (key: MatchMediaKey) => { - // If the key is mobile, return 'max-width' query instead. Note: We use the - // tablet breakpoint as the 'max-width' for mobile, as we don't have a mobile - // breakpoint + the negative 1 ensures 'max-width' fires at same point as - // 'min-width' breakpoint - if (key === 'mobile') { - const tabletMinWidth = BREAKPOINTS.tablet.minWidth; - return `(max-width: ${tabletMinWidth - 1}px)`; - } + // If the key is mobile, return 'max-width' query instead. Note: We use the + // tablet breakpoint as the 'max-width' for mobile, as we don't have a mobile + // breakpoint + the negative 1 ensures 'max-width' fires at same point as + // 'min-width' breakpoint + if (key === 'mobile') { + const tabletMinWidth = BREAKPOINTS.tablet.minWidth; + return `(max-width: ${tabletMinWidth - 1}px)`; + } - // If the key is a valid breakpoint, return the min-width query - if (key in BREAKPOINTS) { - const minWidth = BREAKPOINTS[key as Target]?.minWidth; - if (minWidth) return `(min-width: ${minWidth}px)`; - } + // If the key is a valid breakpoint, return the min-width query + if (key in BREAKPOINTS) { + const minWidth = BREAKPOINTS[key as Target]?.minWidth; + if (minWidth) return `(min-width: ${minWidth}px)`; + } - // If key in misc queries - return the corresponding query - if (key in MISC_QUERIES) { - return MISC_QUERIES[key as keyof typeof MISC_QUERIES]; - } + // If key in misc queries - return the corresponding query + if (key in MISC_QUERIES) { + return MISC_QUERIES[key as keyof typeof MISC_QUERIES]; + } - return key; + return key; }; /** @@ -66,7 +66,7 @@ export const getMatchMediaQuery = (key: MatchMediaKey) => { * @param key - The shorthand key to match for (see `getMatchMediaQuery`). */ export const getMatchMediaMatch = (key: MatchMediaKey) => { - if (!IS_BROWSER) return false; - const query = getMatchMediaQuery(key); - return window.matchMedia(query).matches; + if (!IS_BROWSER) return false; + const query = getMatchMediaQuery(key); + return window.matchMedia(query).matches; }; diff --git a/dataweaver/apps/web/src/hooks/use-match-media.ts b/dataweaver/apps/web/src/hooks/use-match-media.ts index 9b344a54..031d69e9 100644 --- a/dataweaver/apps/web/src/hooks/use-match-media.ts +++ b/dataweaver/apps/web/src/hooks/use-match-media.ts @@ -1,33 +1,33 @@ import { useIsomorphicLayoutEffect } from 'motion/react'; import { useCallback, useState } from 'react'; import { - getMatchMediaQuery, - type MatchMediaKey, + getMatchMediaQuery, + type MatchMediaKey, } from '~/functions/match-media'; -type Config = { - /** - * Fallback value that is returned in SSR and first match of any unique query. - * - * @default false - */ - defaultValue?: boolean | null; - - /** - * Whether or not to listen to events. - * - * @default true - */ - isEnabled?: boolean; -}; +interface Config { + /** + * Fallback value that is returned in SSR and first match of any unique query. + * + * @default false + */ + defaultValue?: boolean | null; + + /** + * Whether or not to listen to events. + * + * @default true + */ + isEnabled?: boolean; +} type EventHandler = (event: MediaQueryListEvent) => void; -type Query = { - matchMedia: MediaQueryList; - existingListeners: EventHandler[]; - eventHandler: EventHandler; -}; +interface Query { + matchMedia: MediaQueryList; + existingListeners: EventHandler[]; + eventHandler: EventHandler; +} const QUERIES = new Map(); @@ -54,122 +54,122 @@ const QUERIES = new Map(); * `defaultValue` as fallback. */ export const useMatchMedia = < - TConfig extends Config = Config, - TDefaultValue extends - | TConfig['defaultValue'] - | never = TConfig['defaultValue'] extends undefined - ? undefined - : TConfig['defaultValue'] extends null - ? null - : never, + TConfig extends Config = Config, + TDefaultValue extends + | TConfig['defaultValue'] + | never = TConfig['defaultValue'] extends undefined + ? undefined + : TConfig['defaultValue'] extends null + ? null + : never, >( - queryKey: MatchMediaKey, - config: Partial = {}, + queryKey: MatchMediaKey, + config: Partial = {}, ) => { - const query = getMatchMediaQuery(queryKey); - - const getExistingMatch = useCallback(() => { - const matchedQuery = QUERIES.get(query); - - // If query already exists, return its matched value - if (matchedQuery) return matchedQuery.matchMedia.matches; - - // Else, return config default value if it exists or fallback (false) - return config.defaultValue === undefined ? false : config.defaultValue; - }, [config.defaultValue, query]); - - const createEventHandler = useCallback( - (query: string) => (event: MediaQueryListEvent) => { - const matchedQuery = QUERIES.get(query); - - if (matchedQuery) { - matchedQuery.existingListeners.forEach((listener) => { - listener(event); - }); - } - }, - [], - ); - - const addListener = useCallback( - (query: string, listener: EventHandler) => { - const matchedQuery = QUERIES.get(query); - - // If query already exists, add this new listener to existing array - if (matchedQuery) { - matchedQuery.existingListeners.push(listener); - return matchedQuery.matchMedia.matches; - } - - // Else, first query, so create it... - const newQuery = { - matchMedia: window.matchMedia(query), - existingListeners: [listener], - eventHandler: createEventHandler(query), - }; - QUERIES.set(query, newQuery); - - // Listen to changes with fallback - const currentMatchMedia = newQuery.matchMedia; - if (!currentMatchMedia.addEventListener) { - currentMatchMedia.addListener(newQuery.eventHandler); - } else { - currentMatchMedia.addEventListener('change', newQuery.eventHandler); - } - - return newQuery.matchMedia.matches; - }, - [createEventHandler], - ); - - const removeListener = useCallback( - (query: string, listener: EventHandler) => { - const matchedQuery = QUERIES.get(query); - - // If this matches, filter out this listener from existing array - if (matchedQuery) { - matchedQuery.existingListeners = matchedQuery.existingListeners.filter( - (l) => l !== listener, - ); - } - - // Ignore unsubscribe below if theres any more existing listeners - if (!matchedQuery || matchedQuery.existingListeners.length > 0) return; - - // Unsubscribe from changes with fallback - const currentMatchMedia = matchedQuery.matchMedia; - if (!currentMatchMedia.removeEventListener) { - currentMatchMedia.removeListener(matchedQuery.eventHandler); - } else { - currentMatchMedia.removeEventListener( - 'change', - matchedQuery.eventHandler, - ); - } - - QUERIES.delete(query); - }, - [], - ); - - const [matches, setMatches] = useState( - getExistingMatch, - ); - - useIsomorphicLayoutEffect(() => { - if (config.isEnabled ?? true) { - const listener = (event: MediaQueryListEvent) => { - setMatches(event.matches); - }; - - // On mount we only checked if there was an existing match. So here we - // set matches again in case this is the first of this query - const matches = addListener(query, listener); - setMatches(matches); - - return () => removeListener(query, listener); - } - }, [config.isEnabled, query, addListener, removeListener]); - - return matches; + const query = getMatchMediaQuery(queryKey); + + const getExistingMatch = useCallback(() => { + const matchedQuery = QUERIES.get(query); + + // If query already exists, return its matched value + if (matchedQuery) return matchedQuery.matchMedia.matches; + + // Else, return config default value if it exists or fallback (false) + return config.defaultValue === undefined ? false : config.defaultValue; + }, [config.defaultValue, query]); + + const createEventHandler = useCallback( + (query: string) => (event: MediaQueryListEvent) => { + const matchedQuery = QUERIES.get(query); + + if (matchedQuery) { + matchedQuery.existingListeners.forEach((listener) => { + listener(event); + }); + } + }, + [], + ); + + const addListener = useCallback( + (query: string, listener: EventHandler) => { + const matchedQuery = QUERIES.get(query); + + // If query already exists, add this new listener to existing array + if (matchedQuery) { + matchedQuery.existingListeners.push(listener); + return matchedQuery.matchMedia.matches; + } + + // Else, first query, so create it... + const newQuery = { + matchMedia: window.matchMedia(query), + existingListeners: [listener], + eventHandler: createEventHandler(query), + }; + QUERIES.set(query, newQuery); + + // Listen to changes with fallback + const currentMatchMedia = newQuery.matchMedia; + if (!currentMatchMedia.addEventListener) { + currentMatchMedia.addListener(newQuery.eventHandler); + } else { + currentMatchMedia.addEventListener('change', newQuery.eventHandler); + } + + return newQuery.matchMedia.matches; + }, + [createEventHandler], + ); + + const removeListener = useCallback( + (query: string, listener: EventHandler) => { + const matchedQuery = QUERIES.get(query); + + // If this matches, filter out this listener from existing array + if (matchedQuery) { + matchedQuery.existingListeners = matchedQuery.existingListeners.filter( + (l) => l !== listener, + ); + } + + // Ignore unsubscribe below if theres any more existing listeners + if (!matchedQuery || matchedQuery.existingListeners.length > 0) return; + + // Unsubscribe from changes with fallback + const currentMatchMedia = matchedQuery.matchMedia; + if (!currentMatchMedia.removeEventListener) { + currentMatchMedia.removeListener(matchedQuery.eventHandler); + } else { + currentMatchMedia.removeEventListener( + 'change', + matchedQuery.eventHandler, + ); + } + + QUERIES.delete(query); + }, + [], + ); + + const [matches, setMatches] = useState( + getExistingMatch, + ); + + useIsomorphicLayoutEffect(() => { + if (config.isEnabled ?? true) { + const listener = (event: MediaQueryListEvent) => { + setMatches(event.matches); + }; + + // On mount we only checked if there was an existing match. So here we + // set matches again in case this is the first of this query + const matches = addListener(query, listener); + setMatches(matches); + + return () => removeListener(query, listener); + } + }, [config.isEnabled, query, addListener, removeListener]); + + return matches; }; diff --git a/dataweaver/apps/web/src/styles/breakpoints.ts b/dataweaver/apps/web/src/styles/breakpoints.ts index db55a55f..6ca075ea 100644 --- a/dataweaver/apps/web/src/styles/breakpoints.ts +++ b/dataweaver/apps/web/src/styles/breakpoints.ts @@ -1,22 +1,22 @@ import { - BREAKPOINT_DESKTOP, - BREAKPOINT_LAPTOP, - BREAKPOINT_TABLET, -} from './tokens/generated'; + BREAKPOINT_DESKTOP, + BREAKPOINT_LAPTOP, + BREAKPOINT_TABLET, +} from '@package/tokens/ts'; export type Target = 'mobile' | 'tablet' | 'laptop' | 'desktop'; -type Breakpoint = { - minWidth: number; -}; +interface Breakpoint { + minWidth: number; +} type Breakpoints = { - [key in Target]: key extends 'mobile' ? undefined : Breakpoint; + [Key in Target]: Key extends 'mobile' ? undefined : Breakpoint; }; export const BREAKPOINTS: Breakpoints = { - mobile: undefined, - tablet: { minWidth: BREAKPOINT_TABLET }, - laptop: { minWidth: BREAKPOINT_LAPTOP }, - desktop: { minWidth: BREAKPOINT_DESKTOP }, + mobile: undefined, + tablet: { minWidth: BREAKPOINT_TABLET }, + laptop: { minWidth: BREAKPOINT_LAPTOP }, + desktop: { minWidth: BREAKPOINT_DESKTOP }, } as const; diff --git a/dataweaver/apps/web/src/styles/core.scss b/dataweaver/apps/web/src/styles/core.scss index b49db7c0..c7bab162 100644 --- a/dataweaver/apps/web/src/styles/core.scss +++ b/dataweaver/apps/web/src/styles/core.scss @@ -1,3 +1,4 @@ @use "./core/reset"; @use "./core/root"; @use "./core/base"; +@import "@package/tokens/css" layer(root); diff --git a/dataweaver/apps/web/src/styles/includes.scss b/dataweaver/apps/web/src/styles/includes.scss index 49311761..b503153f 100644 --- a/dataweaver/apps/web/src/styles/includes.scss +++ b/dataweaver/apps/web/src/styles/includes.scss @@ -1,4 +1,3 @@ @forward "./includes/breakpoints.module"; @forward "./includes/helpers.module"; @forward "./includes/z-indices.module"; -@forward "./tokens/generated.module"; diff --git a/dataweaver/apps/web/src/styles/includes/_breakpoints.module.scss b/dataweaver/apps/web/src/styles/includes/_breakpoints.module.scss index 239298d0..bd5825ef 100644 --- a/dataweaver/apps/web/src/styles/includes/_breakpoints.module.scss +++ b/dataweaver/apps/web/src/styles/includes/_breakpoints.module.scss @@ -1,5 +1,5 @@ @use "sass:map"; -@use "../tokens/generated.module" as tokens; +@use "@package/tokens/scss" as tokens; $-breakpoints: ( "tablet": tokens.$breakpoint-tablet, diff --git a/dataweaver/apps/web/src/styles/tokens/_generated.module.scss b/dataweaver/apps/web/src/styles/tokens/_generated.module.scss deleted file mode 100644 index e5e32781..00000000 --- a/dataweaver/apps/web/src/styles/tokens/_generated.module.scss +++ /dev/null @@ -1,21 +0,0 @@ -// ⚠️ AUTO-GENERATED — Do not edit. Modify the JSON tokens in 'packages/tokens/src/' and run 'pnpm generate:tokens' - -// Variables -$breakpoint-tablet: 768; -$breakpoint-laptop: 1280; -$breakpoint-desktop: 1600; - -// Colors -$color-black: rgb(0 0 0); -$color-grey-almost-black: rgb(32 33 36); -$color-grey-muted: rgb(68 71 70); -$color-grey-light: rgb(227 231 237); -$color-white: rgb(255 255 255); -$color-blue: rgb(11 87 208); -$color-blue-light: rgb(247 252 255); - -// Eases -$ease-linear: cubic-bezier(0, 0, 1, 1); -$ease-out: cubic-bezier(0.26, 1, 0.48, 1); -$ease-in: cubic-bezier(0.52, 0, 0.74, 0); -$ease-in-out: cubic-bezier(0.76, 0, 0.24, 1); diff --git a/dataweaver/apps/web/src/styles/tokens/generated.ts b/dataweaver/apps/web/src/styles/tokens/generated.ts deleted file mode 100644 index 74326604..00000000 --- a/dataweaver/apps/web/src/styles/tokens/generated.ts +++ /dev/null @@ -1,22 +0,0 @@ -// ⚠️ AUTO-GENERATED — Do not edit. Modify the JSON tokens in 'packages/tokens/src/' and run 'pnpm generate:tokens' - -import type { BezierDefinition } from "motion/react"; - -export const BREAKPOINT_TABLET = 768; -export const BREAKPOINT_LAPTOP = 1280; -export const BREAKPOINT_DESKTOP = 1600; - -export const COLORS = { - black: "0, 0, 0", - "grey-almost-black": "32, 33, 36", - "grey-muted": "68, 71, 70", - "grey-light": "227, 231, 237", - white: "255, 255, 255", - blue: "11, 87, 208", - "blue-light": "247, 252, 255", -} as const; - -export const EASE_LINEAR: BezierDefinition = [0, 0, 1, 1]; -export const EASE_OUT: BezierDefinition = [0.26, 1, 0.48, 1]; -export const EASE_IN: BezierDefinition = [0.52, 0, 0.74, 0]; -export const EASE_IN_OUT: BezierDefinition = [0.76, 0, 0.24, 1]; diff --git a/dataweaver/apps/web/tsconfig.json b/dataweaver/apps/web/tsconfig.json index c121d14d..6b70599b 100644 --- a/dataweaver/apps/web/tsconfig.json +++ b/dataweaver/apps/web/tsconfig.json @@ -1,52 +1,52 @@ { - "$schema": "https://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", - // Options largely based on - https://www.totaltypescript.com/tsconfig-cheat-sheet - "compilerOptions": { - // Next.js plugin for TypeScript - "plugins": [{ "name": "next" }], + // Options largely based on - https://www.totaltypescript.com/tsconfig-cheat-sheet + "compilerOptions": { + // Next.js plugin for TypeScript + "plugins": [{ "name": "next" }], - // Base Options - "allowJs": true, - "esModuleInterop": true, - "isolatedModules": true, - "moduleDetection": "force", - "resolveJsonModule": true, - "skipLibCheck": true, - "target": "es2022", - "verbatimModuleSyntax": true, - "jsx": "react-jsx", - "incremental": false, + // Base Options + "allowJs": true, + "esModuleInterop": true, + "isolatedModules": true, + "moduleDetection": "force", + "resolveJsonModule": true, + "skipLibCheck": true, + "target": "es2022", + "verbatimModuleSyntax": true, + "jsx": "react-jsx", + "incremental": false, - // Strictness - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "noImplicitOverride": true, - "noUncheckedIndexedAccess": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "strict": true, + // Strictness + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "strict": true, - // TS -> JS Compilation - "module": "preserve", - "moduleResolution": "bundler", - "noEmit": true, + // TS -> JS Compilation + "module": "preserve", + "moduleResolution": "bundler", + "noEmit": true, - // For the DOM - "lib": ["es2022", "dom", "dom.iterable"], + // For the DOM + "lib": ["es2022", "dom", "dom.iterable"], - // Aliases - "paths": { - "~/public/*": ["./public/*"], - "~/*": ["./src/*"] - } - }, - "include": [ - "**/*.ts", - "**/*.tsx", - "next-env.d.ts", - ".next/types/**/*.ts", - ".next/dev/types/**/*.ts" - ], - "exclude": ["node_modules", ".next"] + // Aliases + "paths": { + "~/public/*": ["./public/*"], + "~/*": ["./src/*"] + } + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "next-env.d.ts", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": ["node_modules", ".next"] } diff --git a/dataweaver/biome.json b/dataweaver/biome.json index c909fa49..cb63c7fc 100644 --- a/dataweaver/biome.json +++ b/dataweaver/biome.json @@ -1,91 +1,128 @@ { - "$schema": "https://biomejs.dev/schemas/2.4.7/schema.json", + "$schema": "https://biomejs.dev/schemas/2.4.7/schema.json", - "vcs": { - "enabled": true, - "clientKind": "git", - "useIgnoreFile": true - }, + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, - "files": { - "ignoreUnknown": true - }, + "files": { + "ignoreUnknown": true + }, - "formatter": { - "enabled": true, - "indentStyle": "tab" - }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2 + }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "suspicious": { - "noConfusingVoidType": "off", - "noArrayIndexKey": "off", - "noConsole": { - "level": "error", - "options": { "allow": ["warn", "error", "info"] } - } - }, - "correctness": { - "noUnusedImports": "error", - "noUnusedFunctionParameters": "error", - "useExhaustiveDependencies": "error" - }, - "complexity": { - "useOptionalChain": "off" - }, - "style": { - "noDefaultExport": "error", - "useImportType": "error", - "useConsistentTypeDefinitions": { - "level": "error", - "options": { "style": "type" } - }, - "noRestrictedImports": { - "level": "error", - "options": { - "patterns": [ - { - "group": ["../**"], - "message": "Relative imports are not allowed." - } - ] - } - } - }, - "a11y": { - "noStaticElementInteractions": "off" - } - } - }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noArrayIndexKey": "off", + "noConfusingVoidType": "off", + "noConstEnum": "error", + "noDebugger": "error", + "noDoubleEquals": "error", + "noEmptyBlockStatements": "error", + "noExplicitAny": "error", + "noFallthroughSwitchClause": "error", + "noUnusedExpressions": "error", + "noVar": "error", + "noWith": "error", + "useGuardForIn": "error", + "noConsole": { + "level": "error", + "options": { "allow": ["warn", "error", "info"] } + } + }, + "correctness": { + "noUnusedFunctionParameters": "error", + "noUnusedImports": "error", + "useExhaustiveDependencies": "error" + }, + "complexity": { + "useArrowFunction": "error", + "useOptionalChain": "off" + }, + "style": { + "noCommonJs": "error", + "noDefaultExport": "error", + "noNamespace": "error", + "noNonNullAssertion": "error", + "useArrayLiterals": "error", + "useConst": "error", + "useExportType": "error", + "useForOf": "error", + "useImportType": "error", + "useSingleVarDeclarator": "error", + "useTemplate": "error", + "useThrowNewError": "error", + "useThrowOnlyError": "error", + "useNamingConvention": { + "level": "error", + "options": { "strictCase": false } + }, + "useConsistentArrayType": { + "level": "error", + "options": { "syntax": "shorthand" } + }, + "useConsistentTypeDefinitions": { + "level": "error", + "options": { "style": "interface" } + }, + "noRestrictedImports": { + "level": "error", + "options": { + "patterns": [ + { + "group": ["../**"], + "message": "Relative imports are not allowed." + } + ] + } + } + }, + "a11y": { + "noStaticElementInteractions": "off" + } + } + }, - "assist": { - "enabled": true, - "actions": { - "source": { - "organizeImports": "on" - } - } - }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + }, - "javascript": { - "formatter": { - "quoteStyle": "single" - } - }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + }, - "overrides": [ - { - "includes": ["**/*.scss"], - "formatter": { "enabled": false }, - "linter": { "enabled": false }, - "assist": { "enabled": false } - }, - { - "includes": ["**/src/app/**", "**/*.config.*"], - "linter": { "rules": { "style": { "noDefaultExport": "off" } } } - } - ] + "css": { + "formatter": { + "quoteStyle": "single" + } + }, + + "overrides": [ + { + "includes": ["**/*.scss"], + "formatter": { "enabled": false }, + "linter": { "enabled": false }, + "assist": { "enabled": false } + }, + { + "includes": ["**/src/app/**", "**/*.config.*"], + "linter": { "rules": { "style": { "noDefaultExport": "off" } } } + } + ] } diff --git a/dataweaver/package.json b/dataweaver/package.json index bfb09911..489408d8 100644 --- a/dataweaver/package.json +++ b/dataweaver/package.json @@ -1,39 +1,39 @@ { - "name": "dataweaver", - "description": "Monorepo for Dataweaver", - "type": "module", - "version": "1.0.0", - "private": true, - "scripts": { - "dev": "pnpm -r --parallel dev", - "build": "pnpm -r build", - "preview": "pnpm -r --parallel preview", - "lint": "pnpm -r run lint:type-check && pnpm run lint:biome && pnpm run lint:styles", - "lint:biome": "biome check", - "lint:styles": "stylelint \"**/*.scss\"", - "fix": "pnpm run fix:biome && pnpm run fix:styles", - "fix:biome": "biome check --write --unsafe", - "fix:styles": "stylelint \"**/*.scss\" --fix", - "generate:tokens": "pnpm --filter @app/web generate:tokens" - }, - "devDependencies": { - "@biomejs/biome": "2.4.7", - "prettier": "^3.8.1", - "stylelint-config-css-modules": "^4.6.0", - "stylelint-config-recess-order": "^7.7.0", - "stylelint-config-standard-scss": "^17.0.0", - "stylelint-declaration-block-no-ignored-properties": "^3.0.0", - "stylelint-prettier": "^5.0.3", - "stylelint": "^17.5.0", - "typescript": "5.9.3" - }, - "packageManager": "pnpm@10.20.0", - "engines": { - "node": ">=22", - "pnpm": ">=10" - }, - "browserslist": [ - "> 0.2%", - "not dead" - ] + "name": "dataweaver", + "description": "Monorepo for Dataweaver", + "type": "module", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "pnpm -r --parallel dev", + "build": "pnpm -r build", + "preview": "pnpm -r --parallel preview", + "lint": "pnpm -r run lint:type-check && pnpm run lint:biome && pnpm run lint:styles", + "lint:biome": "biome check", + "lint:styles": "stylelint \"**/*.scss\"", + "fix": "pnpm run fix:biome && pnpm run fix:styles", + "fix:biome": "biome check --write --unsafe", + "fix:styles": "stylelint \"**/*.scss\" --fix", + "generate:tokens": "pnpm --filter @package/tokens generate" + }, + "devDependencies": { + "@biomejs/biome": "2.4.7", + "prettier": "^3.8.1", + "stylelint-config-css-modules": "^4.6.0", + "stylelint-config-recess-order": "^7.7.0", + "stylelint-config-standard-scss": "^17.0.0", + "stylelint-declaration-block-no-ignored-properties": "^3.0.0", + "stylelint-prettier": "^5.0.3", + "stylelint": "^17.5.0", + "typescript": "5.9.3" + }, + "packageManager": "pnpm@10.20.0", + "engines": { + "node": ">=22", + "pnpm": ">=10" + }, + "browserslist": [ + "> 0.2%", + "not dead" + ] } diff --git a/dataweaver/packages/tokens/dist/_tokens.scss b/dataweaver/packages/tokens/dist/_tokens.scss new file mode 100644 index 00000000..54f93980 --- /dev/null +++ b/dataweaver/packages/tokens/dist/_tokens.scss @@ -0,0 +1,5 @@ +// ⚠️ AUTO-GENERATED — Do not edit. Modify the JSON tokens in 'packages/tokens/src/' and run 'pnpm generate:tokens' + +$breakpoint-tablet: 768; +$breakpoint-laptop: 1280; +$breakpoint-desktop: 1600; diff --git a/dataweaver/packages/tokens/dist/tokens.css b/dataweaver/packages/tokens/dist/tokens.css new file mode 100644 index 00000000..57069f1b --- /dev/null +++ b/dataweaver/packages/tokens/dist/tokens.css @@ -0,0 +1,11 @@ +/* ⚠️ AUTO-GENERATED — Do not edit. Modify the JSON tokens in 'packages/tokens/src/' and run 'pnpm generate:tokens' */ + +:root { + --color-black: 0 0 0; + --color-grey-almost-black: 32 33 36; + --color-grey-muted: 68 71 70; + --color-grey-light: 227 231 237; + --color-white: 255 255 255; + --color-blue: 11 87 208; + --color-blue-light: 247 252 255; +} diff --git a/dataweaver/packages/tokens/dist/tokens.ts b/dataweaver/packages/tokens/dist/tokens.ts new file mode 100644 index 00000000..87731044 --- /dev/null +++ b/dataweaver/packages/tokens/dist/tokens.ts @@ -0,0 +1,22 @@ +// ⚠️ AUTO-GENERATED — Do not edit. Modify the JSON tokens in 'packages/tokens/src/' and run 'pnpm generate:tokens' + +export const BREAKPOINT_TABLET = 768; +export const BREAKPOINT_LAPTOP = 1280; +export const BREAKPOINT_DESKTOP = 1600; + +export const COLORS = { + black: '0, 0, 0', + 'grey-almost-black': '32, 33, 36', + 'grey-muted': '68, 71, 70', + 'grey-light': '227, 231, 237', + white: '255, 255, 255', + blue: '11, 87, 208', + 'blue-light': '247, 252, 255', +} as const; + +type Easing = [number, number, number, number]; + +export const EASE_LINEAR: Easing = [0, 0, 1, 1]; +export const EASE_OUT: Easing = [0.26, 1, 0.48, 1]; +export const EASE_IN: Easing = [0.52, 0, 0.74, 0]; +export const EASE_IN_OUT: Easing = [0.76, 0, 0.24, 1]; diff --git a/dataweaver/packages/tokens/package.json b/dataweaver/packages/tokens/package.json index 256a0c69..f8de442c 100644 --- a/dataweaver/packages/tokens/package.json +++ b/dataweaver/packages/tokens/package.json @@ -1,12 +1,17 @@ { - "name": "@package/tokens", - "type": "module", - "scripts": { - "generate": "tsx src/generate.ts", - "lint:type-check": "tsc --noEmit" - }, - "devDependencies": { - "@types/node": "^25.5.0", - "tsx": "^4.21.0" - } + "name": "@package/tokens", + "type": "module", + "exports": { + "./ts": "./dist/tokens.ts", + "./css": "./dist/tokens.css", + "./scss": "./dist/_tokens.scss" + }, + "scripts": { + "generate": "tsx src/generate.ts", + "lint:type-check": "tsc --noEmit" + }, + "devDependencies": { + "@types/node": "^25.5.0", + "tsx": "^4.21.0" + } } diff --git a/dataweaver/packages/tokens/src/colors.json b/dataweaver/packages/tokens/src/colors.json index a5b6fb4d..4c813580 100644 --- a/dataweaver/packages/tokens/src/colors.json +++ b/dataweaver/packages/tokens/src/colors.json @@ -1,9 +1,9 @@ { - "black": [0, 0, 0], - "grey-almost-black": [32, 33, 36], - "grey-muted": [68, 71, 70], - "grey-light": [227, 231, 237], - "white": [255, 255, 255], - "blue": [11, 87, 208], - "blue-light": [247, 252, 255] + "black": [0, 0, 0], + "grey-almost-black": [32, 33, 36], + "grey-muted": [68, 71, 70], + "grey-light": [227, 231, 237], + "white": [255, 255, 255], + "blue": [11, 87, 208], + "blue-light": [247, 252, 255] } diff --git a/dataweaver/packages/tokens/src/eases.json b/dataweaver/packages/tokens/src/eases.json index 960a8f09..0cdd8027 100644 --- a/dataweaver/packages/tokens/src/eases.json +++ b/dataweaver/packages/tokens/src/eases.json @@ -1,6 +1,6 @@ { - "linear": [0, 0, 1, 1], - "out": [0.26, 1.0, 0.48, 1.0], - "in": [0.52, 0.0, 0.74, 0.0], - "in-out": [0.76, 0.0, 0.24, 1.0] + "linear": [0, 0, 1, 1], + "out": [0.26, 1.0, 0.48, 1.0], + "in": [0.52, 0.0, 0.74, 0.0], + "in-out": [0.76, 0.0, 0.24, 1.0] } diff --git a/dataweaver/packages/tokens/src/generate.ts b/dataweaver/packages/tokens/src/generate.ts index 97a4ef2d..3507f5fd 100644 --- a/dataweaver/packages/tokens/src/generate.ts +++ b/dataweaver/packages/tokens/src/generate.ts @@ -1,7 +1,12 @@ /** - * Generates _generated.module.scss and generated.ts from JSON token files. + * Generates the design tokens into this package's `dist/` directory so they can + * be consumed by any application that depends on `@package/tokens`: * - * Usage: tsx src/generate.ts + * - `tokens.css` CSS custom properties for colors (themable at runtime). + * - `_tokens.scss` Build-time SCSS values (breakpoints) for `@media` queries. + * - `tokens.ts` TypeScript exports (breakpoints, eases, colors). + * + * Usage: tsx src/generate.ts */ import { mkdirSync, writeFileSync } from 'node:fs'; import { resolve } from 'node:path'; @@ -10,101 +15,81 @@ import COLORS from './colors.json' with { type: 'json' }; import EASES from './eases.json' with { type: 'json' }; import VARIABLES from './variables.json' with { type: 'json' }; -const [OUTPUT_DIRECTORY] = process.argv - .slice(2) - .filter((argument) => argument !== '--'); - -if (!OUTPUT_DIRECTORY) { - console.error( - '❌ Output directory argument is required. Usage: tsx src/generate.ts ', - ); - process.exit(1); -} - -const RESOLVED_OUTPUT_DIRECTORY = resolve(process.cwd(), OUTPUT_DIRECTORY); +const DIST_DIRECTORY = resolve(import.meta.dirname, '../dist'); const DO_NOT_EDIT_COMMENT_BANNER = - "// ⚠️ AUTO-GENERATED — Do not edit. Modify the JSON tokens in 'packages/tokens/src/' and run 'pnpm generate:tokens'"; + "AUTO-GENERATED — Do not edit. Modify the JSON tokens in 'packages/tokens/src/' and run 'pnpm generate:tokens'"; + +const generateCss = (): string => { + const lines: string[] = [ + `/* ⚠️ ${DO_NOT_EDIT_COMMENT_BANNER} */`, + '', + ':root {', + ]; + + // Colors are exposed as space-separated channels so they can be consumed as + // `rgb(var(--color-x))` (and rgba via `rgb(var(--color-x) / )`) + // and overridden by app as theme + for (const [name, values] of Object.entries(COLORS)) { + lines.push(` --color-${name}: ${values.join(' ')};`); + } + + lines.push('}', ''); + return lines.join('\n'); +}; const generateScss = (): string => { - const lines: string[] = [DO_NOT_EDIT_COMMENT_BANNER]; - - // Variables - lines.push(''); - lines.push('// Variables'); - for (const [name, value] of Object.entries(VARIABLES)) { - const formattedValue = name.startsWith('gutter-') ? `${value}px` : value; - lines.push(`$${name}: ${formattedValue};`); - } - - // Colors - lines.push(''); - lines.push('// Colors'); - for (const [name, values] of Object.entries(COLORS)) { - const formattedValue = values.join(' '); - lines.push(`$color-${name}: rgb(${formattedValue});`); - } - - // Eases - lines.push(''); - lines.push('// Eases'); - for (const [name, values] of Object.entries(EASES)) { - const formattedValue = values.join(', '); - lines.push(`$ease-${name}: cubic-bezier(${formattedValue});`); - } - - lines.push(''); - return lines.join('\n'); + const lines: string[] = [`// ⚠️ ${DO_NOT_EDIT_COMMENT_BANNER}`, '']; + + // Breakpoints are build-time values (CSS variables can't be used in '@media' + // conditions), so they are emitted as SCSS rather than CSS custom properties + for (const [name, value] of Object.entries(VARIABLES)) { + const formattedValue = name.startsWith('gutter-') ? `${value}px` : value; + lines.push(`$${name}: ${formattedValue};`); + } + + lines.push(''); + return lines.join('\n'); }; const generateTypeScript = (): string => { - const lines: string[] = [ - DO_NOT_EDIT_COMMENT_BANNER, - '', - 'import type { BezierDefinition } from "motion/react";', - ]; - - // Variables - lines.push(''); - for (const [name, value] of Object.entries(VARIABLES)) { - const constName = name.replaceAll('-', '_').toUpperCase(); - lines.push(`export const ${constName} = ${value};`); - } - - // Colors - lines.push(''); - lines.push('export const COLORS = {'); - for (const [name, values] of Object.entries(COLORS)) { - const constName = name.includes('-') ? `"${name}"` : name; - const formattedValue = values.join(', '); - lines.push(`\t${constName}: "${formattedValue}",`); - } - lines.push('} as const;'); - - // Eases - lines.push(''); - for (const [name, values] of Object.entries(EASES)) { - const constName = `EASE_${name.toUpperCase().replaceAll('-', '_')}`; - const formattedValue = values.join(', '); - lines.push( - `export const ${constName}: BezierDefinition = [${formattedValue}];`, - ); - } - - lines.push(''); - return lines.join('\n'); + const lines: string[] = [`// ⚠️ ${DO_NOT_EDIT_COMMENT_BANNER}`, '']; + + // Variables + for (const [name, value] of Object.entries(VARIABLES)) { + const constName = name.replaceAll('-', '_').toUpperCase(); + lines.push(`export const ${constName} = ${value};`); + } + + // Colors + lines.push(''); + lines.push('export const COLORS = {'); + for (const [name, values] of Object.entries(COLORS)) { + const constName = name.includes('-') ? `'${name}'` : name; + lines.push(` ${constName}: '${values.join(', ')}',`); + } + lines.push('} as const;'); + + // Eases: Typed as a cubic-bezier tuple so the package stays free of any + // animation-library dependency; it is structurally compatible with consumers + // such as motion's 'BezierDefinition' + lines.push(''); + lines.push('export type Easing = [number, number, number, number];'); + lines.push(''); + for (const [name, values] of Object.entries(EASES)) { + const constName = `EASE_${name.toUpperCase().replaceAll('-', '_')}`; + lines.push(`export const ${constName}: Easing = [${values.join(', ')}];`); + } + + lines.push(''); + return lines.join('\n'); }; -console.info(`🟦 Starting token generations...`); +console.info('🟦 Starting token generation...'); -mkdirSync(RESOLVED_OUTPUT_DIRECTORY, { recursive: true }); -writeFileSync( - resolve(RESOLVED_OUTPUT_DIRECTORY, '_generated.module.scss'), - generateScss(), -); -writeFileSync( - resolve(RESOLVED_OUTPUT_DIRECTORY, 'generated.ts'), - generateTypeScript(), -); +mkdirSync(DIST_DIRECTORY, { recursive: true }); +writeFileSync(resolve(DIST_DIRECTORY, 'tokens.css'), generateCss()); +writeFileSync(resolve(DIST_DIRECTORY, '_tokens.scss'), generateScss()); +writeFileSync(resolve(DIST_DIRECTORY, 'tokens.ts'), generateTypeScript()); -console.info('✅ Generated _generated.module.scss and generated.ts! \n'); +console.info('✅ Generated tokens.css, _tokens.scss and tokens.ts!\n'); diff --git a/dataweaver/packages/tokens/src/variables.json b/dataweaver/packages/tokens/src/variables.json index 5c9a2d27..a42544da 100644 --- a/dataweaver/packages/tokens/src/variables.json +++ b/dataweaver/packages/tokens/src/variables.json @@ -1,5 +1,5 @@ { - "breakpoint-tablet": 768, - "breakpoint-laptop": 1280, - "breakpoint-desktop": 1600 + "breakpoint-tablet": 768, + "breakpoint-laptop": 1280, + "breakpoint-desktop": 1600 } diff --git a/dataweaver/packages/tokens/tsconfig.json b/dataweaver/packages/tokens/tsconfig.json index ebc5f351..7f8ad55e 100644 --- a/dataweaver/packages/tokens/tsconfig.json +++ b/dataweaver/packages/tokens/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../tsconfig.base.json", - "compilerOptions": { "types": ["node"] }, - "include": ["src"] + "extends": "../tsconfig.base.json", + "compilerOptions": { "types": ["node"] }, + "include": ["src"] } diff --git a/dataweaver/packages/tsconfig.base.json b/dataweaver/packages/tsconfig.base.json index 70b1aacb..82c50bdb 100644 --- a/dataweaver/packages/tsconfig.base.json +++ b/dataweaver/packages/tsconfig.base.json @@ -1,15 +1,15 @@ { - "$schema": "https://json.schemastore.org/tsconfig.json", - "compilerOptions": { - "target": "esnext", - "module": "preserve", - "moduleResolution": "bundler", - "moduleDetection": "force", - "strict": true, - "skipLibCheck": true, - "resolveJsonModule": true, - "allowImportingTsExtensions": true, - "noUncheckedIndexedAccess": true, - "noEmit": true - } + "$schema": "https://json.schemastore.org/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "module": "preserve", + "moduleResolution": "bundler", + "moduleDetection": "force", + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "allowImportingTsExtensions": true, + "noUncheckedIndexedAccess": true, + "noEmit": true + } } diff --git a/dataweaver/stylelint.config.mjs b/dataweaver/stylelint.config.mjs index c07d594d..b53e71ae 100644 --- a/dataweaver/stylelint.config.mjs +++ b/dataweaver/stylelint.config.mjs @@ -1,50 +1,67 @@ /** @type {import('stylelint').Config} */ const STYLELINT_CONFIG = { - extends: [ - // Base rules. Note: This installs and uses 'postcss-scs' out of the box - 'stylelint-config-standard-scss', - - // CSS modules syntax support - 'stylelint-config-css-modules', - - // Enforce a logical order for CSS properties - 'stylelint-config-recess-order', - ], - - plugins: [ - // Block ignored properties in CSS - 'stylelint-declaration-block-no-ignored-properties', - - // Prettier integration - 'stylelint-prettier', - ], - - rules: { - // Enable prettier (inherits config from .prettierrc.json) - 'prettier/prettier': true, - - // Enable plugin - 'plugin/declaration-block-no-ignored-properties': true, - - // Ignore select properties in the redundant longhand properties rule - 'declaration-block-no-redundant-longhand-properties': [ - true, - { ignoreShorthands: ['/flex/', '/grid/'] }, - ], - - // Prevent redundant nesting selectors - 'scss/selector-no-redundant-nesting-selector': true, - - // Ignore position declarations inside '@supports' rules - it was causing - // issues that IMO weren't valid concerns - 'no-invalid-position-declaration': [true, { ignoreAtRules: ['supports'] }], - - // Disable some default rules - 'selector-id-pattern': null, - 'selector-class-pattern': null, - 'scss/operator-no-newline-after': null, - }, + extends: [ + // Base rules. Note: This installs and uses 'postcss-scs' out of the box + 'stylelint-config-standard-scss', + + // CSS modules syntax support + 'stylelint-config-css-modules', + + // Enforce a logical order for CSS properties + 'stylelint-config-recess-order', + ], + + plugins: [ + // Block ignored properties in CSS + 'stylelint-declaration-block-no-ignored-properties', + + // Prettier integration + 'stylelint-prettier', + ], + + rules: { + // Enable prettier (inherits config from .prettierrc.json) + 'prettier/prettier': true, + + // Enable plugin + 'plugin/declaration-block-no-ignored-properties': true, + + // Ignore select properties in the redundant longhand properties rule + 'declaration-block-no-redundant-longhand-properties': [ + true, + { ignoreShorthands: ['/flex/', '/grid/'] }, + ], + + // Prevent redundant nesting selectors + 'scss/selector-no-redundant-nesting-selector': true, + + // Use 3-character hex notation where possible (e.g. #ebc) + 'color-hex-length': 'short', + + // Omit units after '0' values where allowed (e.g. margin: 0) + 'length-zero-no-unit': true, + + // Prefer selector specificity over '!important'. Disable per-line with a + // comment when overriding third-party styles is genuinely unavoidable + 'declaration-no-important': true, + + // Avoid ID selectors; prefer classes + 'selector-max-id': 0, + + // Don't qualify classes with a type selector (e.g. 'ul.example' -> + // '.example'). Attribute-qualified type selectors (e.g. 'a[href]') are + // still allowed, as used in the reset + 'selector-no-qualifying-type': [true, { ignore: ['attribute', 'id'] }], + + // Hyphen-delimited (kebab-case) class names + 'selector-class-pattern': [ + '^[a-z][a-z0-9]*(-[a-z0-9]+)*$', + { + message: 'Use hyphen-delimited, lowercase class names (e.g. video-id)', + }, + ], + }, }; export default STYLELINT_CONFIG; From 223d946e88edc259b88e77b00a0df6be41da7aae Mon Sep 17 00:00:00 2001 From: Paulo Ferreira Jorge Date: Fri, 29 May 2026 12:02:39 +0100 Subject: [PATCH 06/20] Further Feedback --- dataweaver/.env.example | 2 +- dataweaver/README.md | 5 +++-- dataweaver/apps/web/.gitignore | 5 +---- dataweaver/package.json | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/dataweaver/.env.example b/dataweaver/.env.example index ec7a8791..6bc7b2dd 100644 --- a/dataweaver/.env.example +++ b/dataweaver/.env.example @@ -1 +1 @@ -NEXT_PUBLIC_SITE_TITLE="Dataweaver" +NEXT_PUBLIC_SITE_TITLE="Data Weaver Experiment" diff --git a/dataweaver/README.md b/dataweaver/README.md index dc65c797..2c832323 100644 --- a/dataweaver/README.md +++ b/dataweaver/README.md @@ -1,6 +1,6 @@ -# dataweaver +# Data Weaver Experiment -Monorepo for Dataweaver. Managed with [pnpm workspaces](https://pnpm.io/workspaces). +Root of the `/dataweaver` directory. Managed with [pnpm workspaces](https://pnpm.io/workspaces). ## Workspace layout @@ -26,6 +26,7 @@ nvm use ### Dependencies ```bash +corepack enable pnpm i ``` diff --git a/dataweaver/apps/web/.gitignore b/dataweaver/apps/web/.gitignore index 3465f99e..4ba1baa9 100644 --- a/dataweaver/apps/web/.gitignore +++ b/dataweaver/apps/web/.gitignore @@ -1,6 +1,3 @@ # Next.js .next/ -next-env.d.ts - -# Generated -src/server/storyblok/generated/globals.json \ No newline at end of file +next-env.d.ts \ No newline at end of file diff --git a/dataweaver/package.json b/dataweaver/package.json index 489408d8..3da6615a 100644 --- a/dataweaver/package.json +++ b/dataweaver/package.json @@ -1,6 +1,6 @@ { "name": "dataweaver", - "description": "Monorepo for Dataweaver", + "description": "Root of the `/dataweaver` directory.", "type": "module", "version": "1.0.0", "private": true, From 38c2b17dec8c32fd57339ed94e71a65a831bd1aa Mon Sep 17 00:00:00 2001 From: Paulo Ferreira Jorge Date: Fri, 29 May 2026 14:14:04 +0100 Subject: [PATCH 07/20] Update Next Config --- dataweaver/apps/web/next.config.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/dataweaver/apps/web/next.config.ts b/dataweaver/apps/web/next.config.ts index f58e54e1..8b6937f9 100644 --- a/dataweaver/apps/web/next.config.ts +++ b/dataweaver/apps/web/next.config.ts @@ -1,4 +1,3 @@ -import { join } from 'node:path'; import type { NextConfig } from 'next'; const NEXT_CONFIG: NextConfig = { @@ -7,17 +6,15 @@ const NEXT_CONFIG: NextConfig = { devIndicators: false, // Transpile the workspace design-tokens package, which ships TypeScript - // (`@package/tokens` -> dist/tokens.ts) rather than pre-built JS. + // ('@package/tokens' -> dist/tokens.ts) rather than pre-built JS transpilePackages: ['@package/tokens'], - // Auto-inject the shared Sass includes (breakpoints, helpers, keyframes, - // typography, z-indices) into every Sass entry module. Includes only - // '@forwards' definitions, so this emits no CSS; it removes the need to - // repeat `@use "~/styles/includes" as *;` at the top of each component file. - // `loadPaths` lets Sass resolve `@use "@package/tokens/..."` from node_modules. sassOptions: { + // Auto-inject the shared Sass includes (breakpoints, helpers, keyframes, + // typography, z-indices) into every Sass entry module. Includes only + // '@forwards' definitions, so this emits no CSS; it removes the need to + // repeat '@use "~/styles/includes" as *;' at the top of each component file additionalData: '@use "~/styles/includes" as *;\n', - loadPaths: [join(import.meta.dirname, 'node_modules')], }, compiler: { From e499aeea04cefe65e752b4838117bc32e8b70fe4 Mon Sep 17 00:00:00 2001 From: Paulo Ferreira Jorge Date: Fri, 29 May 2026 14:16:09 +0100 Subject: [PATCH 08/20] Re-Run Tokens --- dataweaver/packages/tokens/dist/tokens.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dataweaver/packages/tokens/dist/tokens.ts b/dataweaver/packages/tokens/dist/tokens.ts index 87731044..0b6e99a8 100644 --- a/dataweaver/packages/tokens/dist/tokens.ts +++ b/dataweaver/packages/tokens/dist/tokens.ts @@ -14,7 +14,7 @@ export const COLORS = { 'blue-light': '247, 252, 255', } as const; -type Easing = [number, number, number, number]; +export type Easing = [number, number, number, number]; export const EASE_LINEAR: Easing = [0, 0, 1, 1]; export const EASE_OUT: Easing = [0.26, 1, 0.48, 1]; From b40151acc7d2e8ff0c901a61825292ee04f74e35 Mon Sep 17 00:00:00 2001 From: Paulo Ferreira Jorge Date: Fri, 29 May 2026 16:58:17 +0100 Subject: [PATCH 09/20] Add AI Docs --- dataweaver/AGENTS.md | 87 ++++++ dataweaver/FRONTEND.md | 261 ++++++++++++++++++ dataweaver/README.md | 4 +- dataweaver/apps/web/src/app/layout.tsx | 6 +- .../foundations/motion-provider.tsx | 18 ++ 5 files changed, 371 insertions(+), 5 deletions(-) create mode 100644 dataweaver/AGENTS.md create mode 100644 dataweaver/FRONTEND.md create mode 100644 dataweaver/apps/web/src/components/foundations/motion-provider.tsx diff --git a/dataweaver/AGENTS.md b/dataweaver/AGENTS.md new file mode 100644 index 00000000..15d2a3f2 --- /dev/null +++ b/dataweaver/AGENTS.md @@ -0,0 +1,87 @@ +# dataweaver — agent guide + +Root of the `/dataweaver` directory, managed with [pnpm workspaces](https://pnpm.io/workspaces). +It sits within a larger monorepo, so all paths and commands here are relative to +`/dataweaver`, not that outer repo. + +This file is the source of truth for project conventions, imported by each +tool's agent entry point so every assistant reads the same rules. + +## Layout + +- `apps/web` — Next.js 16 app (App Router, React 19, Turbopack). +- `packages/tokens` — design tokens (JSON → generated CSS / SCSS / TS), consumed as `@package/tokens`. + +## Commands + +Run from the root of the `/dataweaver` directory: + +- `corepack enable && pnpm i` — install (the pnpm version is pinned via `packageManager`). +- `pnpm dev` — run apps in dev mode. +- `pnpm build` — build all apps. +- `pnpm lint` — type-check + Biome + Stylelint. +- `pnpm fix` — auto-fix Biome + Stylelint. +- `pnpm generate:tokens` — regenerate `packages/tokens/dist/` from `packages/tokens/src/*.json`. + +Run `pnpm lint` (and `pnpm build` for UI changes) before considering work done. + +## Code style + +- **TypeScript** follows the [Google TypeScript Style Guide](https://google.github.io/styleguide/tsguide.html), enforced via Biome (`biome.json`). +- **CSS / SCSS** follow the [Google HTML/CSS Style Guide](https://google.github.io/styleguide/htmlcssguide.html): Stylelint for `.scss` (`stylelint.config.mjs`), Biome for plain `.css`. + +## Frontend + +Before writing any React component, SCSS module, or front-end utility, read +[`FRONTEND.md`](FRONTEND.md) — the authoritative reference for TypeScript, SCSS, +primitives, motion, performance, and accessibility conventions. + +## Components (`apps/web/src/components`) + +Four categories, layered low → high. Composition flows upward (scopes compose +elements compose primitives); `foundations` wrap the whole tree from the root. + +- **`primitives/`** — the lowest-level building blocks: thin wrappers over a + single platform / third-party concern (a link, an icon, an image, rich text). + No business logic, minimal styling. Always go through these instead of raw + `` / `` / `NextLink` / inline ``. _e.g._ `primitives/link`, + `primitives/icons/*`. +- **`elements/`** — generic, reusable, presentational building blocks composed + from primitives (a button, a tabs control). Self-contained, no feature or + business logic, usable anywhere. _e.g._ `elements/button`. +- **`scopes/`** — feature- or page-scoped compositions that assemble primitives + and elements into a specific view. A scope **owns its sub-components**: pieces + used only by that scope live in its folder, not in `elements/`. _e.g._ + `scopes/page-home` and `scopes/tldraw`. +- **`foundations/`** — app-level infrastructure and cross-cutting providers / + services that the rest of the tree depends on but that render little or no UI + of their own: context providers, motion / scroll providers, analytics, global + embeds, dev tooling. Mounted once near the root (`app/layout.tsx`). _e.g._ + `foundations/motion-provider` (wraps the tree in motion's `LazyMotion`). + +Decide placement by reuse and concern: a single platform concern → `primitive`; +reusable presentational UI → `element`; tied to one view → that `scope`; +app-wide service / provider → `foundation`. + +## Design tokens (`@package/tokens`) + +Source: `packages/tokens/src/*.json`. Edit those and run `pnpm generate:tokens`; +never edit `packages/tokens/dist/**` (generated). + +- **Colors** → `@package/tokens/css` (imported once in `core.scss`): `:root` custom properties as space-separated channels. Use as `rgb(var(--color-name))`; alpha as `rgb(var(--color-name) / 50%)`. Names are literal (e.g. `blue`, `grey-light`), defined in `colors.json` as `[r, g, b]`. +- **Breakpoints** → `@package/tokens/scss` (build-time SCSS for `@media`) and `@package/tokens/ts` (`BREAKPOINT_*`). Not CSS variables — `var()` is invalid in media-query conditions. +- **Eases / color values in JS** → `@package/tokens/ts` (`EASE_*`, `COLORS`). + +Add new colors to `colors.json`, breakpoints to `variables.json`, eases to `eases.json`, then regenerate. + +## Verify + +`pnpm lint` for every change; `pnpm build` and a manual check for UI changes. + +## Self-maintenance + +When a change affects project structure, dependencies, commands, or +architecture, update this file, [`FRONTEND.md`](FRONTEND.md), and `README.md` to +match. Keep `AGENTS.md` / `FRONTEND.md` as the technical source of truth and +`README.md` as user-facing docs. The per-tool agent entry points import this +file — edit conventions here, not in those. diff --git a/dataweaver/FRONTEND.md b/dataweaver/FRONTEND.md new file mode 100644 index 00000000..b5f00343 --- /dev/null +++ b/dataweaver/FRONTEND.md @@ -0,0 +1,261 @@ +# Frontend Conventions + +The authoritative reference for writing React + SCSS in this project. Read this +before touching any component, hook, or stylesheet. + +These conventions follow the same shape as our other projects, but this is a +**Google-style-guide project**, so several rules are inverted by the linting +setup. The Google-specific differences: + +| Topic | This project (Google) | Enforced by | +|---|---|---| +| Object shapes | **`interface` over `type`** (props named `ComponentNameProps`) | Biome `useConsistentTypeDefinitions: interface` | +| Indentation | **2 spaces** (no tabs) | Biome / Stylelint | +| Translucent colour | **`rgb(var(--color-x) / )`** — tokens are CSS variables | convention | +| Token source | **`@package/tokens`** (`/css`, `/scss`, `/ts`) — not in-app generated | convention | +| Class names | **kebab-case** | Stylelint `selector-class-pattern` | + +Full rule sources: [Google TypeScript Style Guide](https://google.github.io/styleguide/tsguide.html) +and [Google HTML/CSS Style Guide](https://google.github.io/styleguide/htmlcssguide.html), +enforced via `biome.json` and `stylelint.config.mjs`. + +--- + +## 1. File layout + +| Component | Location | +|---|---| +| Primitive (wrapper over one platform / third-party concern) | `components/primitives/.tsx`, or grouped by category — e.g. `components/primitives/icons/.tsx` | +| Element (generic, reusable building block) | `components/elements//.tsx` + `.module.scss` | +| Scope (feature- / page-scoped composition) | `components/scopes//.tsx` | +| Scope-local subcomponent (used only by that scope) | nested in the scope folder, e.g. `components/scopes/tldraw/card/card.tsx` | +| Foundation (app-level provider / service / global embed) | `components/foundations/.tsx`, mounted once near the root | + +Layered low → high: **primitives → elements → scopes**, with **foundations** +wrapping the tree from the root. Composition flows upward; decide placement by +reuse and concern (one platform concern → primitive; reusable presentational UI +→ element; tied to one view → that scope; app-wide service → foundation). See +[AGENTS.md](AGENTS.md) for the full description of each category. + +- **Kebab-case** file names; **PascalCase** component identifiers + (`card-text.tsx` → `CardText`). +- Every `.tsx` pairs with a co-located `.module.scss`. No styled-jsx, no + Tailwind, no inline styles (CSS custom properties via `style={{}}` are fine + for index-driven values only — see §3.5). +- Keep a component in the narrowest scope that owns it. A component used only + inside one scope lives in that scope's folder, not in `elements/`. + +--- + +## 2. TypeScript + +- **`interface` over `type`** for object shapes. Name a component's props + `ComponentNameProps` (`interface CardProps`). Unions, tuples, mapped and + function types stay `type`. +- **Named exports only** — `export default` is allowed only in `src/app/**` + (Next.js route files) and `*.config.*`. +- **`import type`** for type-only imports; **`export type`** for type-only + re-exports. +- **Components use an explicit `return` block** — `() => { return ( … ); }`, + never an implicit arrow return, even for one-line JSX. Implicit returns are + fine for non-component helpers whose body fits on one line. +- Prefer arrow functions; only mark a function `async` when it `await`s. +- **No `any`** — use `unknown` and narrow. If unavoidable, suppress the single + line with a comment, never relax the rule globally. +- `===` / `!==` only; `const` by default; **braced** control statements. +- **Single quotes** for JS / TS strings (Biome). SCSS strings use double quotes, + plain `.css` single — see §3.1. +- **80-column lines** — keep code and prose (incl. this doc and Markdown) at + ≤ 80 chars. Biome (JS / TS / JSON) and Prettier (SCSS) reflow on format; wrap + long lines rather than letting them run. The only exceptions are unbreakable + tokens (long URLs, import paths). +- No relative parent imports (`../`) — use the `~/` alias (`~/* → apps/web/src/*`). +- Destructure props **in the same order as the interface** so reviewers can diff + props against the type at a glance. +- Declare **DOM-bound variables (refs, IDs) in the order they appear in the + rendered tree**, so the hooks block can be read top-to-bottom against the JSX. +- Prefer full names (`index` not `i`, `calculateTotal` not `calcTotal`). Prefix + booleans with `is` / `has` / `should` (`isLoading`, `hasFooter`). +- Add JSDoc where intent is non-obvious. + +--- + +## 3. SCSS + +CSS Modules only (`*.module.scss`), imported as `import s from './x.module.scss'`. + +`~/styles/includes` (breakpoint / helper / z-index mixins) is **auto-injected** +into every module via `next.config.ts` `additionalData` — do **not** re-`@use` +it. Only `@use` files that aren't part of includes (e.g. +`@use "~/styles/typography.module" as typography;`). + +### 3.1 Selectors & formatting + +- Class names are **kebab-case** (e.g. `.card`, `.icon`, `.actions`). For + multi-word classes, access them in JSX via `s['card-text']`; prefer concise + single-word names where the role is clear. +- 2-space indent. SCSS strings use double quotes; plain `.css` uses single + quotes (Biome owns `.css`, Stylelint owns `.scss`). +- No redundant nesting selectors (Stylelint enforces). + +### 3.2 Class naming vocabulary + +- **Root element = `.container`.** One per file; identity comes from the file + name and `data-*` attributes, not a unique root class. +- **Layout wrappers = `-container`** (e.g. `.assets-container`). Never + `wrapper` / `outer` / `inner`. +- **Bare leaf class only when it renders a prop of the same name** — + `

{title}

`. Otherwise use a `-container` or + an element-prefixed class. +- **Multiple buttons / icons → element-prefixed kebab**: `.button-close`, + `.icon-arrow-right` (read left-to-right: "a button that closes"). + +### 3.3 Variants & state via `data-*` + +Drive visual variants and boolean state through `data-*` attributes on the +container — never through className flags: + +```tsx +
+``` + +```scss +.container[data-state="selected"] .card { … } +``` + +### 3.4 Design tokens + +Tokens come from `@package/tokens` (see [AGENTS.md](AGENTS.md)): + +- **Colour**: `rgb(var(--color-name))`; translucent: `rgb(var(--color-name) / 50%)`. + Never a bare hex or `rgb(0 0 0 / …)` — always anchor to a `--color-*` variable. +- **Breakpoints (SCSS, build-time)**: `@include breakpoint(tablet|laptop|desktop)`. +- **Z-index**: `$z-index-*` (ordered stack in `_z-indices.module.scss`) — never a + raw number. +- **Easing (SCSS)**: `$ease-linear` / `$ease-out` / `$ease-in` / `$ease-in-out`. + +If a design value has no matching token, flag it — don't introduce a one-off +constant without discussion. + +### 3.5 Index-driven values + +Pass index-based values as CSS custom properties via `style`, never as class +permutations: `
  • ` → `grid-row: var(--row);`. + +### 3.6 Responsiveness — mobile-first + +Base styles target mobile; layer up with `@include breakpoint(tablet)` (768px+), +`laptop` (1280px+), `desktop` (1600px+). + +### 3.7 Motion + +Always wrap animation / transition styles in `@include prefers-motion { … }` so +reduced-motion users get a static rendering. Animate **`transform` / `opacity` +only** for per-frame values (compositor-only; skip layout/paint). + +### 3.8 Misc + +- Prefer `overflow: clip` over `overflow: hidden` when you only need to crop + (no scroll container / new stacking context). +- Prefer **grid-stack** (`display: grid` + `grid-area: 1 / 1`) over absolute + positioning to overlap siblings. +- When removing a property at a breakpoint, use `unset`, not a hardcoded zero. +- **Don't restate the reset.** `styles/core/_reset.scss` already neutralises + margins, `button` chrome, `a` decoration, list bullets, etc. Check it before + adding `padding: 0` / `border: 0` / `background: none`. +- Override focus offset via `--outline-offset` (`-default` 2px / `-inset` -3px); + never override `outline` itself. +- Typography mixins live in `~/styles/typography.module.scss` — `@use` it where + needed (it isn't part of the auto-injected includes). +- Cascade layers, low → high: `reset, root, base, primitive` (`~/styles/layers.css`). + +--- + +## 4. Primitives & hooks — use these, don't reimplement + +| Need | Use | Import | +|---|---|---| +| Link (internal / external / anchor) | `…` | `~/components/primitives/link` | +| Compose class names | `mergeClassNames(…)` | `~/functions/merge-class-names` | +| Match a breakpoint / media query | `useMatchMedia("tablet" \| "prefers-motion" \| …)` | `~/hooks/use-match-media` | + +`` derives internal (`NextLink`) vs external (`target="_blank"` + +`rel="noopener noreferrer"`) from `href` / `isExternal` — never pass `target` +/ `rel` yourself, and never render a raw `` / `NextLink`. + +--- + +## 5. Motion / animation + +The library is `motion/react`. The `MotionProvider` foundation +(`foundations/motion-provider`) wires `LazyMotion` (`strict`, `domMax`) at the +root layout, so use **`m.*`**, never `motion.*`. + +JS-side easing constants (typed): import `EASE_LINEAR` / `EASE_OUT` / `EASE_IN` +/ `EASE_IN_OUT` from `@package/tokens/ts`. SCSS equivalents: `$ease-*`. + +Gate motion in CSS with `@include prefers-motion`. For Motion props, gate the +animation block on `useMatchMedia("prefers-motion")` via a conditional spread so +reduced-motion users render the static resting state. + +--- + +## 6. Performance + +- Animate `transform` / `opacity` only for per-frame values — both stay on the + compositor. +- Don't drive per-frame transforms through a CSS variable consumed by many + descendants (cascading style recalc) — pass the `MotionValue` to an `m.*` + element's `style`. +- Batch DOM reads before writes; keep `getBoundingClientRect()` / `offsetWidth` + out of scroll / animation hot paths. + +--- + +## 7. Accessibility + +Target WCAG 2.2 AA. Build it in, don't bolt it on. + +- **Semantic HTML first.** `
    ` for sections, `
    ` for + self-contained content (a card), ` + ); +}; diff --git a/dataweaver/apps/web/src/components/elements/card/base.module.scss b/dataweaver/apps/web/src/components/elements/card/base.module.scss new file mode 100644 index 00000000..545d499f --- /dev/null +++ b/dataweaver/apps/web/src/components/elements/card/base.module.scss @@ -0,0 +1,75 @@ +.container { + --actions-height: 48px; + --corner-size: 16px; + --border-thickness: 2px; + + display: grid; + height: 100%; +} + +.actions-container { + display: flex; + grid-area: 1 / 1; + gap: 6px; + align-items: center; + justify-content: center; + height: calc(var(--actions-height) + var(--corner-size)); + padding-bottom: calc(var(--corner-size) - var(--border-thickness)); + margin: var(--actions-height) 2px 0; + background: rgb(var(--color-card-base-selected)); + border-radius: var(--corner-size) var(--corner-size) 0 0; + box-shadow: + 0 1px 3px 1px rgb(var(--color-card-shadow) / 15%), + 0 1px 2px rgb(var(--color-card-shadow) / 30%); + + @include prefers-motion { + transition: transform 0.5s $ease-out; + } + + [data-state="selected"] & { + transform: translateY(calc(var(--actions-height) * -1)); + } +} + +.children-container { + display: flex; + flex-direction: column; + grid-area: 1 / 1; + height: fit-content; + max-height: 100%; + overflow-y: auto; + background: rgb(var(--color-card-base)); + border: var(--border-thickness) solid transparent; + border-radius: var(--corner-size); + box-shadow: + 0 1px 3px 1px rgb(var(--color-card-shadow) / 15%), + 0 1px 2px rgb(var(--color-card-shadow) / 30%); + + @include prefers-motion { + transition: + transform 0.5s $ease-out, + border-color 0.5s $ease-out; + } + + [data-state="selected"] & { + border-color: rgb(var(--color-card-base-selected)); + transform: translateY(var(--actions-height)); + } +} + +.content { + display: flex; + flex-shrink: 0; + flex-direction: column; + padding: 28px; +} + +.footer { + flex-shrink: 0; + padding: 0 28px 18px; + + [data-state="loading"] & { + visibility: hidden; + pointer-events: none; + } +} diff --git a/dataweaver/apps/web/src/components/elements/card/base.tsx b/dataweaver/apps/web/src/components/elements/card/base.tsx new file mode 100644 index 00000000..6c162fe2 --- /dev/null +++ b/dataweaver/apps/web/src/components/elements/card/base.tsx @@ -0,0 +1,66 @@ +'use client'; + +import type { ComponentPropsWithRef, ComponentType, ReactNode } from 'react'; +import { Button } from '~/components/elements/button'; +import { useCachedResizeValues } from '~/hooks/use_cached_resize_values'; +import s from './base.module.scss'; + +export type CardState = 'loading' | 'default' | 'selected'; + +interface CardAction { + icon: ComponentType>; + label: string; + onClick?: () => void; +} + +interface CardProps { + state: CardState; + actions: CardAction[]; + content: ReactNode; + + /** **Note**: This isn't shown while `state` is `loading`. */ + footer?: ReactNode; +} + +export const CardBase = ({ state, actions, content, footer }: CardProps) => { + const getCachedCanScroll = useCachedResizeValues((element: HTMLElement) => { + return element.scrollHeight > element.clientHeight; + }); + + return ( +
    +
    + {actions.map((action, index) => ( +
    + +
    { + if (getCachedCanScroll(false, event.currentTarget)) { + event.stopPropagation(); + } + }} + > +
    {content}
    +
    + {footer} +
    +
    +
    + ); +}; diff --git a/dataweaver/apps/web/src/components/elements/card/chart/chart.module.scss b/dataweaver/apps/web/src/components/elements/card/chart/chart.module.scss new file mode 100644 index 00000000..fe767932 --- /dev/null +++ b/dataweaver/apps/web/src/components/elements/card/chart/chart.module.scss @@ -0,0 +1,15 @@ +.header-container { + display: flex; + flex-direction: column; + row-gap: 8px; + margin-bottom: 20px; + color: rgb(var(--color-card-content)); +} + +.title { + @include type-title; +} + +.description { + @include type-body; +} diff --git a/dataweaver/apps/web/src/components/elements/card/chart/chart.tsx b/dataweaver/apps/web/src/components/elements/card/chart/chart.tsx new file mode 100644 index 00000000..22a662f4 --- /dev/null +++ b/dataweaver/apps/web/src/components/elements/card/chart/chart.tsx @@ -0,0 +1,61 @@ +'use client'; + +import { useState } from 'react'; +import { IconLineGraph } from '~/components/primitives/icons/line_graph'; +import { IconTable } from '~/components/primitives/icons/table'; +import s from './chart.module.scss'; +import { type ChartDatum, DataLineChart } from './data_line_chart'; +import { DataTable } from './data_table'; +import { type TabItem, Tabs } from './tabs'; + +const TABS: TabItem[] = [ + { id: 'chart', label: 'Chart', icon: IconLineGraph }, + { id: 'table', label: 'Table', icon: IconTable }, +] as const; + +// TODO: Get dynamically instead of hard coding here +const CHART_WIDTH = 356; +const CHART_HEIGHT = 200; + +interface CardChartProps { + title?: string; + description?: string; + data?: ChartDatum[]; + + /** + * Renders skeleton lines in place of the body while content loads. + * + * @default false + */ + isLoading?: boolean; +} + +export const CardChart = ({ + data, + title, + description, + + // TODO: Support loading state here + // isLoading = false, +}: CardChartProps) => { + const [activeTab, setActiveTab] = useState('chart'); + + return ( + <> + {(title || description) && ( +
    + {title &&

    {title}

    } + {description &&

    {description}

    } +
    + )} + + + + {activeTab === 'chart' && data && ( + + )} + + {activeTab === 'table' && data && } + + ); +}; diff --git a/dataweaver/apps/web/src/components/elements/card/chart/data_line_chart.tsx b/dataweaver/apps/web/src/components/elements/card/chart/data_line_chart.tsx new file mode 100644 index 00000000..2c0be48e --- /dev/null +++ b/dataweaver/apps/web/src/components/elements/card/chart/data_line_chart.tsx @@ -0,0 +1,53 @@ +'use client'; + +import { COLORS } from '@package/tokens/ts'; +import { CartesianGrid, Line, LineChart, XAxis, YAxis } from 'recharts'; + +export interface ChartDatum { + year: number; + emissions: number; +} + +// Recharts colours SVG presentation attributes, where CSS `var()` doesn't +// resolve — so source concrete values from the JS token export. +const LINE_COLOR = `rgb(${COLORS['card-base-selected']})`; +const GRID_COLOR = `rgb(${COLORS['surface-decorator']})`; +const AXIS_COLOR = `rgb(${COLORS['card-content-muted']})`; + +interface ChartProps { + data: ChartDatum[]; + width: number; + height: number; +} + +export const DataLineChart = ({ data, width, height }: ChartProps) => { + return ( + + + + + + + ); +}; diff --git a/dataweaver/apps/web/src/components/elements/card/chart/data_table.module.scss b/dataweaver/apps/web/src/components/elements/card/chart/data_table.module.scss new file mode 100644 index 00000000..0a9ff413 --- /dev/null +++ b/dataweaver/apps/web/src/components/elements/card/chart/data_table.module.scss @@ -0,0 +1,11 @@ +.table { + @include type-label; + + width: 100%; + text-align: left; +} + +.table th, +.table td { + padding: 4px 8px; +} diff --git a/dataweaver/apps/web/src/components/elements/card/chart/data_table.tsx b/dataweaver/apps/web/src/components/elements/card/chart/data_table.tsx new file mode 100644 index 00000000..90a5561d --- /dev/null +++ b/dataweaver/apps/web/src/components/elements/card/chart/data_table.tsx @@ -0,0 +1,27 @@ +import type { ChartDatum } from './data_line_chart'; +import s from './data_table.module.scss'; + +interface DataTableProps { + data: ChartDatum[]; +} + +export const DataTable = ({ data }: DataTableProps) => { + return ( + + + + + + + + + {data.map(({ year, emissions }) => ( + + + + + ))} + +
    YearEmissions (Mt CO₂e)
    {year}{emissions}
    + ); +}; diff --git a/dataweaver/apps/web/src/components/elements/card/chart/tabs.module.scss b/dataweaver/apps/web/src/components/elements/card/chart/tabs.module.scss new file mode 100644 index 00000000..1dda1fc7 --- /dev/null +++ b/dataweaver/apps/web/src/components/elements/card/chart/tabs.module.scss @@ -0,0 +1,42 @@ +.container { + margin-bottom: 16px; + border-bottom: 1px solid rgb(var(--color-card-content-subtle)); +} + +.tabs-container { + display: flex; +} + +.tab { + @include type-tab; + + display: flex; + gap: 4px; + align-items: center; + padding: 4px 12px 4px 8px; + color: rgb(var(--color-card-content-muted)); + border-bottom: 1px solid transparent; + + @include prefers-motion { + transition-timing-function: $ease-linear; + transition-duration: 0.2s; + transition-property: color, border-bottom-color; + } + + &[data-is-active="true"] { + color: rgb(var(--color-card-base-selected)); + border-bottom-color: rgb(var(--color-card-base-selected)); + } + + @include hover { + &[data-is-active="false"] { + color: rgb(var(--color-card-base-selected)); + } + } +} + +.icon { + flex-shrink: 0; + width: 24px; + height: 24px; +} diff --git a/dataweaver/apps/web/src/components/elements/card/chart/tabs.tsx b/dataweaver/apps/web/src/components/elements/card/chart/tabs.tsx new file mode 100644 index 00000000..2efda1af --- /dev/null +++ b/dataweaver/apps/web/src/components/elements/card/chart/tabs.tsx @@ -0,0 +1,44 @@ +import type { ComponentPropsWithRef, ComponentType } from 'react'; +import s from './tabs.module.scss'; + +export interface TabItem { + id: string; + label: string; + icon: ComponentType>; +} + +interface TabsProps { + tabs: TabItem[]; + activeTab: string; + onChange: (id: string) => void; +} + +// TODO: Animate line between tab changes +export const Tabs = ({ tabs, activeTab, onChange }: TabsProps) => { + return ( +
    +
    + {tabs.map((tab) => { + const isActive = tab.id === activeTab; + + return ( + + ); + })} +
    +
    + ); +}; diff --git a/dataweaver/apps/web/src/components/elements/card/index.ts b/dataweaver/apps/web/src/components/elements/card/index.ts new file mode 100644 index 00000000..968bb6ec --- /dev/null +++ b/dataweaver/apps/web/src/components/elements/card/index.ts @@ -0,0 +1,9 @@ +import { CardBase } from './base'; +import { CardChart } from './chart/chart'; +import { CardText } from './text/text'; + +export const Card = { + Base: CardBase, + Text: CardText, + Chart: CardChart, +} as const; diff --git a/dataweaver/apps/web/src/components/elements/card/text/skeleton.module.scss b/dataweaver/apps/web/src/components/elements/card/text/skeleton.module.scss new file mode 100644 index 00000000..791e9800 --- /dev/null +++ b/dataweaver/apps/web/src/components/elements/card/text/skeleton.module.scss @@ -0,0 +1,21 @@ +.container { + display: flex; + flex-direction: column; + gap: 4px; +} + +@keyframes skeleton-pulse { + 50% { + opacity: 0.45; + } +} + +.line { + height: 18px; + background-color: rgb(var(--color-card-skeleton)); + border-radius: 8px; + + @include prefers-motion { + animation: skeleton-pulse 1.5s $ease-in-out infinite; + } +} diff --git a/dataweaver/apps/web/src/components/elements/card/text/skeleton.tsx b/dataweaver/apps/web/src/components/elements/card/text/skeleton.tsx new file mode 100644 index 00000000..ada91009 --- /dev/null +++ b/dataweaver/apps/web/src/components/elements/card/text/skeleton.tsx @@ -0,0 +1,30 @@ +import { ScreenReaderOnly } from '~/components/primitives/screen_reader'; +import s from './skeleton.module.scss'; + +interface SkeletonProps { + /** One line per entry; each value is the line's width as a % of the row. */ + widths: number[]; + + /** Announced to assistive tech while content loads. @default 'Loading…' */ + label?: string; +} + +/** Animated placeholder lines shown in place of content while it loads. */ +export const Skeleton = ({ widths, label = 'Loading…' }: SkeletonProps) => { + return ( +
    + {widths.map((width, index) => ( + + ))} + + {label} +
    + ); +}; diff --git a/dataweaver/apps/web/src/components/elements/card/text/text.module.scss b/dataweaver/apps/web/src/components/elements/card/text/text.module.scss new file mode 100644 index 00000000..453f6669 --- /dev/null +++ b/dataweaver/apps/web/src/components/elements/card/text/text.module.scss @@ -0,0 +1,12 @@ +.title { + @include type-title; + + margin-bottom: 16px; + color: rgb(var(--color-card-content)); +} + +.body { + @include type-body; + + color: rgb(var(--color-card-content)); +} diff --git a/dataweaver/apps/web/src/components/elements/card/text/text.tsx b/dataweaver/apps/web/src/components/elements/card/text/text.tsx new file mode 100644 index 00000000..4899deb4 --- /dev/null +++ b/dataweaver/apps/web/src/components/elements/card/text/text.tsx @@ -0,0 +1,29 @@ +import { Skeleton } from './skeleton'; +import s from './text.module.scss'; + +/** Skeleton line widths (% of the body) mirroring the loading design. */ +const SKELETON_LINE_WIDTHS = [100, 84, 92, 72]; + +interface CardTextProps { + title?: string; + body?: string; + + /** + * Renders skeleton lines in place of the body while content loads. + * + * @default false + */ + isLoading?: boolean; +} + +export const CardText = ({ title, body, isLoading = false }: CardTextProps) => { + return ( + <> + {title &&

    {title}

    } + + {isLoading && } + + {body &&
    {body}
    } + + ); +}; diff --git a/dataweaver/apps/web/src/components/primitives/icons/bar_chart.tsx b/dataweaver/apps/web/src/components/primitives/icons/bar_chart.tsx new file mode 100644 index 00000000..b2653299 --- /dev/null +++ b/dataweaver/apps/web/src/components/primitives/icons/bar_chart.tsx @@ -0,0 +1,18 @@ +import type { ComponentPropsWithRef } from 'react'; + +export const IconBarChart = (props: ComponentPropsWithRef<'svg'>) => { + return ( + + ); +}; diff --git a/dataweaver/apps/web/src/components/primitives/icons/delete.tsx b/dataweaver/apps/web/src/components/primitives/icons/delete.tsx new file mode 100644 index 00000000..afe7b1ae --- /dev/null +++ b/dataweaver/apps/web/src/components/primitives/icons/delete.tsx @@ -0,0 +1,18 @@ +import type { ComponentPropsWithRef } from 'react'; + +export const IconDelete = (props: ComponentPropsWithRef<'svg'>) => { + return ( + + ); +}; diff --git a/dataweaver/apps/web/src/components/primitives/icons/export.tsx b/dataweaver/apps/web/src/components/primitives/icons/export.tsx new file mode 100644 index 00000000..334f7e3b --- /dev/null +++ b/dataweaver/apps/web/src/components/primitives/icons/export.tsx @@ -0,0 +1,18 @@ +import type { ComponentPropsWithRef } from 'react'; + +export const IconExport = (props: ComponentPropsWithRef<'svg'>) => { + return ( + + ); +}; diff --git a/dataweaver/apps/web/src/components/primitives/icons/line_graph.tsx b/dataweaver/apps/web/src/components/primitives/icons/line_graph.tsx new file mode 100644 index 00000000..c107509e --- /dev/null +++ b/dataweaver/apps/web/src/components/primitives/icons/line_graph.tsx @@ -0,0 +1,18 @@ +import type { ComponentPropsWithRef } from 'react'; + +export const IconLineGraph = (props: ComponentPropsWithRef<'svg'>) => { + return ( + + ); +}; diff --git a/dataweaver/apps/web/src/components/primitives/icons/pencil.tsx b/dataweaver/apps/web/src/components/primitives/icons/pencil.tsx new file mode 100644 index 00000000..d3e1e415 --- /dev/null +++ b/dataweaver/apps/web/src/components/primitives/icons/pencil.tsx @@ -0,0 +1,18 @@ +import type { ComponentPropsWithRef } from 'react'; + +export const IconPencil = (props: ComponentPropsWithRef<'svg'>) => { + return ( + + ); +}; diff --git a/dataweaver/apps/web/src/components/primitives/icons/table.tsx b/dataweaver/apps/web/src/components/primitives/icons/table.tsx new file mode 100644 index 00000000..ec134d14 --- /dev/null +++ b/dataweaver/apps/web/src/components/primitives/icons/table.tsx @@ -0,0 +1,18 @@ +import type { ComponentPropsWithRef } from 'react'; + +export const IconTable = (props: ComponentPropsWithRef<'svg'>) => { + return ( + + ); +}; diff --git a/dataweaver/apps/web/src/components/primitives/screen_reader.module.scss b/dataweaver/apps/web/src/components/primitives/screen_reader.module.scss new file mode 100644 index 00000000..4d70fa9a --- /dev/null +++ b/dataweaver/apps/web/src/components/primitives/screen_reader.module.scss @@ -0,0 +1,3 @@ +.screenReaderOnly { + @include screen-reader-only; +} diff --git a/dataweaver/apps/web/src/components/primitives/screen_reader.tsx b/dataweaver/apps/web/src/components/primitives/screen_reader.tsx new file mode 100644 index 00000000..818a700d --- /dev/null +++ b/dataweaver/apps/web/src/components/primitives/screen_reader.tsx @@ -0,0 +1,18 @@ +import type { ComponentPropsWithRef } from 'react'; +import s from './screen_reader.module.scss'; + +type ScreenReaderOnlyProps = Omit< + WithRequired, 'children'>, + 'className' +>; + +export const ScreenReaderOnly = ({ + children, + ...rest +}: ScreenReaderOnlyProps) => { + return ( + + {children} + + ); +}; diff --git a/dataweaver/apps/web/src/components/scopes/tldraw.module.scss b/dataweaver/apps/web/src/components/scopes/atlas/atlas.module.scss similarity index 60% rename from dataweaver/apps/web/src/components/scopes/tldraw.module.scss rename to dataweaver/apps/web/src/components/scopes/atlas/atlas.module.scss index 96a6c7e0..8f7da2ba 100644 --- a/dataweaver/apps/web/src/components/scopes/tldraw.module.scss +++ b/dataweaver/apps/web/src/components/scopes/atlas/atlas.module.scss @@ -1,6 +1,6 @@ @import "tldraw/tldraw.css" layer(primitive); -.canvas { +.tldraw { --tl-color-background: transparent; position: fixed; @@ -9,10 +9,4 @@ // Ensure all nested layers appear from a base of 0 and not above app content z-index: $z-index-content; background-color: rgb(var(--color-surface-base)); - background-image: radial-gradient( - rgb(var(--color-surface-decorator)) 1px, - transparent 1px - ); - background-position: center center; - background-size: 20px 20px; } diff --git a/dataweaver/apps/web/src/components/scopes/atlas/atlas.tsx b/dataweaver/apps/web/src/components/scopes/atlas/atlas.tsx new file mode 100644 index 00000000..aeb10bf3 --- /dev/null +++ b/dataweaver/apps/web/src/components/scopes/atlas/atlas.tsx @@ -0,0 +1,90 @@ +'use client'; + +import { + createContext, + type ReactNode, + useCallback, + useContext, + useRef, +} from 'react'; +import { type Editor, Tldraw } from 'tldraw'; +import s from './atlas.module.scss'; +import { ATLAS_COMPONENTS, ATLAS_SHAPES } from './config'; +import { type AtlasContent, contentToShape, gridPosition } from './helpers'; + +interface ContextValue { + /** Push new content onto the atlas - auto-placed in the grid. */ + add: (content: AtlasContent) => void; +} + +const AtlasContext = createContext(null); + +interface ProviderProps { + /** Content placed onto the atlas when the editor first mounts. */ + initialContent?: AtlasContent[]; + children?: ReactNode; +} + +export const Atlas = ({ initialContent = [], children }: ProviderProps) => { + const hasPopulatedInitialContentRef = useRef(false); + const editorRef = useRef(null); + + // TODO: For now using count for positioning - we likely want to use a smarter + // approach that accounts for deleted content and doesn't rely on order of + // addition, etc later once we hook up to real data + const countRef = useRef(0); + + const populate = useCallback((editor: Editor, content: AtlasContent) => { + const shape = contentToShape(content, gridPosition(countRef.current)); + + // If we don't get a shape back (e.g. unsupported content type) - skip it + if (!shape) return; + + editor.createShape(shape); + countRef.current += 1; + }, []); + + const mounted = useCallback( + (editor: Editor) => { + editorRef.current = editor; + + // Render the dot grid (camera-tracked via the 'Grid' component slot) + editor.updateInstanceState({ isGridMode: true }); + + // Populate content (if we haven't already, e.g. due to fast refresh) + if (!hasPopulatedInitialContentRef.current) { + hasPopulatedInitialContentRef.current = true; + for (const content of initialContent) populate(editor, content); + } + }, + [initialContent, populate], + ); + + const add = useCallback( + (content: AtlasContent) => { + const editor = editorRef.current; + if (editor) populate(editor, content); + }, + [populate], + ); + + return ( + + + {children} + + ); +}; + +/** Hook for accessing the atlas context. */ +export const useAtlas = () => { + const value = useContext(AtlasContext); + if (!value) throw new Error("'useAtlas' must be used within 'Atlas'."); + return value; +}; diff --git a/dataweaver/apps/web/src/components/scopes/atlas/components/grid.module.scss b/dataweaver/apps/web/src/components/scopes/atlas/components/grid.module.scss new file mode 100644 index 00000000..6d179233 --- /dev/null +++ b/dataweaver/apps/web/src/components/scopes/atlas/components/grid.module.scss @@ -0,0 +1,11 @@ +@import "tldraw/tldraw.css" layer(primitive); + +.grid { + position: absolute; + inset: 0; + background-image: radial-gradient( + rgb(var(--color-surface-decorator)) 1px, + transparent 1px + ); + background-size: 20px 20px; +} diff --git a/dataweaver/apps/web/src/components/scopes/atlas/components/grid.tsx b/dataweaver/apps/web/src/components/scopes/atlas/components/grid.tsx new file mode 100644 index 00000000..abb4ef40 --- /dev/null +++ b/dataweaver/apps/web/src/components/scopes/atlas/components/grid.tsx @@ -0,0 +1,15 @@ +import type { TLGridProps } from 'tldraw'; +import s from './grid.module.scss'; + +/** + * Dot grid that tracks the camera. TLDraw transforms only its inner canvas + * layer - so the grid is rendered through this slot (handed the live camera) + * rather than as a static background on the outer container. Dots pan with the + * camera ('x * z', 'y * z') but keep a fixed screen size (no zoom scaling). + */ +export const Grid = ({ x, y, z }: TLGridProps) => ( +
    +); diff --git a/dataweaver/apps/web/src/components/scopes/atlas/config.ts b/dataweaver/apps/web/src/components/scopes/atlas/config.ts new file mode 100644 index 00000000..580322b7 --- /dev/null +++ b/dataweaver/apps/web/src/components/scopes/atlas/config.ts @@ -0,0 +1,8 @@ +import type { TLComponents } from 'tldraw'; +import { Grid } from './components/grid'; +import { CardChartShapeUtil } from './shapes/card_chart'; +import { CardTextShapeUtil } from './shapes/card_text'; + +export const ATLAS_COMPONENTS = { Grid } as const satisfies TLComponents; + +export const ATLAS_SHAPES = [CardTextShapeUtil, CardChartShapeUtil] as const; diff --git a/dataweaver/apps/web/src/components/scopes/atlas/helpers.ts b/dataweaver/apps/web/src/components/scopes/atlas/helpers.ts new file mode 100644 index 00000000..eec90a28 --- /dev/null +++ b/dataweaver/apps/web/src/components/scopes/atlas/helpers.ts @@ -0,0 +1,87 @@ +import { createShapeId, type TLCreateShapePartial } from 'tldraw'; +import type { ChartDatum } from '~/components/elements/card/chart/data_line_chart'; +import type { CardChartShape } from './shapes/card_chart'; +import type { CardTextShape } from './shapes/card_text'; + +/** + * App-level description of a thing to mount on the canvas. This is the layer's + * public vocabulary — callers describe content, not tldraw shapes. + */ +export type AtlasContent = + | { kind: 'card'; title: string; body?: string; isLoading?: boolean } + | { + kind: 'card-chart'; + title: string; + description: string; + data: ChartDatum[]; + }; + +interface Position { + x: number; + y: number; +} + +const GRID = { + columns: 4, + stepX: 460, + stepY: 600, + originX: 120, + originY: 120, +} as const; + +/** A simple utility to get a new content's position based on index. */ +export const gridPosition = (index: number): Position => ({ + x: GRID.originX + (index % GRID.columns) * GRID.stepX, + y: GRID.originY + Math.floor(index / GRID.columns) * GRID.stepY, +}); + +type AtlasShapePartial = + | TLCreateShapePartial + | TLCreateShapePartial; + +export const contentToShape = ( + content: AtlasContent, + position: Position, +): AtlasShapePartial | null => { + const baseProps = { + id: createShapeId(), + x: position.x, + y: position.y, + }; + + switch (content.kind) { + case 'card': + return { + ...baseProps, + type: 'card', + props: { + w: 360, + h: 440, + title: content.title, + body: content.body ?? '', + isLoading: content.isLoading ?? false, + }, + }; + + case 'card-chart': + return { + ...baseProps, + type: 'card-chart', + props: { + w: 420, + h: 520, + title: content.title, + description: content.description, + data: content.data, + }, + }; + + default: { + console.warn(`Attempting to draw unsupported content in Atlas:`, content); + + // Note: We purposely don't want to throw to improve DX when iterating on + // content types. If there's not content type we can just ignore it + return null; + } + } +}; diff --git a/dataweaver/apps/web/src/components/scopes/atlas/shapes/card_chart.tsx b/dataweaver/apps/web/src/components/scopes/atlas/shapes/card_chart.tsx new file mode 100644 index 00000000..58b3a93f --- /dev/null +++ b/dataweaver/apps/web/src/components/scopes/atlas/shapes/card_chart.tsx @@ -0,0 +1,115 @@ +import { + HTMLContainer, + type RecordProps, + Rectangle2d, + ShapeUtil, + T, + type TLShape, +} from 'tldraw'; +import { Button } from '~/components/elements/button'; +import { Card } from '~/components/elements/card'; +import type { CardState } from '~/components/elements/card/base'; +import type { ChartDatum } from '~/components/elements/card/chart/data_line_chart'; +import { IconBarChart } from '~/components/primitives/icons/bar_chart'; +import { IconDelete } from '~/components/primitives/icons/delete'; +import { IconExport } from '~/components/primitives/icons/export'; +import { IconPencil } from '~/components/primitives/icons/pencil'; + +interface CardChartShapeProps { + w: number; + h: number; + title?: string; + description?: string; + data?: ChartDatum[]; + isLoading: boolean; +} + +// Register the custom shape within tldraw +declare module 'tldraw' { + interface TLGlobalShapePropsMap { + 'card-chart': CardChartShapeProps; + } +} + +export type CardChartShape = TLShape<'card-chart'>; + +export class CardChartShapeUtil extends ShapeUtil { + static override type = 'card-chart' as const; + + static override props: RecordProps = { + w: T.number, + h: T.number, + title: T.string.optional(), + description: T.string.optional(), + data: T.arrayOf( + T.object({ year: T.number, emissions: T.number }), + ).optional(), + isLoading: T.boolean, + }; + + override getDefaultProps = (): CardChartShapeProps => { + return { w: 420, h: 520, data: [], isLoading: false }; + }; + + override getGeometry = (shape: CardChartShape) => { + return new Rectangle2d({ + width: shape.props.w, + height: shape.props.h, + isFilled: true, + }); + }; + + #getState = (shape: CardChartShape): CardState => { + if (shape.props.isLoading) return 'loading'; + + const isSelected = this.editor.getSelectedShapeIds().includes(shape.id); + if (isSelected) return 'selected'; + + return 'default'; + }; + + override component = (shape: CardChartShape) => { + const { w, h, title, description, data, isLoading } = shape.props; + + return ( + + this.editor.deleteShapes([shape.id]), + }, + ]} + content={ + + } + footer={ + + } + /> + + ); + }; + + // Disable default TLDraw events + override canResize = () => false; + override hideSelectionBoundsFg = () => true; + override hideSelectionBoundsBg = () => true; + override getIndicatorPath = () => undefined; +} diff --git a/dataweaver/apps/web/src/components/scopes/atlas/shapes/card_text.tsx b/dataweaver/apps/web/src/components/scopes/atlas/shapes/card_text.tsx new file mode 100644 index 00000000..ec8db63e --- /dev/null +++ b/dataweaver/apps/web/src/components/scopes/atlas/shapes/card_text.tsx @@ -0,0 +1,103 @@ +import { + HTMLContainer, + type RecordProps, + Rectangle2d, + ShapeUtil, + T, + type TLShape, +} from 'tldraw'; +import { Button } from '~/components/elements/button'; +import { Card } from '~/components/elements/card'; +import type { CardState } from '~/components/elements/card/base'; +import { IconDelete } from '~/components/primitives/icons/delete'; +import { IconExport } from '~/components/primitives/icons/export'; +import { IconPencil } from '~/components/primitives/icons/pencil'; + +interface CardTextShapeProps { + w: number; + h: number; + title?: string; + body?: string; + isLoading: boolean; +} + +// Register the custom shape within tldraw +declare module 'tldraw' { + interface TLGlobalShapePropsMap { + card: CardTextShapeProps; + } +} + +export type CardTextShape = TLShape<'card'>; + +export class CardTextShapeUtil extends ShapeUtil { + static override type = 'card' as const; + + static override props: RecordProps = { + w: T.number, + h: T.number, + title: T.string.optional(), + body: T.string.optional(), + isLoading: T.boolean, + }; + + override getDefaultProps = (): CardTextShapeProps => { + return { w: 360, h: 440, isLoading: false }; + }; + + override getGeometry = (shape: CardTextShape) => { + return new Rectangle2d({ + width: shape.props.w, + height: shape.props.h, + isFilled: true, + }); + }; + + #getState = (shape: CardTextShape): CardState => { + if (shape.props.isLoading) return 'loading'; + + const isSelected = this.editor.getSelectedShapeIds().includes(shape.id); + if (isSelected) return 'selected'; + + return 'default'; + }; + + override component = (shape: CardTextShape) => { + const { w, h, title, body, isLoading } = shape.props; + + return ( + + this.editor.deleteShapes([shape.id]), + }, + ]} + content={ + + } + footer={ + + } + /> + + ); + }; + + // Disable default TLDraw events + override canResize = () => false; + override hideSelectionBoundsFg = () => true; + override hideSelectionBoundsBg = () => true; + override getIndicatorPath = () => undefined; +} diff --git a/dataweaver/apps/web/src/components/scopes/page_home.tsx b/dataweaver/apps/web/src/components/scopes/page_home.tsx index b715a35f..29cabdbf 100644 --- a/dataweaver/apps/web/src/components/scopes/page_home.tsx +++ b/dataweaver/apps/web/src/components/scopes/page_home.tsx @@ -1,12 +1,52 @@ 'use client'; import dynamic from 'next/dynamic'; +import type { ChartDatum } from '~/components/elements/card/chart/data_line_chart'; +import type { AtlasContent } from './atlas/helpers'; -const Tldraw = dynamic( - () => import('./tldraw').then((module) => module.Tldraw), +const Atlas = dynamic( + () => import('./atlas/atlas').then((module) => module.Atlas), { ssr: false }, ); +// Mock greenhouse-gas emissions (Mt CO₂e) +const MOCK_GHG_DATA: ChartDatum[] = [ + { year: 2000, emissions: 820 }, + { year: 2003, emissions: 905 }, + { year: 2006, emissions: 980 }, + { year: 2009, emissions: 1045 }, + { year: 2012, emissions: 1180 }, + { year: 2015, emissions: 1260 }, + { year: 2018, emissions: 1390 }, + { year: 2021, emissions: 1470 }, +] as const; + +// TODO: Drop once we have a real data source to feed initial content from +const DUMMY_CONTENT: AtlasContent[] = [ + { + kind: 'card', + title: 'Greenhouse gas emissions in Africa', + isLoading: true, + }, + { + kind: 'card', + title: 'Key insights when evaluating greenhouse gas emissions', + body: 'Emissions per capita remain low relative to other regions, energy access is the dominant driver, and land-use change accounts for a large share of the total. missions per capita remain low relative to other regions, energy access is the dominant driver, and land-use change accounts for a large share of the total. missions per capita remain low relative to other regions, energy access is the dominant driver, and land-use change accounts for a large share of the total. missions per capita remain low relative to other regions, energy access is the dominant driver, and land-use change accounts for a large share of the total. missions per capita remain low relative to other regions, energy access is the dominant driver, and land-use change accounts for a large share of the total. missions per capita remain low relative to other regions, energy access is the dominant driver, and land-use change accounts for a large share of the total.', + }, + { + kind: 'card', + title: 'Key insights when evaluating greenhouse gas emissions', + body: 'Emissions per capita remain low relative to other regions, energy access is the dominant driver, and land-use change accounts for a large share of the total.', + }, + { + kind: 'card-chart', + title: 'Greenhouse gas emissions in Africa', + description: + 'The chart above tracks total GHG emissions across Africa over time.', + data: MOCK_GHG_DATA, + }, +]; + export const PageHome = () => { - return ; + return ; }; diff --git a/dataweaver/apps/web/src/components/scopes/tldraw.tsx b/dataweaver/apps/web/src/components/scopes/tldraw.tsx deleted file mode 100644 index 33e381c8..00000000 --- a/dataweaver/apps/web/src/components/scopes/tldraw.tsx +++ /dev/null @@ -1,8 +0,0 @@ -'use client'; - -import { Tldraw as PrimitiveTldraw } from 'tldraw'; -import s from './tldraw.module.scss'; - -export const Tldraw = () => { - return ; -}; diff --git a/dataweaver/apps/web/src/functions/merge_styles.ts b/dataweaver/apps/web/src/functions/merge_styles.ts new file mode 100644 index 00000000..51d38239 --- /dev/null +++ b/dataweaver/apps/web/src/functions/merge_styles.ts @@ -0,0 +1,29 @@ +import type { CSSProperties } from "react"; + +/** + * Helper to merge multiple style objects into one, filtering out any invalid + * values to avoid overwriting existing styles when none is provided. + * + * @example + * const mergedStyles = mergeStyles( + * { color: 'red', aspectRatio: '16 / 9' }, + * { color: 'blue', aspectRatio: undefined }, + * ); + * + * // Result: { color: 'blue', aspectRatio: '16 / 9' } + */ +export const mergeStyles = (...styles: Array) => { + const merged: CSSProperties = {}; + + for (const style of styles) { + // Ignore if style isn't set + if (!style) continue; + + // Iterate over each style property and only assign it if it's defined + for (const [key, value] of Object.entries(style)) { + if (value !== undefined) merged[key as keyof CSSProperties] = value; + } + } + + return merged; +}; diff --git a/dataweaver/apps/web/src/hooks/use_cached_resize_values.ts b/dataweaver/apps/web/src/hooks/use_cached_resize_values.ts new file mode 100644 index 00000000..5bff2a9f --- /dev/null +++ b/dataweaver/apps/web/src/hooks/use_cached_resize_values.ts @@ -0,0 +1,57 @@ +import { useEffect, useRef } from 'react'; +import { Resize } from '~/utilities/resize'; + +/** + * Hook to cache values that depend on window resize. + * + * The key assumption is that cached values will not change unless the window + * size has changed, allowing for performance optimization by avoiding expensive + * recalculations. + * + * Under the hood we only attach a single resize listener no matter how many + * elements are using this hook for improved performance. + * + * @param callback A callback function that computes values to cache. + * @returns A getter where the first argument is an optional `invalidate` + * boolean. Pass `true` to force re-computation regardless of resize state. If + * additional arguments are passed they are forwarded as second value onwards. + */ +export function useCachedResizeValues< + TCallbackArgs extends unknown[], + TCallbackReturn, +>( + callback: (...args: TCallbackArgs) => TCallbackReturn, +): (invalidate?: boolean, ...args: TCallbackArgs) => TCallbackReturn { + const cachedValueRef = useRef(undefined); + const resizeRef = useRef(null); + + useEffect(() => { + return () => { + if (resizeRef.current) { + resizeRef.current.cleanup(); + resizeRef.current = null; + } + }; + }, []); + + return (invalidate = false, ...args: TCallbackArgs): TCallbackReturn => { + // Initialize resize instance if not already done + if (!resizeRef.current) resizeRef.current = new Resize(); + + // If we have a cached value and neither a resize nor an explicit invalidation + // has occurred since it was cached, return it as is + if ( + cachedValueRef.current !== undefined && + !resizeRef.current.resized && + !invalidate + ) { + return cachedValueRef.current; + } + + // Otherwise; recompute and cache, clearing the resize dirty flag + resizeRef.current.clear(); + const result = callback(...args); + cachedValueRef.current = result; + return result; + }; +} diff --git a/dataweaver/apps/web/src/styles/includes.scss b/dataweaver/apps/web/src/styles/includes.scss index ca30e14b..1fd4eab4 100644 --- a/dataweaver/apps/web/src/styles/includes.scss +++ b/dataweaver/apps/web/src/styles/includes.scss @@ -1,4 +1,5 @@ @forward "./includes/breakpoints.module"; @forward "./includes/helpers.module"; +@forward "./includes/typography.module"; @forward "./includes/z-indices.module"; @forward "@package/tokens/scss"; diff --git a/dataweaver/apps/web/src/styles/includes/_typography.module.scss b/dataweaver/apps/web/src/styles/includes/_typography.module.scss new file mode 100644 index 00000000..47201c7a --- /dev/null +++ b/dataweaver/apps/web/src/styles/includes/_typography.module.scss @@ -0,0 +1,28 @@ +// TODO: ATM we're not loading the font-family. Fix this +@mixin type-title { + font-family: "Google Sans", sans-serif; + font-size: 16px; + font-weight: 700; + line-height: 22px; +} + +@mixin type-body { + font-family: "Google Sans", sans-serif; + font-size: 14px; + font-weight: 400; + line-height: 22px; +} + +@mixin type-label { + font-family: "Google Sans", sans-serif; + font-size: 12px; + font-weight: 500; + line-height: 1.33; +} + +@mixin type-tab { + font-family: "Google Sans", sans-serif; + font-size: 10px; + font-weight: 500; + line-height: 1.4; +} diff --git a/dataweaver/apps/web/src/utilities/resize.ts b/dataweaver/apps/web/src/utilities/resize.ts new file mode 100644 index 00000000..eb04c472 --- /dev/null +++ b/dataweaver/apps/web/src/utilities/resize.ts @@ -0,0 +1,48 @@ +type Callback = () => void; + +let isListening = false; +const CALLBACKS = new Set(); + +const emitCallbacks = () => { + for (const callback of CALLBACKS) callback(); +}; + +export class Resize { + #resized = false; + + constructor() { + CALLBACKS.add(this.#onResize); + this.#attachListenerIfFirst(); + } + + get resized(): boolean { + return this.#resized; + } + + readonly #onResize = (): void => { + this.#resized = true; + }; + + readonly clear = (): void => { + this.#resized = false; + }; + + readonly cleanup = (): void => { + CALLBACKS.delete(this.#onResize); + this.#detachListenerIfLast(); + }; + + readonly #attachListenerIfFirst = (): void => { + if (!isListening) { + window.addEventListener('resize', emitCallbacks); + isListening = true; + } + }; + + readonly #detachListenerIfLast = (): void => { + if (CALLBACKS.size === 0 && isListening) { + window.removeEventListener('resize', emitCallbacks); + isListening = false; + } + }; +} diff --git a/dataweaver/apps/web/types.d.ts b/dataweaver/apps/web/types.d.ts new file mode 100755 index 00000000..1ade946d --- /dev/null +++ b/dataweaver/apps/web/types.d.ts @@ -0,0 +1,28 @@ +import 'react'; + +declare global { + /** + * Helper to make given props required within a union type. + * + * Use this when you have a union type and want to ensure that certain + * properties are required for each member of the union, without affecting the + * overall structure of the types. + * + * @example + * ```ts + * type A = { a: number; common?: string }; + * type B = { b: string; common?: string }; + * type Union = A | B; + * + * type Result = WithRequired; + * // Result is { a: number; common: string } | { b: string; common: string } + * ``` + */ + type WithRequired = Omit & Required>; +} + +declare module 'react' { + interface CSSProperties { + [key: `--${string}`]: string | number | undefined; + } +} diff --git a/dataweaver/biome.json b/dataweaver/biome.json index cb63c7fc..bbf171d3 100644 --- a/dataweaver/biome.json +++ b/dataweaver/biome.json @@ -62,10 +62,6 @@ "useTemplate": "error", "useThrowNewError": "error", "useThrowOnlyError": "error", - "useNamingConvention": { - "level": "error", - "options": { "strictCase": false } - }, "useConsistentArrayType": { "level": "error", "options": { "syntax": "shorthand" } diff --git a/dataweaver/packages/tokens/dist/colors.css b/dataweaver/packages/tokens/dist/colors.css index 4253bc2d..566c5060 100644 --- a/dataweaver/packages/tokens/dist/colors.css +++ b/dataweaver/packages/tokens/dist/colors.css @@ -2,9 +2,16 @@ :root { --color-surface-base: 247 252 255; - --color-surface-raised: 255 255 255; --color-surface-decorator: 227 231 237; - --color-text-primary: 32 33 36; - --color-text-secondary: 68 71 70; - --color-text-strong: 0 0 0; + --color-button-base: 194 231 255; + --color-button-base-hover: 173 214 240; + --color-button-content: 0 0 0; + --color-button-content-hover: 0 0 0; + --color-card-base: 255 255 255; + --color-card-base-selected: 11 87 208; + --color-card-content: 32 33 36; + --color-card-content-muted: 68 71 70; + --color-card-content-subtle: 196 199 197; + --color-card-shadow: 0 0 0; + --color-card-skeleton: 242 242 242; } diff --git a/dataweaver/packages/tokens/dist/tokens.ts b/dataweaver/packages/tokens/dist/tokens.ts index d7f66b63..ee816f24 100644 --- a/dataweaver/packages/tokens/dist/tokens.ts +++ b/dataweaver/packages/tokens/dist/tokens.ts @@ -6,11 +6,18 @@ export const BREAKPOINT_DESKTOP = 1600; export const COLORS = { 'surface-base': '247, 252, 255', - 'surface-raised': '255, 255, 255', 'surface-decorator': '227, 231, 237', - 'text-primary': '32, 33, 36', - 'text-secondary': '68, 71, 70', - 'text-strong': '0, 0, 0', + 'button-base': '194, 231, 255', + 'button-base-hover': '173, 214, 240', + 'button-content': '0, 0, 0', + 'button-content-hover': '0, 0, 0', + 'card-base': '255, 255, 255', + 'card-base-selected': '11, 87, 208', + 'card-content': '32, 33, 36', + 'card-content-muted': '68, 71, 70', + 'card-content-subtle': '196, 199, 197', + 'card-shadow': '0, 0, 0', + 'card-skeleton': '242, 242, 242', } as const; export type Easing = [number, number, number, number]; diff --git a/dataweaver/packages/tokens/src/colors.json b/dataweaver/packages/tokens/src/colors.json index 973d6b57..e76851a6 100644 --- a/dataweaver/packages/tokens/src/colors.json +++ b/dataweaver/packages/tokens/src/colors.json @@ -1,8 +1,15 @@ { "surface-base": [247, 252, 255], - "surface-raised": [255, 255, 255], "surface-decorator": [227, 231, 237], - "text-primary": [32, 33, 36], - "text-secondary": [68, 71, 70], - "text-strong": [0, 0, 0] + "button-base": [194, 231, 255], + "button-base-hover": [173, 214, 240], + "button-content": [0, 0, 0], + "button-content-hover": [0, 0, 0], + "card-base": [255, 255, 255], + "card-base-selected": [11, 87, 208], + "card-content": [32, 33, 36], + "card-content-muted": [68, 71, 70], + "card-content-subtle": [196, 199, 197], + "card-shadow": [0, 0, 0], + "card-skeleton": [242, 242, 242] } diff --git a/dataweaver/pnpm-lock.yaml b/dataweaver/pnpm-lock.yaml index 841b0609..38d0035d 100644 --- a/dataweaver/pnpm-lock.yaml +++ b/dataweaver/pnpm-lock.yaml @@ -53,6 +53,9 @@ importers: react-dom: specifier: ^19.2.6 version: 19.2.6(react@19.2.6) + recharts: + specifier: ^3.8.1 + version: 3.8.1(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react-is@19.2.6)(react@19.2.6)(redux@5.0.1) tldraw: specifier: ^5.0.1 version: 5.0.1(@floating-ui/dom@1.7.6)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -1361,10 +1364,27 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@reduxjs/toolkit@2.12.0': + resolution: {integrity: sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==} + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + '@sindresorhus/merge-streams@4.0.0': resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -1557,6 +1577,33 @@ packages: '@tldraw/validate@5.0.1': resolution: {integrity: sha512-iahdvUjHa3ONn6/xHdsb5yhIpcA26Ge6zuBQqRhOJGikU353mW4B6jmQTU8B/bEsEiH1SMs/Gm+IJxqGPDP29Q==} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + '@types/node@25.9.1': resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} @@ -1668,6 +1715,50 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1677,6 +1768,9 @@ packages: supports-color: optional: true + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -1694,6 +1788,9 @@ packages: error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + es-toolkit@1.47.0: + resolution: {integrity: sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw==} + esbuild@0.28.0: resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} engines: {node: '>=18'} @@ -1702,6 +1799,9 @@ packages: eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1814,6 +1914,12 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} + immer@10.2.0: + resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} + + immer@11.1.8: + resolution: {integrity: sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==} + immutable@5.1.5: resolution: {integrity: sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==} @@ -1827,6 +1933,10 @@ packages: ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -2120,6 +2230,21 @@ packages: peerDependencies: react: ^19.2.6 + react-is@19.2.6: + resolution: {integrity: sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==} + + react-redux@9.3.0: + resolution: {integrity: sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==} + peerDependencies: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -2158,10 +2283,29 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} + recharts@3.8.1: + resolution: {integrity: sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==} + engines: {node: '>=18'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + redux-thunk@3.1.0: + resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} + peerDependencies: + redux: ^5.0.0 + + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + reselect@5.1.1: + resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -2326,6 +2470,9 @@ packages: resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} engines: {node: '>=10.0.0'} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tldraw@5.0.1: resolution: {integrity: sha512-WPmjov5uKU27ybP0abttWYtDBXR2BHBEYRTqSeMOnJs78OHD+JmLlVCOtl826pqsrMrwZzahMcGwRY9Qs3jzHg==} peerDependencies: @@ -2384,6 +2531,9 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + victory-vendor@37.3.6: + resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} + w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} @@ -3538,8 +3688,24 @@ snapshots: '@radix-ui/rect@1.1.1': {} + '@reduxjs/toolkit@2.12.0(react-redux@9.3.0(@types/react@19.2.15)(react@19.2.6)(redux@5.0.1))(react@19.2.6)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@standard-schema/utils': 0.3.0 + immer: 11.1.8 + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + reselect: 5.1.1 + optionalDependencies: + react: 19.2.6 + react-redux: 9.3.0(@types/react@19.2.15)(react@19.2.6)(redux@5.0.1) + '@sindresorhus/merge-streams@4.0.0': {} + '@standard-schema/spec@1.1.0': {} + + '@standard-schema/utils@0.3.0': {} + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -3791,6 +3957,30 @@ snapshots: dependencies: '@tldraw/utils': 5.0.1 + '@types/d3-array@3.2.2': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + '@types/node@25.9.1': dependencies: undici-types: 7.24.6 @@ -3888,10 +4078,50 @@ snapshots: csstype@3.2.3: {} + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-ease@3.0.1: {} + + d3-format@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + debug@4.4.3: dependencies: ms: 2.1.3 + decimal.js-light@2.5.1: {} + detect-libc@2.1.2: optional: true @@ -3905,6 +4135,8 @@ snapshots: dependencies: is-arrayish: 0.2.1 + es-toolkit@1.47.0: {} + esbuild@0.28.0: optionalDependencies: '@esbuild/aix-ppc64': 0.28.0 @@ -3936,6 +4168,8 @@ snapshots: eventemitter3@4.0.7: {} + eventemitter3@5.0.4: {} + fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} @@ -4033,6 +4267,10 @@ snapshots: ignore@7.0.5: {} + immer@10.2.0: {} + + immer@11.1.8: {} + immutable@5.1.5: {} import-fresh@3.3.1: @@ -4044,6 +4282,8 @@ snapshots: ini@1.3.8: {} + internmap@2.0.3: {} + is-arrayish@0.2.1: {} is-extglob@2.1.1: {} @@ -4372,6 +4612,17 @@ snapshots: react: 19.2.6 scheduler: 0.27.0 + react-is@19.2.6: {} + + react-redux@9.3.0(@types/react@19.2.15)(react@19.2.6)(redux@5.0.1): + dependencies: + '@types/use-sync-external-store': 0.0.6 + react: 19.2.6 + use-sync-external-store: 1.6.0(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + redux: 5.0.1 + react-remove-scroll-bar@2.3.8(@types/react@19.2.15)(react@19.2.6): dependencies: react: 19.2.6 @@ -4403,8 +4654,36 @@ snapshots: readdirp@5.0.0: {} + recharts@3.8.1(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react-is@19.2.6)(react@19.2.6)(redux@5.0.1): + dependencies: + '@reduxjs/toolkit': 2.12.0(react-redux@9.3.0(@types/react@19.2.15)(react@19.2.6)(redux@5.0.1))(react@19.2.6) + clsx: 2.1.1 + decimal.js-light: 2.5.1 + es-toolkit: 1.47.0 + eventemitter3: 5.0.4 + immer: 10.2.0 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-is: 19.2.6 + react-redux: 9.3.0(@types/react@19.2.15)(react@19.2.6)(redux@5.0.1) + reselect: 5.1.1 + tiny-invariant: 1.3.3 + use-sync-external-store: 1.6.0(react@19.2.6) + victory-vendor: 37.3.6 + transitivePeerDependencies: + - '@types/react' + - redux + + redux-thunk@3.1.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux@5.0.1: {} + require-from-string@2.0.2: {} + reselect@5.1.1: {} + resolve-from@4.0.0: {} reusify@1.1.0: {} @@ -4622,6 +4901,8 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + tiny-invariant@1.3.3: {} + tldraw@5.0.1(@floating-ui/dom@1.7.6)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: '@tiptap/core': 3.23.6(@tiptap/pm@3.23.6) @@ -4684,6 +4965,23 @@ snapshots: util-deprecate@1.0.2: {} + victory-vendor@37.3.6: + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + w3c-keyname@2.2.8: {} which@1.3.1: From 77b911fd3d2cacd0fc95b009756d1d281782e0a2 Mon Sep 17 00:00:00 2001 From: Paulo Ferreira Jorge Date: Mon, 1 Jun 2026 19:00:48 +0100 Subject: [PATCH 15/20] Extend Setup --- dataweaver/FRONTEND.md | 4 +- .../apps/web/src/app/(atlas)/layout.tsx | 12 ++ .../apps/web/src/app/{ => (atlas)}/page.tsx | 0 dataweaver/apps/web/src/app/layout.tsx | 4 +- .../components/elements/button.module.scss | 14 +- .../web/src/components/elements/button.tsx | 7 +- .../components/elements/card/base.module.scss | 6 +- .../web/src/components/elements/card/base.tsx | 31 +++- .../elements/card/chart/chart.module.scss | 2 +- .../components/elements/card/chart/chart.tsx | 35 +++-- .../web/src/components/elements/card/index.ts | 2 +- .../elements/card/{text => }/text.module.scss | 0 .../web/src/components/elements/card/text.tsx | 20 +++ .../components/elements/card/text/text.tsx | 29 ---- .../{card/text => }/skeleton.module.scss | 0 .../elements/{card/text => }/skeleton.tsx | 22 ++- .../primitives/screen_reader.module.scss | 4 +- .../components/primitives/screen_reader.tsx | 2 +- .../web/src/components/scopes/atlas/atlas.tsx | 111 +++++++------- .../web/src/components/scopes/atlas/config.ts | 8 - .../src/components/scopes/atlas/helpers.ts | 117 ++++++++------- .../components/scopes/atlas/shapes/card.tsx | 138 ++++++++++++++++++ .../scopes/atlas/shapes/card_chart.tsx | 115 --------------- .../scopes/atlas/shapes/card_text.tsx | 103 ------------- .../src/components/scopes/atlas/use_atlas.ts | 44 ++++++ .../web/src/components/scopes/page_home.tsx | 102 +++++++------ .../apps/web/src/functions/merge_styles.ts | 22 +-- 27 files changed, 493 insertions(+), 461 deletions(-) create mode 100644 dataweaver/apps/web/src/app/(atlas)/layout.tsx rename dataweaver/apps/web/src/app/{ => (atlas)}/page.tsx (100%) rename dataweaver/apps/web/src/components/elements/card/{text => }/text.module.scss (100%) create mode 100644 dataweaver/apps/web/src/components/elements/card/text.tsx delete mode 100644 dataweaver/apps/web/src/components/elements/card/text/text.tsx rename dataweaver/apps/web/src/components/elements/{card/text => }/skeleton.module.scss (100%) rename dataweaver/apps/web/src/components/elements/{card/text => }/skeleton.tsx (57%) delete mode 100644 dataweaver/apps/web/src/components/scopes/atlas/config.ts create mode 100644 dataweaver/apps/web/src/components/scopes/atlas/shapes/card.tsx delete mode 100644 dataweaver/apps/web/src/components/scopes/atlas/shapes/card_chart.tsx delete mode 100644 dataweaver/apps/web/src/components/scopes/atlas/shapes/card_text.tsx create mode 100644 dataweaver/apps/web/src/components/scopes/atlas/use_atlas.ts diff --git a/dataweaver/FRONTEND.md b/dataweaver/FRONTEND.md index 2481267b..965e027c 100644 --- a/dataweaver/FRONTEND.md +++ b/dataweaver/FRONTEND.md @@ -172,11 +172,11 @@ Drive visual variants and boolean state through `data-*` attributes on the container — never through className flags: ```tsx -
    +
    ``` ```scss -.container[data-state="selected"] .card { … } +.container[data-is-loading="true"] .card { … } ``` ### 3.4 Design tokens diff --git a/dataweaver/apps/web/src/app/(atlas)/layout.tsx b/dataweaver/apps/web/src/app/(atlas)/layout.tsx new file mode 100644 index 00000000..00c36529 --- /dev/null +++ b/dataweaver/apps/web/src/app/(atlas)/layout.tsx @@ -0,0 +1,12 @@ +import type { ReactNode } from 'react'; +import { AtlasProvider } from '~/components/scopes/atlas/atlas'; + +interface AtlasLayoutProps { + children: ReactNode; +} + +const AtlasLayout = ({ children }: AtlasLayoutProps) => { + return {children}; +}; + +export default AtlasLayout; diff --git a/dataweaver/apps/web/src/app/page.tsx b/dataweaver/apps/web/src/app/(atlas)/page.tsx similarity index 100% rename from dataweaver/apps/web/src/app/page.tsx rename to dataweaver/apps/web/src/app/(atlas)/page.tsx diff --git a/dataweaver/apps/web/src/app/layout.tsx b/dataweaver/apps/web/src/app/layout.tsx index c86d1df5..93cef21f 100644 --- a/dataweaver/apps/web/src/app/layout.tsx +++ b/dataweaver/apps/web/src/app/layout.tsx @@ -3,11 +3,11 @@ import '~/styles/core.scss'; import type { ReactNode } from 'react'; import { MotionProvider } from '~/components/foundations/motion_provider'; -interface LayoutProps { +interface RootLayoutProps { children: ReactNode; } -const RootLayout = ({ children }: LayoutProps) => { +const RootLayout = ({ children }: RootLayoutProps) => { return ( diff --git a/dataweaver/apps/web/src/components/elements/button.module.scss b/dataweaver/apps/web/src/components/elements/button.module.scss index ee5394f6..25738253 100644 --- a/dataweaver/apps/web/src/components/elements/button.module.scss +++ b/dataweaver/apps/web/src/components/elements/button.module.scss @@ -24,9 +24,17 @@ transition-property: color, background; } - @include hover { - color: rgb(var(--color-button-content-hover)); - background: rgb(var(--color-button-base-hover)); + // TODO: Implement disabled style + &[disabled] { + cursor: not-allowed; + opacity: 0.4; + } + + &:not([disabled]) { + @include hover { + color: rgb(var(--color-button-content-hover)); + background: rgb(var(--color-button-base-hover)); + } } } diff --git a/dataweaver/apps/web/src/components/elements/button.tsx b/dataweaver/apps/web/src/components/elements/button.tsx index 8d6ac224..7753999e 100644 --- a/dataweaver/apps/web/src/components/elements/button.tsx +++ b/dataweaver/apps/web/src/components/elements/button.tsx @@ -23,13 +23,17 @@ interface ColorScheme { type ButtonProps = { /** If left `undefined`, the button will use the default app color scheme. */ colorScheme?: ColorScheme; -} & Omit, 'children'> & + + /** @default false */ + isDisabled?: boolean; +} & Omit, 'disabled' | 'children'> & (WithIconOnly | WithChildrenAndIcon); export const Button = ({ icon: Icon, children, colorScheme, + isDisabled = false, ...rest }: ButtonProps) => { const hasChildren = Boolean(children); @@ -41,6 +45,7 @@ export const Button = ({ {...rest} className={mergeClassNames(s.container, rest.className)} data-shape={shape} + disabled={isDisabled} style={mergeStyles( colorScheme && { '--color-button-base': colorScheme.base, diff --git a/dataweaver/apps/web/src/components/elements/card/base.module.scss b/dataweaver/apps/web/src/components/elements/card/base.module.scss index 545d499f..f91f7ff7 100644 --- a/dataweaver/apps/web/src/components/elements/card/base.module.scss +++ b/dataweaver/apps/web/src/components/elements/card/base.module.scss @@ -26,7 +26,7 @@ transition: transform 0.5s $ease-out; } - [data-state="selected"] & { + [data-is-selected="true"] & { transform: translateY(calc(var(--actions-height) * -1)); } } @@ -51,7 +51,7 @@ border-color 0.5s $ease-out; } - [data-state="selected"] & { + [data-is-selected="true"] & { border-color: rgb(var(--color-card-base-selected)); transform: translateY(var(--actions-height)); } @@ -68,7 +68,7 @@ flex-shrink: 0; padding: 0 28px 18px; - [data-state="loading"] & { + [data-loading="true"] & { visibility: hidden; pointer-events: none; } diff --git a/dataweaver/apps/web/src/components/elements/card/base.tsx b/dataweaver/apps/web/src/components/elements/card/base.tsx index 6c162fe2..8b679f47 100644 --- a/dataweaver/apps/web/src/components/elements/card/base.tsx +++ b/dataweaver/apps/web/src/components/elements/card/base.tsx @@ -5,30 +5,46 @@ import { Button } from '~/components/elements/button'; import { useCachedResizeValues } from '~/hooks/use_cached_resize_values'; import s from './base.module.scss'; -export type CardState = 'loading' | 'default' | 'selected'; +/** The card's two orthogonal, independently-settable states. */ +export interface CardState { + isLoading: boolean; + isSelected: boolean; +} interface CardAction { icon: ComponentType>; label: string; onClick?: () => void; + + /** @default false */ + isDisabled?: boolean; } -interface CardProps { - state: CardState; +interface CardProps extends CardState { actions: CardAction[]; content: ReactNode; - /** **Note**: This isn't shown while `state` is `loading`. */ + /** **Note**: This isn't shown while `isLoading`. */ footer?: ReactNode; } -export const CardBase = ({ state, actions, content, footer }: CardProps) => { +export const CardBase = ({ + isLoading, + isSelected, + actions, + content, + footer, +}: CardProps) => { const getCachedCanScroll = useCachedResizeValues((element: HTMLElement) => { return element.scrollHeight > element.clientHeight; }); return ( -
    +
    {actions.map((action, index) => (
    @@ -57,7 +74,7 @@ export const CardBase = ({ state, actions, content, footer }: CardProps) => { }} >
    {content}
    -
    +
    {footer}
    diff --git a/dataweaver/apps/web/src/components/elements/card/chart/chart.module.scss b/dataweaver/apps/web/src/components/elements/card/chart/chart.module.scss index fe767932..e5640fdc 100644 --- a/dataweaver/apps/web/src/components/elements/card/chart/chart.module.scss +++ b/dataweaver/apps/web/src/components/elements/card/chart/chart.module.scss @@ -2,7 +2,7 @@ display: flex; flex-direction: column; row-gap: 8px; - margin-bottom: 20px; + margin-bottom: 16px; color: rgb(var(--color-card-content)); } diff --git a/dataweaver/apps/web/src/components/elements/card/chart/chart.tsx b/dataweaver/apps/web/src/components/elements/card/chart/chart.tsx index 22a662f4..4de64183 100644 --- a/dataweaver/apps/web/src/components/elements/card/chart/chart.tsx +++ b/dataweaver/apps/web/src/components/elements/card/chart/chart.tsx @@ -1,6 +1,8 @@ 'use client'; import { useState } from 'react'; +import type { CardState } from '~/components/elements/card/base'; +import { Skeleton } from '~/components/elements/skeleton'; import { IconLineGraph } from '~/components/primitives/icons/line_graph'; import { IconTable } from '~/components/primitives/icons/table'; import s from './chart.module.scss'; @@ -17,26 +19,17 @@ const TABS: TabItem[] = [ const CHART_WIDTH = 356; const CHART_HEIGHT = 200; -interface CardChartProps { +interface CardChartProps extends Pick { title?: string; description?: string; data?: ChartDatum[]; - - /** - * Renders skeleton lines in place of the body while content loads. - * - * @default false - */ - isLoading?: boolean; } export const CardChart = ({ + isLoading, data, title, description, - - // TODO: Support loading state here - // isLoading = false, }: CardChartProps) => { const [activeTab, setActiveTab] = useState('chart'); @@ -49,13 +42,23 @@ export const CardChart = ({
    )} - + {isLoading ? ( + + ) : ( + <> + - {activeTab === 'chart' && data && ( - - )} + {activeTab === 'chart' && data && ( + + )} - {activeTab === 'table' && data && } + {activeTab === 'table' && data && } + + )} ); }; diff --git a/dataweaver/apps/web/src/components/elements/card/index.ts b/dataweaver/apps/web/src/components/elements/card/index.ts index 968bb6ec..f9c7abc8 100644 --- a/dataweaver/apps/web/src/components/elements/card/index.ts +++ b/dataweaver/apps/web/src/components/elements/card/index.ts @@ -1,6 +1,6 @@ import { CardBase } from './base'; import { CardChart } from './chart/chart'; -import { CardText } from './text/text'; +import { CardText } from './text'; export const Card = { Base: CardBase, diff --git a/dataweaver/apps/web/src/components/elements/card/text/text.module.scss b/dataweaver/apps/web/src/components/elements/card/text.module.scss similarity index 100% rename from dataweaver/apps/web/src/components/elements/card/text/text.module.scss rename to dataweaver/apps/web/src/components/elements/card/text.module.scss diff --git a/dataweaver/apps/web/src/components/elements/card/text.tsx b/dataweaver/apps/web/src/components/elements/card/text.tsx new file mode 100644 index 00000000..cbe3af2e --- /dev/null +++ b/dataweaver/apps/web/src/components/elements/card/text.tsx @@ -0,0 +1,20 @@ +import type { CardState } from '~/components/elements/card/base'; +import { Skeleton } from '~/components/elements/skeleton'; +import s from './text.module.scss'; + +interface CardTextProps extends Pick { + title?: string; + body?: string; +} + +export const CardText = ({ title, body, isLoading }: CardTextProps) => { + return ( + <> + {title &&

    {title}

    } + + {isLoading && } + + {body &&
    {body}
    } + + ); +}; diff --git a/dataweaver/apps/web/src/components/elements/card/text/text.tsx b/dataweaver/apps/web/src/components/elements/card/text/text.tsx deleted file mode 100644 index 4899deb4..00000000 --- a/dataweaver/apps/web/src/components/elements/card/text/text.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Skeleton } from './skeleton'; -import s from './text.module.scss'; - -/** Skeleton line widths (% of the body) mirroring the loading design. */ -const SKELETON_LINE_WIDTHS = [100, 84, 92, 72]; - -interface CardTextProps { - title?: string; - body?: string; - - /** - * Renders skeleton lines in place of the body while content loads. - * - * @default false - */ - isLoading?: boolean; -} - -export const CardText = ({ title, body, isLoading = false }: CardTextProps) => { - return ( - <> - {title &&

    {title}

    } - - {isLoading && } - - {body &&
    {body}
    } - - ); -}; diff --git a/dataweaver/apps/web/src/components/elements/card/text/skeleton.module.scss b/dataweaver/apps/web/src/components/elements/skeleton.module.scss similarity index 100% rename from dataweaver/apps/web/src/components/elements/card/text/skeleton.module.scss rename to dataweaver/apps/web/src/components/elements/skeleton.module.scss diff --git a/dataweaver/apps/web/src/components/elements/card/text/skeleton.tsx b/dataweaver/apps/web/src/components/elements/skeleton.tsx similarity index 57% rename from dataweaver/apps/web/src/components/elements/card/text/skeleton.tsx rename to dataweaver/apps/web/src/components/elements/skeleton.tsx index ada91009..6e1b0640 100644 --- a/dataweaver/apps/web/src/components/elements/card/text/skeleton.tsx +++ b/dataweaver/apps/web/src/components/elements/skeleton.tsx @@ -1,16 +1,30 @@ import { ScreenReaderOnly } from '~/components/primitives/screen_reader'; import s from './skeleton.module.scss'; +/** Skeleton line widths (% of the body) mirroring the loading design. */ +const DEFAULT_LINE_WIDTHS = [100, 84, 92, 72]; + interface SkeletonProps { - /** One line per entry; each value is the line's width as a % of the row. */ - widths: number[]; + /** + * One line per entry; each value is the line's width as a % of the row. + * + * @default '[100, 84, 92, 72]' + */ + widths?: number[]; - /** Announced to assistive tech while content loads. @default 'Loading…' */ + /** + * Announced to assistive tech while content loads. + * + * @default 'Loading…' + */ label?: string; } /** Animated placeholder lines shown in place of content while it loads. */ -export const Skeleton = ({ widths, label = 'Loading…' }: SkeletonProps) => { +export const Skeleton = ({ + widths = DEFAULT_LINE_WIDTHS, + label = 'Loading…', +}: SkeletonProps) => { return (
    {widths.map((width, index) => ( diff --git a/dataweaver/apps/web/src/components/primitives/screen_reader.module.scss b/dataweaver/apps/web/src/components/primitives/screen_reader.module.scss index 4d70fa9a..457a2abe 100644 --- a/dataweaver/apps/web/src/components/primitives/screen_reader.module.scss +++ b/dataweaver/apps/web/src/components/primitives/screen_reader.module.scss @@ -1,3 +1,3 @@ -.screenReaderOnly { - @include screen-reader-only; +.screen-reader-only { + @include screen-reader-only; } diff --git a/dataweaver/apps/web/src/components/primitives/screen_reader.tsx b/dataweaver/apps/web/src/components/primitives/screen_reader.tsx index 818a700d..46d37bc0 100644 --- a/dataweaver/apps/web/src/components/primitives/screen_reader.tsx +++ b/dataweaver/apps/web/src/components/primitives/screen_reader.tsx @@ -11,7 +11,7 @@ export const ScreenReaderOnly = ({ ...rest }: ScreenReaderOnlyProps) => { return ( - + {children} ); diff --git a/dataweaver/apps/web/src/components/scopes/atlas/atlas.tsx b/dataweaver/apps/web/src/components/scopes/atlas/atlas.tsx index aeb10bf3..6071bcd7 100644 --- a/dataweaver/apps/web/src/components/scopes/atlas/atlas.tsx +++ b/dataweaver/apps/web/src/components/scopes/atlas/atlas.tsx @@ -1,72 +1,82 @@ 'use client'; -import { - createContext, - type ReactNode, - useCallback, - useContext, - useRef, -} from 'react'; -import { type Editor, Tldraw } from 'tldraw'; +import { type ReactNode, useCallback, useRef } from 'react'; +import { createShapeId, type Editor, type TLComponents, Tldraw } from 'tldraw'; import s from './atlas.module.scss'; -import { ATLAS_COMPONENTS, ATLAS_SHAPES } from './config'; -import { type AtlasContent, contentToShape, gridPosition } from './helpers'; +import { Grid } from './components/grid'; +import { contentToShape, gridPosition } from './helpers'; +import { ShapeCardUtil } from './shapes/card'; +import { type Atlas, AtlasContext } from './use_atlas'; -interface ContextValue { - /** Push new content onto the atlas - auto-placed in the grid. */ - add: (content: AtlasContent) => void; -} +const ATLAS_COMPONENTS = { Grid } as const satisfies TLComponents; + +const ATLAS_SHAPES = [ShapeCardUtil] as const; -const AtlasContext = createContext(null); +type Operation = (editor: Editor) => void; -interface ProviderProps { - /** Content placed onto the atlas when the editor first mounts. */ - initialContent?: AtlasContent[]; - children?: ReactNode; +interface AtlasProviderProps { + children: ReactNode; } -export const Atlas = ({ initialContent = [], children }: ProviderProps) => { - const hasPopulatedInitialContentRef = useRef(false); +export const AtlasProvider = ({ children }: AtlasProviderProps) => { const editorRef = useRef(null); + const pendingShapesQueueRef = useRef<((editor: Editor) => void)[]>([]); // TODO: For now using count for positioning - we likely want to use a smarter // approach that accounts for deleted content and doesn't rely on order of // addition, etc later once we hook up to real data - const countRef = useRef(0); - - const populate = useCallback((editor: Editor, content: AtlasContent) => { - const shape = contentToShape(content, gridPosition(countRef.current)); + const countRef = useRef(-1); - // If we don't get a shape back (e.g. unsupported content type) - skip it - if (!shape) return; + const withEditor = useCallback((operation: Operation) => { + const editor = editorRef.current; - editor.createShape(shape); - countRef.current += 1; + // If we have the editor available - perform the operation immediately + if (editor) operation(editor); + // Otherwise queue it up for when the editor is ready (e.g. once mounted) + else pendingShapesQueueRef.current.push(operation); }, []); - const mounted = useCallback( - (editor: Editor) => { - editorRef.current = editor; + const add: Atlas['add'] = useCallback( + (content) => { + const shapeId = createShapeId(); + const index = countRef.current++; - // Render the dot grid (camera-tracked via the 'Grid' component slot) - editor.updateInstanceState({ isGridMode: true }); + // First: Create the shape with any immediately available content + withEditor((e) => + e.createShape(contentToShape(shapeId, content, gridPosition(index))), + ); - // Populate content (if we haven't already, e.g. due to fast refresh) - if (!hasPopulatedInitialContentRef.current) { - hasPopulatedInitialContentRef.current = true; - for (const content of initialContent) populate(editor, content); - } + // Then: Return handle that allows for future updates to the shape as more + // content becomes available, or for the shape to be removed + return { + id: shapeId, + variant: content.variant, + update(props) { + withEditor((e) => + e.updateShape({ id: shapeId, type: 'card', props }), + ); + }, + remove() { + withEditor((e) => e.deleteShapes([shapeId])); + }, + }; }, - [initialContent, populate], + [withEditor], ); - const add = useCallback( - (content: AtlasContent) => { - const editor = editorRef.current; - if (editor) populate(editor, content); - }, - [populate], - ); + const mounted = useCallback((editor: Editor) => { + // Render the dot grid (camera-tracked via the 'Grid' component slot) + editor.updateInstanceState({ isGridMode: true }); + + editorRef.current = editor; + countRef.current = 0; + + // If there were any operations queued up before the editor was ready, + // run them now that we've mounted + const queued = pendingShapesQueueRef.current; + pendingShapesQueueRef.current = []; + for (const operation of queued) operation(editor); + }, []); return ( @@ -81,10 +91,3 @@ export const Atlas = ({ initialContent = [], children }: ProviderProps) => { ); }; - -/** Hook for accessing the atlas context. */ -export const useAtlas = () => { - const value = useContext(AtlasContext); - if (!value) throw new Error("'useAtlas' must be used within 'Atlas'."); - return value; -}; diff --git a/dataweaver/apps/web/src/components/scopes/atlas/config.ts b/dataweaver/apps/web/src/components/scopes/atlas/config.ts deleted file mode 100644 index 580322b7..00000000 --- a/dataweaver/apps/web/src/components/scopes/atlas/config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { TLComponents } from 'tldraw'; -import { Grid } from './components/grid'; -import { CardChartShapeUtil } from './shapes/card_chart'; -import { CardTextShapeUtil } from './shapes/card_text'; - -export const ATLAS_COMPONENTS = { Grid } as const satisfies TLComponents; - -export const ATLAS_SHAPES = [CardTextShapeUtil, CardChartShapeUtil] as const; diff --git a/dataweaver/apps/web/src/components/scopes/atlas/helpers.ts b/dataweaver/apps/web/src/components/scopes/atlas/helpers.ts index eec90a28..68a4efe7 100644 --- a/dataweaver/apps/web/src/components/scopes/atlas/helpers.ts +++ b/dataweaver/apps/web/src/components/scopes/atlas/helpers.ts @@ -1,20 +1,38 @@ -import { createShapeId, type TLCreateShapePartial } from 'tldraw'; +import type { TLCreateShapePartial, TLShape, TLShapeId } from 'tldraw'; import type { ChartDatum } from '~/components/elements/card/chart/data_line_chart'; -import type { CardChartShape } from './shapes/card_chart'; -import type { CardTextShape } from './shapes/card_text'; + +export type CardVariant = 'text' | 'chart'; + +interface BaseContent { + isLoading?: boolean; + followUp?: string; +} + +interface TextContent extends BaseContent { + variant: 'text'; + title?: string; + body?: string; +} + +interface ChartContent extends BaseContent { + variant: 'chart'; + title?: string; + description?: string; + data?: ChartDatum[]; +} /** - * App-level description of a thing to mount on the canvas. This is the layer's - * public vocabulary — callers describe content, not tldraw shapes. + * App-level description of a thing to mount on the canvas. The atlas only + * renders cards; the variant decides which content fields are valid. */ -export type AtlasContent = - | { kind: 'card'; title: string; body?: string; isLoading?: boolean } - | { - kind: 'card-chart'; - title: string; - description: string; - data: ChartDatum[]; - }; +export type AtlasContent = TextContent | ChartContent; + +/** + * Flat view of every possible content field — variant-specific fields become + * optional. Used by the shape util, since tldraw stores props as a flat record. + */ +export type CardContentFields = Omit & + Omit; interface Position { x: number; @@ -35,53 +53,50 @@ export const gridPosition = (index: number): Position => ({ y: GRID.originY + Math.floor(index / GRID.columns) * GRID.stepY, }); -type AtlasShapePartial = - | TLCreateShapePartial - | TLCreateShapePartial; +// Per-variant default canvas footprint +const VARIANT_SIZE: Record = { + text: { w: 360, h: 440 }, + chart: { w: 420, h: 520 }, +}; export const contentToShape = ( + shapeId: TLShapeId, content: AtlasContent, position: Position, -): AtlasShapePartial | null => { +): TLCreateShapePartial> => { const baseProps = { - id: createShapeId(), + id: shapeId, x: position.x, y: position.y, + type: 'card' as const, }; - switch (content.kind) { - case 'card': - return { - ...baseProps, - type: 'card', - props: { - w: 360, - h: 440, - title: content.title, - body: content.body ?? '', - isLoading: content.isLoading ?? false, - }, - }; - - case 'card-chart': - return { - ...baseProps, - type: 'card-chart', - props: { - w: 420, - h: 520, - title: content.title, - description: content.description, - data: content.data, - }, - }; - - default: { - console.warn(`Attempting to draw unsupported content in Atlas:`, content); + const shapeProps = { + ...VARIANT_SIZE[content.variant], + isLoading: content.isLoading ?? false, + followUp: content.followUp, + }; - // Note: We purposely don't want to throw to improve DX when iterating on - // content types. If there's not content type we can just ignore it - return null; - } + if (content.variant === 'chart') { + return { + ...baseProps, + props: { + ...shapeProps, + variant: 'chart', + title: content.title, + description: content.description, + data: content.data, + }, + }; } + + return { + ...baseProps, + props: { + ...shapeProps, + variant: 'text', + title: content.title, + body: content.body, + }, + }; }; diff --git a/dataweaver/apps/web/src/components/scopes/atlas/shapes/card.tsx b/dataweaver/apps/web/src/components/scopes/atlas/shapes/card.tsx new file mode 100644 index 00000000..27ad47c4 --- /dev/null +++ b/dataweaver/apps/web/src/components/scopes/atlas/shapes/card.tsx @@ -0,0 +1,138 @@ +import { + HTMLContainer, + type RecordProps, + Rectangle2d, + ShapeUtil, + T, + type TLShape, +} from 'tldraw'; +import { Button } from '~/components/elements/button'; +import { Card } from '~/components/elements/card'; +import { IconBarChart } from '~/components/primitives/icons/bar_chart'; +import { IconDelete } from '~/components/primitives/icons/delete'; +import { IconExport } from '~/components/primitives/icons/export'; +import { IconPencil } from '~/components/primitives/icons/pencil'; +import type { + CardContentFields, + CardVariant, +} from '~/components/scopes/atlas/helpers'; + +type ShapeCardProps = CardContentFields & { + w: number; + h: number; + variant: CardVariant; + isLoading: boolean; +}; + +// Register the custom shape within tldraw +declare module 'tldraw' { + interface TLGlobalShapePropsMap { + card: ShapeCardProps; + } +} + +type ShapeCard = TLShape<'card'>; + +export class ShapeCardUtil extends ShapeUtil { + static override type = 'card' as const; + + static override props: RecordProps = { + w: T.number, + h: T.number, + variant: T.literalEnum('text', 'chart'), + title: T.string.optional(), + description: T.string.optional(), + body: T.string.optional(), + data: T.arrayOf( + T.object({ year: T.number, emissions: T.number }), + ).optional(), + isLoading: T.boolean, + followUp: T.string.optional(), + }; + + override getDefaultProps = (): ShapeCardProps => { + return { w: 360, h: 440, variant: 'text', isLoading: false }; + }; + + override getGeometry = (shape: ShapeCard) => { + return new Rectangle2d({ + width: shape.props.w, + height: shape.props.h, + isFilled: true, + }); + }; + + #getActions = (shape: ShapeCard, isLoading: boolean) => { + const deleteAction = { + icon: IconDelete, + label: 'Delete', + onClick: () => this.editor.deleteShapes([shape.id]), + }; + + // TODO: Hook up action(s) once supported + const exportAction = { + icon: IconExport, + label: 'Export', + isDisabled: isLoading, + }; + + if (shape.props.variant === 'chart') { + return [ + { icon: IconBarChart, label: 'View chart', isDisabled: isLoading }, + exportAction, + deleteAction, + ]; + } + + return [exportAction, deleteAction]; + }; + + #renderContent = (shape: ShapeCard, isLoading: boolean) => { + const { variant, title, description, body, data } = shape.props; + + if (variant === 'chart') { + return ( + + ); + } + + return ; + }; + + override component = (shape: ShapeCard) => { + const { w, h, isLoading, followUp } = shape.props; + const isSelected = this.editor.getSelectedShapeIds().includes(shape.id); + + return ( + + event.stopPropagation()} + > + {followUp} + + ) + } + /> + + ); + }; + + // Disable default TLDraw events + override canResize = () => false; + override hideSelectionBoundsFg = () => true; + override hideSelectionBoundsBg = () => true; + override getIndicatorPath = () => undefined; +} diff --git a/dataweaver/apps/web/src/components/scopes/atlas/shapes/card_chart.tsx b/dataweaver/apps/web/src/components/scopes/atlas/shapes/card_chart.tsx deleted file mode 100644 index 58b3a93f..00000000 --- a/dataweaver/apps/web/src/components/scopes/atlas/shapes/card_chart.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { - HTMLContainer, - type RecordProps, - Rectangle2d, - ShapeUtil, - T, - type TLShape, -} from 'tldraw'; -import { Button } from '~/components/elements/button'; -import { Card } from '~/components/elements/card'; -import type { CardState } from '~/components/elements/card/base'; -import type { ChartDatum } from '~/components/elements/card/chart/data_line_chart'; -import { IconBarChart } from '~/components/primitives/icons/bar_chart'; -import { IconDelete } from '~/components/primitives/icons/delete'; -import { IconExport } from '~/components/primitives/icons/export'; -import { IconPencil } from '~/components/primitives/icons/pencil'; - -interface CardChartShapeProps { - w: number; - h: number; - title?: string; - description?: string; - data?: ChartDatum[]; - isLoading: boolean; -} - -// Register the custom shape within tldraw -declare module 'tldraw' { - interface TLGlobalShapePropsMap { - 'card-chart': CardChartShapeProps; - } -} - -export type CardChartShape = TLShape<'card-chart'>; - -export class CardChartShapeUtil extends ShapeUtil { - static override type = 'card-chart' as const; - - static override props: RecordProps = { - w: T.number, - h: T.number, - title: T.string.optional(), - description: T.string.optional(), - data: T.arrayOf( - T.object({ year: T.number, emissions: T.number }), - ).optional(), - isLoading: T.boolean, - }; - - override getDefaultProps = (): CardChartShapeProps => { - return { w: 420, h: 520, data: [], isLoading: false }; - }; - - override getGeometry = (shape: CardChartShape) => { - return new Rectangle2d({ - width: shape.props.w, - height: shape.props.h, - isFilled: true, - }); - }; - - #getState = (shape: CardChartShape): CardState => { - if (shape.props.isLoading) return 'loading'; - - const isSelected = this.editor.getSelectedShapeIds().includes(shape.id); - if (isSelected) return 'selected'; - - return 'default'; - }; - - override component = (shape: CardChartShape) => { - const { w, h, title, description, data, isLoading } = shape.props; - - return ( - - this.editor.deleteShapes([shape.id]), - }, - ]} - content={ - - } - footer={ - - } - /> - - ); - }; - - // Disable default TLDraw events - override canResize = () => false; - override hideSelectionBoundsFg = () => true; - override hideSelectionBoundsBg = () => true; - override getIndicatorPath = () => undefined; -} diff --git a/dataweaver/apps/web/src/components/scopes/atlas/shapes/card_text.tsx b/dataweaver/apps/web/src/components/scopes/atlas/shapes/card_text.tsx deleted file mode 100644 index ec8db63e..00000000 --- a/dataweaver/apps/web/src/components/scopes/atlas/shapes/card_text.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { - HTMLContainer, - type RecordProps, - Rectangle2d, - ShapeUtil, - T, - type TLShape, -} from 'tldraw'; -import { Button } from '~/components/elements/button'; -import { Card } from '~/components/elements/card'; -import type { CardState } from '~/components/elements/card/base'; -import { IconDelete } from '~/components/primitives/icons/delete'; -import { IconExport } from '~/components/primitives/icons/export'; -import { IconPencil } from '~/components/primitives/icons/pencil'; - -interface CardTextShapeProps { - w: number; - h: number; - title?: string; - body?: string; - isLoading: boolean; -} - -// Register the custom shape within tldraw -declare module 'tldraw' { - interface TLGlobalShapePropsMap { - card: CardTextShapeProps; - } -} - -export type CardTextShape = TLShape<'card'>; - -export class CardTextShapeUtil extends ShapeUtil { - static override type = 'card' as const; - - static override props: RecordProps = { - w: T.number, - h: T.number, - title: T.string.optional(), - body: T.string.optional(), - isLoading: T.boolean, - }; - - override getDefaultProps = (): CardTextShapeProps => { - return { w: 360, h: 440, isLoading: false }; - }; - - override getGeometry = (shape: CardTextShape) => { - return new Rectangle2d({ - width: shape.props.w, - height: shape.props.h, - isFilled: true, - }); - }; - - #getState = (shape: CardTextShape): CardState => { - if (shape.props.isLoading) return 'loading'; - - const isSelected = this.editor.getSelectedShapeIds().includes(shape.id); - if (isSelected) return 'selected'; - - return 'default'; - }; - - override component = (shape: CardTextShape) => { - const { w, h, title, body, isLoading } = shape.props; - - return ( - - this.editor.deleteShapes([shape.id]), - }, - ]} - content={ - - } - footer={ - - } - /> - - ); - }; - - // Disable default TLDraw events - override canResize = () => false; - override hideSelectionBoundsFg = () => true; - override hideSelectionBoundsBg = () => true; - override getIndicatorPath = () => undefined; -} diff --git a/dataweaver/apps/web/src/components/scopes/atlas/use_atlas.ts b/dataweaver/apps/web/src/components/scopes/atlas/use_atlas.ts new file mode 100644 index 00000000..2f75f011 --- /dev/null +++ b/dataweaver/apps/web/src/components/scopes/atlas/use_atlas.ts @@ -0,0 +1,44 @@ +'use client'; + +import { createContext, useContext } from 'react'; +import type { TLShapeId } from 'tldraw'; +import type { AtlasContent, CardVariant } from './helpers'; + +/** The content shape that corresponds to a given card variant. */ +type ContentForVariant = Extract< + AtlasContent, + { variant: TVariant } +>; + +/** + * Handle returned by `atlas.add(...)`. Use it to populate a card with real + * data as it arrives, or to remove the card from the canvas. The handle is + * typed against the variant passed to `add`, so updates can only set fields + * that belong to that variant. + */ +interface CardHandle { + readonly id: TLShapeId; + readonly variant: TVariant; + update(props: Partial, 'variant'>>): void; + remove(): void; +} + +/** Public atlas surface — what `useAtlas()` returns. */ +export interface Atlas { + add( + content: ContentForVariant, + ): CardHandle; +} + +/** @internal */ +export const AtlasContext = createContext(null); + +/** Read the atlas — must be used inside ``. */ +export const useAtlas = (): Atlas => { + const context = useContext(AtlasContext); + if (!context) { + throw new Error("'useAtlas' must be used within 'AtlasProvider'."); + } + + return context; +}; diff --git a/dataweaver/apps/web/src/components/scopes/page_home.tsx b/dataweaver/apps/web/src/components/scopes/page_home.tsx index 29cabdbf..f1f79f1c 100644 --- a/dataweaver/apps/web/src/components/scopes/page_home.tsx +++ b/dataweaver/apps/web/src/components/scopes/page_home.tsx @@ -1,52 +1,60 @@ 'use client'; -import dynamic from 'next/dynamic'; -import type { ChartDatum } from '~/components/elements/card/chart/data_line_chart'; -import type { AtlasContent } from './atlas/helpers'; - -const Atlas = dynamic( - () => import('./atlas/atlas').then((module) => module.Atlas), - { ssr: false }, -); - -// Mock greenhouse-gas emissions (Mt CO₂e) -const MOCK_GHG_DATA: ChartDatum[] = [ - { year: 2000, emissions: 820 }, - { year: 2003, emissions: 905 }, - { year: 2006, emissions: 980 }, - { year: 2009, emissions: 1045 }, - { year: 2012, emissions: 1180 }, - { year: 2015, emissions: 1260 }, - { year: 2018, emissions: 1390 }, - { year: 2021, emissions: 1470 }, -] as const; - -// TODO: Drop once we have a real data source to feed initial content from -const DUMMY_CONTENT: AtlasContent[] = [ - { - kind: 'card', - title: 'Greenhouse gas emissions in Africa', - isLoading: true, - }, - { - kind: 'card', - title: 'Key insights when evaluating greenhouse gas emissions', - body: 'Emissions per capita remain low relative to other regions, energy access is the dominant driver, and land-use change accounts for a large share of the total. missions per capita remain low relative to other regions, energy access is the dominant driver, and land-use change accounts for a large share of the total. missions per capita remain low relative to other regions, energy access is the dominant driver, and land-use change accounts for a large share of the total. missions per capita remain low relative to other regions, energy access is the dominant driver, and land-use change accounts for a large share of the total. missions per capita remain low relative to other regions, energy access is the dominant driver, and land-use change accounts for a large share of the total. missions per capita remain low relative to other regions, energy access is the dominant driver, and land-use change accounts for a large share of the total.', - }, - { - kind: 'card', - title: 'Key insights when evaluating greenhouse gas emissions', - body: 'Emissions per capita remain low relative to other regions, energy access is the dominant driver, and land-use change accounts for a large share of the total.', - }, - { - kind: 'card-chart', - title: 'Greenhouse gas emissions in Africa', - description: - 'The chart above tracks total GHG emissions across Africa over time.', - data: MOCK_GHG_DATA, - }, -]; +import { useEffect } from 'react'; +import { useAtlas } from './atlas/use_atlas'; export const PageHome = () => { - return ; + const atlas = useAtlas(); + + // TODO: This is only temporary to show case adding / update flow + useEffect(() => { + const cardText = atlas.add({ + variant: 'text', + title: 'Key insights when evaluating greenhouse gas emissions', + isLoading: true, + }); + + const cardChart = atlas.add({ + variant: 'chart', + title: 'Greenhouse gas emissions in Africa', + isLoading: true, + }); + + // Populate them as 'data arrives' + const cardTimeout = setTimeout(() => { + cardText.update({ + body: 'Emissions per capita remain low relative to other regions, energy access is the dominant driver, and land-use change accounts for a large share of the total.', + isLoading: false, + followUp: 'What are the key drivers of these trends?', + }); + }, 1500); + + const chartTimeout = setTimeout(() => { + cardChart.update({ + description: + 'The chart above tracks total GHG emissions across Africa over time.', + data: [ + { year: 2000, emissions: 820 }, + { year: 2003, emissions: 905 }, + { year: 2006, emissions: 980 }, + { year: 2009, emissions: 1045 }, + { year: 2012, emissions: 1180 }, + { year: 2015, emissions: 1260 }, + { year: 2018, emissions: 1390 }, + { year: 2021, emissions: 1470 }, + ], + isLoading: false, + followUp: 'What are the key drivers of these trends?', + }); + }, 2500); + + return () => { + clearTimeout(cardTimeout); + clearTimeout(chartTimeout); + cardText.remove(); + cardChart.remove(); + }; + }, [atlas]); + + return null; }; diff --git a/dataweaver/apps/web/src/functions/merge_styles.ts b/dataweaver/apps/web/src/functions/merge_styles.ts index 51d38239..bb53beb7 100644 --- a/dataweaver/apps/web/src/functions/merge_styles.ts +++ b/dataweaver/apps/web/src/functions/merge_styles.ts @@ -1,4 +1,4 @@ -import type { CSSProperties } from "react"; +import type { CSSProperties } from 'react'; /** * Helper to merge multiple style objects into one, filtering out any invalid @@ -13,17 +13,17 @@ import type { CSSProperties } from "react"; * // Result: { color: 'blue', aspectRatio: '16 / 9' } */ export const mergeStyles = (...styles: Array) => { - const merged: CSSProperties = {}; + const merged: CSSProperties = {}; - for (const style of styles) { - // Ignore if style isn't set - if (!style) continue; + for (const style of styles) { + // Ignore if style isn't set + if (!style) continue; - // Iterate over each style property and only assign it if it's defined - for (const [key, value] of Object.entries(style)) { - if (value !== undefined) merged[key as keyof CSSProperties] = value; - } - } + // Iterate over each style property and only assign it if it's defined + for (const [key, value] of Object.entries(style)) { + if (value !== undefined) merged[key as keyof CSSProperties] = value; + } + } - return merged; + return merged; }; From 9e5a140cae33f5ae8c68defd9b894bc5d9a1d77b Mon Sep 17 00:00:00 2001 From: Paulo Ferreira Jorge Date: Tue, 2 Jun 2026 14:31:11 +0100 Subject: [PATCH 16/20] Implement Gemini Feedback --- .../web/src/components/elements/button.tsx | 1 + .../web/src/components/elements/card/base.tsx | 2 + .../components/elements/card/chart/chart.tsx | 45 ++++++----- ...dule.scss => conditional_tabs.module.scss} | 7 +- .../elements/card/chart/conditional_tabs.tsx | 75 +++++++++++++++++++ .../components/elements/card/chart/tabs.tsx | 44 ----------- .../web/src/components/elements/card/text.tsx | 4 +- 7 files changed, 103 insertions(+), 75 deletions(-) rename dataweaver/apps/web/src/components/elements/card/chart/{tabs.module.scss => conditional_tabs.module.scss} (98%) create mode 100644 dataweaver/apps/web/src/components/elements/card/chart/conditional_tabs.tsx delete mode 100644 dataweaver/apps/web/src/components/elements/card/chart/tabs.tsx diff --git a/dataweaver/apps/web/src/components/elements/button.tsx b/dataweaver/apps/web/src/components/elements/button.tsx index 7753999e..c5a36cbf 100644 --- a/dataweaver/apps/web/src/components/elements/button.tsx +++ b/dataweaver/apps/web/src/components/elements/button.tsx @@ -5,6 +5,7 @@ import s from './button.module.scss'; interface WithIconOnly { icon: ComponentType>; + 'aria-label': string; children?: never; } diff --git a/dataweaver/apps/web/src/components/elements/card/base.tsx b/dataweaver/apps/web/src/components/elements/card/base.tsx index 8b679f47..e8e4a3be 100644 --- a/dataweaver/apps/web/src/components/elements/card/base.tsx +++ b/dataweaver/apps/web/src/components/elements/card/base.tsx @@ -57,6 +57,8 @@ export const CardBase = ({ 'content-hover': 'var(--color-card-base-selected)', }} aria-label={action.label} + // Prevent tldraw from triggering canvas gestures (e.g. dragging) + onPointerDown={(event) => event.stopPropagation()} onClick={action.onClick} isDisabled={action.isDisabled} /> diff --git a/dataweaver/apps/web/src/components/elements/card/chart/chart.tsx b/dataweaver/apps/web/src/components/elements/card/chart/chart.tsx index 4de64183..9a25c4fd 100644 --- a/dataweaver/apps/web/src/components/elements/card/chart/chart.tsx +++ b/dataweaver/apps/web/src/components/elements/card/chart/chart.tsx @@ -1,19 +1,13 @@ 'use client'; -import { useState } from 'react'; import type { CardState } from '~/components/elements/card/base'; import { Skeleton } from '~/components/elements/skeleton'; import { IconLineGraph } from '~/components/primitives/icons/line_graph'; import { IconTable } from '~/components/primitives/icons/table'; import s from './chart.module.scss'; +import { ConditionalTabs } from './conditional_tabs'; import { type ChartDatum, DataLineChart } from './data_line_chart'; import { DataTable } from './data_table'; -import { type TabItem, Tabs } from './tabs'; - -const TABS: TabItem[] = [ - { id: 'chart', label: 'Chart', icon: IconLineGraph }, - { id: 'table', label: 'Table', icon: IconTable }, -] as const; // TODO: Get dynamically instead of hard coding here const CHART_WIDTH = 356; @@ -31,8 +25,6 @@ export const CardChart = ({ title, description, }: CardChartProps) => { - const [activeTab, setActiveTab] = useState('chart'); - return ( <> {(title || description) && ( @@ -42,22 +34,29 @@ export const CardChart = ({
    )} - {isLoading ? ( + {isLoading || !data ? ( ) : ( - <> - - - {activeTab === 'chart' && data && ( - - )} - - {activeTab === 'table' && data && } - + + ), + }, + { + icon: IconTable, + label: 'Table', + children: , + }, + ]} + /> )} ); diff --git a/dataweaver/apps/web/src/components/elements/card/chart/tabs.module.scss b/dataweaver/apps/web/src/components/elements/card/chart/conditional_tabs.module.scss similarity index 98% rename from dataweaver/apps/web/src/components/elements/card/chart/tabs.module.scss rename to dataweaver/apps/web/src/components/elements/card/chart/conditional_tabs.module.scss index 1dda1fc7..a2effab4 100644 --- a/dataweaver/apps/web/src/components/elements/card/chart/tabs.module.scss +++ b/dataweaver/apps/web/src/components/elements/card/chart/conditional_tabs.module.scss @@ -1,10 +1,7 @@ -.container { - margin-bottom: 16px; - border-bottom: 1px solid rgb(var(--color-card-content-subtle)); -} - .tabs-container { display: flex; + margin-bottom: 16px; + border-bottom: 1px solid rgb(var(--color-card-content-subtle)); } .tab { diff --git a/dataweaver/apps/web/src/components/elements/card/chart/conditional_tabs.tsx b/dataweaver/apps/web/src/components/elements/card/chart/conditional_tabs.tsx new file mode 100644 index 00000000..44c5eb21 --- /dev/null +++ b/dataweaver/apps/web/src/components/elements/card/chart/conditional_tabs.tsx @@ -0,0 +1,75 @@ +import { AnimatePresence, m } from 'motion/react'; +import type { ComponentPropsWithRef, ComponentType, ReactNode } from 'react'; +import { useId, useState } from 'react'; +import s from './conditional_tabs.module.scss'; + +interface Tab { + icon: ComponentType>; + + /** **Note**: Make sure each label is unique as it's used as key. */ + label: string; + children: ReactNode; +} + +interface ConditionalTabsProps { + tabs: Tab[]; +} + +// TODO: Animate line between tab changes +export const ConditionalTabs = ({ tabs }: ConditionalTabsProps) => { + const baseId = useId(); + + const [activeIndex, setActiveIndex] = useState(0); + + const tabId = (index: number) => `${baseId}-tab-${index}`; + + const panelId = (index: number) => `${baseId}-tabpanel-${index}`; + + const activeTab = tabs[activeIndex]; + + return ( + <> +
    + {tabs.map((tab, index) => { + const isActive = index === activeIndex; + + return ( + + ); + })} +
    + + + {activeTab && ( + + {activeTab.children} + + )} + + + ); +}; diff --git a/dataweaver/apps/web/src/components/elements/card/chart/tabs.tsx b/dataweaver/apps/web/src/components/elements/card/chart/tabs.tsx deleted file mode 100644 index 2efda1af..00000000 --- a/dataweaver/apps/web/src/components/elements/card/chart/tabs.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import type { ComponentPropsWithRef, ComponentType } from 'react'; -import s from './tabs.module.scss'; - -export interface TabItem { - id: string; - label: string; - icon: ComponentType>; -} - -interface TabsProps { - tabs: TabItem[]; - activeTab: string; - onChange: (id: string) => void; -} - -// TODO: Animate line between tab changes -export const Tabs = ({ tabs, activeTab, onChange }: TabsProps) => { - return ( -
    -
    - {tabs.map((tab) => { - const isActive = tab.id === activeTab; - - return ( - - ); - })} -
    -
    - ); -}; diff --git a/dataweaver/apps/web/src/components/elements/card/text.tsx b/dataweaver/apps/web/src/components/elements/card/text.tsx index cbe3af2e..293a1ca0 100644 --- a/dataweaver/apps/web/src/components/elements/card/text.tsx +++ b/dataweaver/apps/web/src/components/elements/card/text.tsx @@ -12,9 +12,7 @@ export const CardText = ({ title, body, isLoading }: CardTextProps) => { <> {title &&

    {title}

    } - {isLoading && } - - {body &&
    {body}
    } + {isLoading ? : body &&
    {body}
    } ); }; From 259598279a6cec7d3c99bb3f7a1bd47e5feefcb5 Mon Sep 17 00:00:00 2001 From: Paulo Ferreira Jorge Date: Wed, 3 Jun 2026 11:27:53 +0100 Subject: [PATCH 17/20] Self Review --- .../apps/web/src/components/elements/card/chart/chart.tsx | 3 +++ .../web/src/components/elements/card/chart/data_line_chart.tsx | 2 -- .../apps/web/src/components/elements/card/chart/data_table.tsx | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dataweaver/apps/web/src/components/elements/card/chart/chart.tsx b/dataweaver/apps/web/src/components/elements/card/chart/chart.tsx index 9a25c4fd..3f099475 100644 --- a/dataweaver/apps/web/src/components/elements/card/chart/chart.tsx +++ b/dataweaver/apps/web/src/components/elements/card/chart/chart.tsx @@ -16,6 +16,9 @@ const CHART_HEIGHT = 200; interface CardChartProps extends Pick { title?: string; description?: string; + + // TODO: Atm data rendered within the card is very specific to the emissions + // dataset. Let's make it more generic once we have real data to work with data?: ChartDatum[]; } diff --git a/dataweaver/apps/web/src/components/elements/card/chart/data_line_chart.tsx b/dataweaver/apps/web/src/components/elements/card/chart/data_line_chart.tsx index 2c0be48e..e35a2eaa 100644 --- a/dataweaver/apps/web/src/components/elements/card/chart/data_line_chart.tsx +++ b/dataweaver/apps/web/src/components/elements/card/chart/data_line_chart.tsx @@ -8,8 +8,6 @@ export interface ChartDatum { emissions: number; } -// Recharts colours SVG presentation attributes, where CSS `var()` doesn't -// resolve — so source concrete values from the JS token export. const LINE_COLOR = `rgb(${COLORS['card-base-selected']})`; const GRID_COLOR = `rgb(${COLORS['surface-decorator']})`; const AXIS_COLOR = `rgb(${COLORS['card-content-muted']})`; diff --git a/dataweaver/apps/web/src/components/elements/card/chart/data_table.tsx b/dataweaver/apps/web/src/components/elements/card/chart/data_table.tsx index 90a5561d..347ae36c 100644 --- a/dataweaver/apps/web/src/components/elements/card/chart/data_table.tsx +++ b/dataweaver/apps/web/src/components/elements/card/chart/data_table.tsx @@ -5,6 +5,7 @@ interface DataTableProps { data: ChartDatum[]; } +// TODO: This is temporary - either style it or render using recharts export const DataTable = ({ data }: DataTableProps) => { return ( From 7d05ce14a5756b1385a6e129cfc8c93100dd156b Mon Sep 17 00:00:00 2001 From: Paulo Ferreira Jorge Date: Wed, 3 Jun 2026 14:36:35 +0100 Subject: [PATCH 18/20] Add Type Support --- dataweaver/apps/web/src/app/layout.tsx | 12 ++++++++++++ .../web/src/styles/includes/_typography.module.scss | 1 - 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/dataweaver/apps/web/src/app/layout.tsx b/dataweaver/apps/web/src/app/layout.tsx index 93cef21f..349d0dbc 100644 --- a/dataweaver/apps/web/src/app/layout.tsx +++ b/dataweaver/apps/web/src/app/layout.tsx @@ -3,6 +3,9 @@ import '~/styles/core.scss'; import type { ReactNode } from 'react'; import { MotionProvider } from '~/components/foundations/motion_provider'; +const FONT_URL = + 'https://fonts.googleapis.com/css?family=Google+Sans:400,500,700&display=swap&lang=en'; + interface RootLayoutProps { children: ReactNode; } @@ -10,6 +13,15 @@ interface RootLayoutProps { const RootLayout = ({ children }: RootLayoutProps) => { return ( + + + + +
    {children}
    diff --git a/dataweaver/apps/web/src/styles/includes/_typography.module.scss b/dataweaver/apps/web/src/styles/includes/_typography.module.scss index 47201c7a..2ba85521 100644 --- a/dataweaver/apps/web/src/styles/includes/_typography.module.scss +++ b/dataweaver/apps/web/src/styles/includes/_typography.module.scss @@ -1,4 +1,3 @@ -// TODO: ATM we're not loading the font-family. Fix this @mixin type-title { font-family: "Google Sans", sans-serif; font-size: 16px; From f643bf317ccdb62134f317fdb78a2d38fb5f0032 Mon Sep 17 00:00:00 2001 From: Paulo Ferreira Jorge Date: Wed, 3 Jun 2026 17:00:20 +0100 Subject: [PATCH 19/20] Implement Atlas Controls --- .../components/elements/button.module.scss | 37 ++++- .../web/src/components/elements/button.tsx | 4 + .../components/elements/card/base.module.scss | 8 +- .../web/src/components/elements/card/base.tsx | 1 + .../card/chart/conditional_tabs.module.scss | 2 +- .../card/chart/data_table.module.scss | 2 +- .../components/primitives/icons/cursor.tsx | 18 +++ .../src/components/primitives/icons/hand.tsx | 18 +++ .../primitives/icons/insert_text.tsx | 18 +++ .../src/components/primitives/icons/minus.tsx | 15 ++ .../src/components/primitives/icons/plus.tsx | 18 +++ .../components/scopes/atlas/atlas.module.scss | 8 + .../web/src/components/scopes/atlas/atlas.tsx | 28 +++- .../atlas/components/controls.module.scss | 60 ++++++++ .../scopes/atlas/components/controls.tsx | 140 ++++++++++++++++++ .../web/src/components/scopes/atlas/config.ts | 49 ++++++ .../components/scopes/atlas/shapes/card.tsx | 1 + .../apps/web/src/functions/map_range.ts | 15 ++ .../styles/includes/_typography.module.scss | 14 +- dataweaver/packages/tokens/dist/colors.css | 8 +- dataweaver/packages/tokens/dist/tokens.ts | 8 +- dataweaver/packages/tokens/src/colors.json | 10 +- 22 files changed, 454 insertions(+), 28 deletions(-) create mode 100644 dataweaver/apps/web/src/components/primitives/icons/cursor.tsx create mode 100644 dataweaver/apps/web/src/components/primitives/icons/hand.tsx create mode 100644 dataweaver/apps/web/src/components/primitives/icons/insert_text.tsx create mode 100644 dataweaver/apps/web/src/components/primitives/icons/minus.tsx create mode 100644 dataweaver/apps/web/src/components/primitives/icons/plus.tsx create mode 100644 dataweaver/apps/web/src/components/scopes/atlas/components/controls.module.scss create mode 100644 dataweaver/apps/web/src/components/scopes/atlas/components/controls.tsx create mode 100644 dataweaver/apps/web/src/components/scopes/atlas/config.ts create mode 100644 dataweaver/apps/web/src/functions/map_range.ts diff --git a/dataweaver/apps/web/src/components/elements/button.module.scss b/dataweaver/apps/web/src/components/elements/button.module.scss index 25738253..a1f36f3f 100644 --- a/dataweaver/apps/web/src/components/elements/button.module.scss +++ b/dataweaver/apps/web/src/components/elements/button.module.scss @@ -1,6 +1,5 @@ .container { display: flex; - gap: 2px; align-items: center; justify-content: center; color: rgb(var(--color-button-content)); @@ -10,12 +9,34 @@ height: 28px; padding-inline: 12px 16px; border-radius: 14px; + + &[data-size="small"] { + column-gap: 2px; + height: 28px; + padding-inline: 12px 16px; + border-radius: 14px; + } + + &[data-size="large"] { + column-gap: 6px; + height: 46px; + padding-inline: 22px 24px; + border-radius: 23px; + } } &[data-shape="square"] { - width: 40px; - height: 40px; - border-radius: 20px; + &[data-size="small"] { + width: 28px; + height: 28px; + border-radius: 14px; + } + + &[data-size="large"] { + width: 40px; + height: 40px; + border-radius: 20px; + } } @include prefers-motion { @@ -45,5 +66,11 @@ } .children { - @include type-label; + [data-size="small"] & { + @include type-label-small; + } + + [data-size="large"] & { + @include type-label-large; + } } diff --git a/dataweaver/apps/web/src/components/elements/button.tsx b/dataweaver/apps/web/src/components/elements/button.tsx index c5a36cbf..3aa99fc2 100644 --- a/dataweaver/apps/web/src/components/elements/button.tsx +++ b/dataweaver/apps/web/src/components/elements/button.tsx @@ -22,6 +22,8 @@ interface ColorScheme { } type ButtonProps = { + size: 'small' | 'large'; + /** If left `undefined`, the button will use the default app color scheme. */ colorScheme?: ColorScheme; @@ -33,6 +35,7 @@ type ButtonProps = { export const Button = ({ icon: Icon, children, + size, colorScheme, isDisabled = false, ...rest @@ -46,6 +49,7 @@ export const Button = ({ {...rest} className={mergeClassNames(s.container, rest.className)} data-shape={shape} + data-size={size} disabled={isDisabled} style={mergeStyles( colorScheme && { diff --git a/dataweaver/apps/web/src/components/elements/card/base.module.scss b/dataweaver/apps/web/src/components/elements/card/base.module.scss index f91f7ff7..62ff1521 100644 --- a/dataweaver/apps/web/src/components/elements/card/base.module.scss +++ b/dataweaver/apps/web/src/components/elements/card/base.module.scss @@ -19,8 +19,8 @@ background: rgb(var(--color-card-base-selected)); border-radius: var(--corner-size) var(--corner-size) 0 0; box-shadow: - 0 1px 3px 1px rgb(var(--color-card-shadow) / 15%), - 0 1px 2px rgb(var(--color-card-shadow) / 30%); + 0 1px 3px 1px rgb(var(--color-shadow) / 15%), + 0 1px 2px rgb(var(--color-shadow) / 30%); @include prefers-motion { transition: transform 0.5s $ease-out; @@ -42,8 +42,8 @@ border: var(--border-thickness) solid transparent; border-radius: var(--corner-size); box-shadow: - 0 1px 3px 1px rgb(var(--color-card-shadow) / 15%), - 0 1px 2px rgb(var(--color-card-shadow) / 30%); + 0 1px 3px 1px rgb(var(--color-shadow) / 15%), + 0 1px 2px rgb(var(--color-shadow) / 30%); @include prefers-motion { transition: diff --git a/dataweaver/apps/web/src/components/elements/card/base.tsx b/dataweaver/apps/web/src/components/elements/card/base.tsx index e8e4a3be..94ef22b5 100644 --- a/dataweaver/apps/web/src/components/elements/card/base.tsx +++ b/dataweaver/apps/web/src/components/elements/card/base.tsx @@ -50,6 +50,7 @@ export const CardBase = ({ + + +
    + {Object.entries(TOOLS).map(([name, tool]) => ( +
    + + ); +}; diff --git a/dataweaver/apps/web/src/components/scopes/atlas/config.ts b/dataweaver/apps/web/src/components/scopes/atlas/config.ts new file mode 100644 index 00000000..37a1dd32 --- /dev/null +++ b/dataweaver/apps/web/src/components/scopes/atlas/config.ts @@ -0,0 +1,49 @@ +import type { TLComponents } from 'tldraw'; +import { Controls } from './components/controls'; +import { Grid } from './components/grid'; +import { ShapeCardUtil } from './shapes/card'; + +/** + * Component overrides for tldraw - this allows us to inject our own React + * components into the editor's UI via given 'slots'. + */ +export const ATLAS_COMPONENTS = { + Grid, + InFrontOfTheCanvas: Controls, +} as const satisfies TLComponents; + +/** The shapes that the Atlas supports. */ +export const ATLAS_SHAPES = [ShapeCardUtil] as const; + +/** Atlas min zoom level. This is the minimum zoom enforced by tldraw. */ +export const MIN_ZOOM = 0.25; + +/** Atlas max zoom level. This is the maximum zoom enforced by tldraw. */ +export const MAX_ZOOM = 3.25; + +/** The range the actual zoom is rescaled onto for display in the controls. */ +export const ZOOM_DISPLAY_RANGE = [0, 200] as const; + +/** + * How much the displayed zoom value changes per zoom in / out step. We divide + * the display range evenly (i.e. here 200 / 20 = 10 steps). + */ +const ZOOM_DISPLAY_STEP = 20; + +/** + * The discrete zoom levels the controls step through, generated so they stay + * evenly spaced across [MIN_ZOOM, MAX_ZOOM] — that even spacing is what makes + * the displayed value increment by a constant `ZOOM_DISPLAY_STEP`. + */ +const ZOOM_STEP_COUNT = + (ZOOM_DISPLAY_RANGE[1] - ZOOM_DISPLAY_RANGE[0]) / ZOOM_DISPLAY_STEP; + +/** + * The discrete zoom levels the controls step through, generated so they stay + * evenly spaced across [MIN_ZOOM, MAX_ZOOM] — that even spacing is what makes + * the displayed value increment by a constant `ZOOM_DISPLAY_STEP`. + */ +export const ZOOM_STEPS: readonly number[] = Array.from( + { length: ZOOM_STEP_COUNT + 1 }, + (_, index) => MIN_ZOOM + (index * (MAX_ZOOM - MIN_ZOOM)) / ZOOM_STEP_COUNT, +); diff --git a/dataweaver/apps/web/src/components/scopes/atlas/shapes/card.tsx b/dataweaver/apps/web/src/components/scopes/atlas/shapes/card.tsx index 27ad47c4..f5c647f9 100644 --- a/dataweaver/apps/web/src/components/scopes/atlas/shapes/card.tsx +++ b/dataweaver/apps/web/src/components/scopes/atlas/shapes/card.tsx @@ -119,6 +119,7 @@ export class ShapeCardUtil extends ShapeUtil { followUp && (