diff --git a/.changeset/dark-mode-prime-config.md b/.changeset/dark-mode-prime-config.md new file mode 100644 index 000000000..6ccc693c2 --- /dev/null +++ b/.changeset/dark-mode-prime-config.md @@ -0,0 +1,5 @@ +--- +'@viamrobotics/motion-tools': minor +--- + +Add `DarkMode` plugin diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index d7d51d1ad..508a2ee03 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -42,6 +42,7 @@ export default defineConfig({ { label: 'Plugins', items: [ + { label: '', link: '/plugins/dark-mode/' }, { label: '', link: '/plugins/debug/' }, { label: '', link: '/plugins/draw-service/' }, { label: '', link: '/plugins/measure-tool/' }, diff --git a/docs/src/content/docs/plugins/dark-mode.mdx b/docs/src/content/docs/plugins/dark-mode.mdx new file mode 100644 index 000000000..dcd8d2ff9 --- /dev/null +++ b/docs/src/content/docs/plugins/dark-mode.mdx @@ -0,0 +1,68 @@ +--- +title: +description: Cycle the motion-tools visualizer between system, dark, and light themes, defaulting to the user's system preference. +--- + +`` adds a theme button to the visualizer dashboard that cycles between three modes — **system**, **dark**, and **light**. It shows a sun/moon icon for system, a moon for dark, and a sun for light, and the choice is persisted across reloads. + +In **system** mode (the default) the theme follows the user's operating-system preference (`prefers-color-scheme`) **live** — if the OS switches (for example a sunset auto-dark schedule), the visualizer follows along. **dark** and **light** pin the theme regardless of the OS. Each press of the button advances `system → dark → light → system`. + +The visualizer follows the system preference on its own — mounting `` is only needed to give users a manual override. + +## Usage + +Mount `` anywhere inside ``: + +```svelte + + +
+ + + +
+``` + +## How it works + +`` owns the theme, stored as a `system` / `dark` / `light` mode (persisted to local storage, default `system`): + +- In `system` mode it sets **no** class on the document's root element, leaving the CSS `prefers-color-scheme` media query in charge — so the theme tracks the OS live. +- `dark` / `light` add a `.dark` / `.light` class to the root element that overrides the media query and pins the theme. + +Two things respond to that state: + +- **CSS** — the [`@viamrobotics/tailwind-config`](https://github.com/viamrobotics/prime/tree/main/packages/tailwind-config) dark theme reassigns its color tokens under the dark scope (the `prefers-color-scheme` media query in `system` mode, or the `.dark` class when pinned), so every semantic token (`text-default`, `bg-light`, `border-medium`, the status colors, …) flips with no markup changes. `color-scheme` follows the same class, so native controls and scrollbars match. +- **WebGL / JS surfaces** that can't read CSS tokens switch off the effective theme: the Tweakpane controls (`primeTheme` ↔ `primeThemeDark`), the scene backdrop and grid, the selection outline, and the measurement line. + +## Reading the theme + +Use `useDarkMode()` to read or cycle the theme from your own components anywhere inside ``. Most code wants `isDark` — the resolved boolean, which already accounts for the OS in `system` mode: + +```svelte + + +

Mode: {darkMode.current}

+

Currently {darkMode.isDark ? 'dark' : 'light'}

+ + + +{#if darkMode.current === ThemeModes.System} + Following the system preference +{/if} +``` + +| Member | Type | Description | +| ---------- | ------------------------------- | ---------------------------------------------------------------------------------- | +| `current` | `'system' \| 'dark' \| 'light'` | The chosen mode. Compare against the exported `ThemeModes` constants. | +| `isDark` | `boolean` | The active theme — `true` when dark is showing (resolves `system` against the OS). | +| `toggle()` | `() => void` | Advance to the next mode: `system → dark → light → system`. | + +`ThemeModes` (`{ System, Dark, Light }`) is exported for comparing `current`. Outside the provided context, the hook returns a light-mode default (`current: 'light'`, `isDark: false`) with a no-op `toggle()`. diff --git a/package.json b/package.json index 271ed744d..ce7c79c4b 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,6 @@ "@eslint/js": "10.0.1", "@playwright/test": "1.55.1", "@sentry/sveltekit": "10.10.0", - "@skeletonlabs/skeleton": "3.2.0", - "@skeletonlabs/skeleton-svelte": "1.5.1", "@sveltejs/adapter-static": "3.0.9", "@sveltejs/kit": "2.60.1", "@sveltejs/package": "2.5.7", @@ -77,7 +75,8 @@ "@viamrobotics/prime-core": "0.1.5", "@viamrobotics/sdk": "0.69.0", "@viamrobotics/svelte-sdk": "1.2.2", - "@viamrobotics/tweakpane-config": "0.1.1", + "@viamrobotics/tailwind-config": "1.1.0", + "@viamrobotics/tweakpane-config": "0.2.1", "@vitest/browser": "3.2.4", "@vitest/coverage-v8": "^3.2.4", "@zag-js/collapsible": "1.22.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ea9be5e92..a83d17861 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -79,12 +79,6 @@ importers: '@sentry/sveltekit': specifier: 10.10.0 version: 10.10.0(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.1.4(svelte@5.55.7)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1)))(svelte@5.55.7)(typescript@5.9.2)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1)))(svelte@5.55.7)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1)) - '@skeletonlabs/skeleton': - specifier: 3.2.0 - version: 3.2.0(tailwindcss@4.1.13) - '@skeletonlabs/skeleton-svelte': - specifier: 1.5.1 - version: 1.5.1(svelte@5.55.7) '@sveltejs/adapter-static': specifier: 3.0.9 version: 3.0.9(@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.1.4(svelte@5.55.7)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1)))(svelte@5.55.7)(typescript@5.9.2)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1))) @@ -160,9 +154,12 @@ importers: '@viamrobotics/svelte-sdk': specifier: 1.2.2 version: 1.2.2(@viamrobotics/sdk@0.69.0)(svelte@5.55.7) + '@viamrobotics/tailwind-config': + specifier: 1.1.0 + version: 1.1.0(tailwindcss@4.1.13) '@viamrobotics/tweakpane-config': - specifier: 0.1.1 - version: 0.1.1(svelte-tweakpane-ui@1.5.16(svelte@5.55.7))(svelte@5.55.7) + specifier: 0.2.1 + version: 0.2.1(svelte-tweakpane-ui@1.5.16(svelte@5.55.7))(svelte@5.55.7) '@vitest/browser': specifier: 3.2.4 version: 3.2.4(playwright@1.55.1)(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.6) @@ -885,6 +882,15 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@fontsource-variable/public-sans@5.2.7': + resolution: {integrity: sha512-4mvade2J3slKkvwRkS+p8T3szet/0vhWoSnuUJTVU81Uo2pRpSZY/Y8bSLRqpSwzIPxjVmRJ53oq6JKP/l/PSg==} + + '@fontsource-variable/roboto-mono@5.2.9': + resolution: {integrity: sha512-OzFO2AXlSGcXl/NcXS3CGjImb6rczCByPJ1C+Dzp9kkYOrUPyrGTuAtqPcmA/d+nZGX5oyOWKXLk5BrwVLYqkw==} + + '@fontsource-variable/space-grotesk@5.2.10': + resolution: {integrity: sha512-yJQO/o35/hAP3CFnpdFTwQku2yzJOae2HIpBmqkOVoxhhXJaQP3g+b6Jrz7u+eI7A5ZdCIf88uMWpBJdFiGr5w==} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -1556,16 +1562,6 @@ packages: resolution: {integrity: sha512-fMR2d+EHwbzBa0S1fp45SNUTProxmyFBp+DeBWWQOSP9IU6AH6ea2rqrpMAnp/skkcdW4z4LSRrOEpMZ5rWXLw==} engines: {node: '>= 14'} - '@skeletonlabs/skeleton-svelte@1.5.1': - resolution: {integrity: sha512-XYvIy0CPYYIGUV6TjGpUrjSkTJcuWxGqT7aKK+zXuMjVUGqNLAWrHabITRgWHvAlQwlo5V10pKxwphrQeunqWA==} - peerDependencies: - svelte: ^5.20.0 - - '@skeletonlabs/skeleton@3.2.0': - resolution: {integrity: sha512-pV1IXFr3uOfeS4ISHEvONGke25p2GRKVtbEtF11HPcM2P8UKBFyPaIT7bAmlVE1vdIlq5x77+F8b77+6LuvWWw==} - peerDependencies: - tailwindcss: ^4.0.0 - '@stablelib/base64@1.0.1': resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} @@ -1960,8 +1956,13 @@ packages: '@viamrobotics/sdk': '>=0.57' svelte: '>=5' - '@viamrobotics/tweakpane-config@0.1.1': - resolution: {integrity: sha512-oIRkTIjE+c5MLlwETi5l0WbpNrZa/qXcntjO2Aa5eBJSUsAIvdPrBR0mj6xJSgY5B0qGKymEmfZuSng1uF0v2A==} + '@viamrobotics/tailwind-config@1.1.0': + resolution: {integrity: sha512-2szwX3O1xgbSFG+/FdWRDkDXLL7osbt+O/sd1zG6apYtbqr+T5IP0VoRl7TM0V5s/QdXFVFVMoIKRFByHAAC3Q==} + peerDependencies: + tailwindcss: '>=4 <5' + + '@viamrobotics/tweakpane-config@0.2.1': + resolution: {integrity: sha512-5cMtSKQKfdawZ8mo3VcbHuXHDLLvaEvsM4unlfM/hMC7M6jVlVqcSSl42bcoE4Az+dV0nercNeSEgXI5KdsrFA==} peerDependencies: svelte: ^5.0.0 svelte-tweakpane-ui: ^1.5.16 @@ -2042,51 +2043,27 @@ packages: '@webgpu/types@0.1.66': resolution: {integrity: sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA==} - '@zag-js/accordion@1.27.1': - resolution: {integrity: sha512-c6Ny0M/I4gqhcemD7THNBIKiBXLgJnRLmT1oOSZdPmwWI8Y6HMS/As8DDV3hsyzV/X26dbZiuJtuylAepMN3bQ==} - '@zag-js/anatomy@1.22.1': resolution: {integrity: sha512-I5OvOuJBt6hEqbpqVkWCOEoDfGMnKuLx+S0h7Un5SyAwnif3F1dSqDYujU28bCy8FtKs36vsq/izxufXyiXSEg==} - '@zag-js/anatomy@1.27.1': - resolution: {integrity: sha512-vAgZ9RFm9wSucTMpbV1ZY2soZ6ZHQ9JAMOkLy1EPkWyE7+68VxzDh8rKc3Gq3LAjowgF2yR8tIe05njP9KFRjg==} - '@zag-js/anatomy@1.32.0': resolution: {integrity: sha512-99u8VBSt5wkP9ZluIV6lR06Pvyt0AQaTGnvhZkvSmbz5U5+1XwCU0Db7Q+dcQRpM7d0p1ViWFxNVHDuuwY25Fw==} '@zag-js/aria-hidden@1.22.1': resolution: {integrity: sha512-vPfAE35BfYPS1UbYRcNw8/kMl7uayE7LyRncK/gPMnoQMjmEKW0nXmD5WlCHFLdGX9WFGYTIde8k4U8ay+oqcg==} - '@zag-js/aria-hidden@1.27.1': - resolution: {integrity: sha512-8ax2IG0jOJnvNMb20INgxa5OS7jvr14dxuc8vgaNR9a0yfWzIuINp/O0FrN+8GNzaJzGMODwqGb0I7AOOIOAbw==} - '@zag-js/aria-hidden@1.32.0': resolution: {integrity: sha512-rgOSWjFMbeFwGPCdUWaSFbb4GM0Ovdr6sM0ilHmGQeXsrYkfg56R/o7I4/8P0kDIi/hDoE5TwBMUiGvKwWMLgA==} - '@zag-js/auto-resize@1.27.1': - resolution: {integrity: sha512-1hfHePSLs2AcESHUTzZhizn6lCCUeqwsWB4MsJwUzEqpGoW7DOKfOy27mfX50YlU7ttJOjlpYJ+Fp2g5FnzWWA==} - - '@zag-js/avatar@1.27.1': - resolution: {integrity: sha512-HTxhl+06CefIQIDnFvEdazCe0To3HDeYx+KrJ7UdBGUgGHVQ/n3/767QdrtIS26h71rTV9q7dMEsmr4ttugn6w==} - '@zag-js/collapsible@1.22.1': resolution: {integrity: sha512-vKfDe/fzm3ndDfaueqW/XgGaWCHVD8MuLFtRRyv3jX3ubdNYn5R/j7ftQURdYyqRlPI3Si50FWSAtOqtvs4y9Q==} '@zag-js/collection@1.22.1': resolution: {integrity: sha512-jjeSKALTH3iK2vTI6uAh2NCtS9n+e2r1cGERKCfNkbt86U6VSp9xiXqalUsEI4ovNIPcgg0+/nzixoVwFO1Vgg==} - '@zag-js/collection@1.27.1': - resolution: {integrity: sha512-zcf2GIpsACn0RUpNJSpRmbWOLbuiqMkRgZP4+Ub2Wy6lsYKi3Fou2A9o6Sm6HuNYMLJYRvos0oEEktHNJsLNyw==} - - '@zag-js/combobox@1.27.1': - resolution: {integrity: sha512-Q+fC5SQgsLNdZoaaG1TbSZbvgm5H/tjaAbOull2zyrQzoe20WLCTJgHCWmVm1X0u5mEKXVf8xISzys8a+LJxwg==} - '@zag-js/core@1.22.1': resolution: {integrity: sha512-4BNrwO9Tadq2Z0d2xSSQs4O/o3OarEHzXM2FQqx46vrwSE57qUghnZex429ZQ51fuk8AL5Lowt26a9JxE9sVPg==} - '@zag-js/core@1.27.1': - resolution: {integrity: sha512-C35SJO7O5fyzgk+mhTSRWiApz0QP2MngT/qgCrSEDHss0ESd1scQylpDK8LrKDGHyYObJLLJhOAoeHWKALBTPA==} - '@zag-js/core@1.32.0': resolution: {integrity: sha512-aibBwyqw3saAzYBf2YHfuyOGt9kdNc4+ile3D1jbUsXiccLvGuFPP3+Ox7fULcILIiQhR0X6M+sTF17IyEMrYA==} @@ -2096,27 +2073,15 @@ packages: '@zag-js/dismissable@1.22.1': resolution: {integrity: sha512-0DzbykJu9QoXYw4Zcjte69Mtk6ThNRCXWxxCKBf930V8Bw3Ha7vfY5bgdb4RFT5K+BQP3E8vLT+PzIaDINn2Xw==} - '@zag-js/dismissable@1.27.1': - resolution: {integrity: sha512-EZ9xq47m50scGV8JHYTAIL1DjnoFYChBC68cE9XNsqZO2vOINdYAfzsH73otx/CQ6RkhSk7eymP4fIzZVtRTVw==} - '@zag-js/dismissable@1.32.0': resolution: {integrity: sha512-AUjajoaj2/3GfQvYJ5cdVwnov/dqAuS2N2dHRwxxSe/ktD4FbN/t0AcJ5Q2ojOuT6nBScEZQNd2vOrVgxLEiwQ==} '@zag-js/dom-query@1.22.1': resolution: {integrity: sha512-mtvGj2z3rkl40mkjd+QwoOHvxqpiOkY4mtVjzNzgzcbVtUN63Mz7giW8OZB+KLy37hwFX0B8JfiQncU8IOHNpw==} - '@zag-js/dom-query@1.27.1': - resolution: {integrity: sha512-+oyhPQ6of1zCCK8iTaxxQc2FMFEJz1mr+1571vaSRdoxG31GhNkq/QDfUvQQnRR+2HV9yNiFk4hcjCDGqdXDTQ==} - '@zag-js/dom-query@1.32.0': resolution: {integrity: sha512-12XCCV1bchIFC7wjtYim5sEqUrAddzt0eysMTbBrCuK5r6TmLecwLtxUNKIRQ8adkCn0jqecUiuNyXojaArP3g==} - '@zag-js/file-upload@1.27.1': - resolution: {integrity: sha512-xChLU5NGnHn/G+L4GDPkCOa8XWY/gHeBbvQAaHItFKTpVmjWwM8uZ6X/utcWuZmdmUU8j/YfWYmCGdyux58I9Q==} - - '@zag-js/file-utils@1.27.1': - resolution: {integrity: sha512-Ov5gMd7qUGdWydfjmtq/sm+PEl0LdJr7G4VuLZz3b4pJYVDBMv3pEHRXJj5x+b4mgm5U82vncE/itg/1Z08wKA==} - '@zag-js/floating-panel@1.22.1': resolution: {integrity: sha512-YGjLoYt2xSk4pkTgsR0z/7U7V5OdaicSOZa0HDtskH4MkKPxQxrgf2G4e8dNsw8hnQwfVuoc0RGPGW0BArVr6A==} @@ -2126,45 +2091,18 @@ packages: '@zag-js/focus-trap@1.32.0': resolution: {integrity: sha512-irzYMaz1ycAnHOGLr4r6+3MwhfKnA67N9jPft9ejnE2QoTp40W+xp07K2KQNf6xN7Boe6xGlw8SwLBnGZw2eCQ==} - '@zag-js/focus-visible@1.27.1': - resolution: {integrity: sha512-KGQAoiEejkEi0NMS33Q8mc7kedB0Y4g8WyKNaVlMgnqCEpi39cHkszgyZEvX1SYWfv4+RezQGfYOKo0Y6ze4uQ==} - - '@zag-js/i18n-utils@1.27.1': - resolution: {integrity: sha512-dNP7ZMoKxCD8KOc3RPqBpj7mEr5qb2qix8o6AQHw2QMDdgMDj0E9y5ZaNHIzkpMyYmvURY5I+pwWfFricv/rHg==} - '@zag-js/interact-outside@1.22.1': resolution: {integrity: sha512-+iZ3xHC9+jVo2FCC4B9c9ntcXv19shVOqQGDr2cD30Hwmwtm9kCOdVydMqv3Lp3UhR8a105MXEVUAKg53WbCoA==} - '@zag-js/interact-outside@1.27.1': - resolution: {integrity: sha512-MfZobJhDWSiS9SzF35FJd/R+/ksvwzgjXl51p1sjaqVQoTIfKAe/pK8Hcky7NWX9NsVx77x/YidJpb/Bwf3f6g==} - '@zag-js/interact-outside@1.32.0': resolution: {integrity: sha512-OwQgBxijmv9ZXFu6WzhLRULJh0FoixuP6DJV9B2CGBKJZz4hbQAqVrW9PFaPiBuWOILZpZ/iI6ZDjkGSKZ0WHw==} - '@zag-js/live-region@1.27.1': - resolution: {integrity: sha512-T5cMSazmWC9AYKHinJQPZSc2bnokF8zK8ly4NGApv6ExM6Mf5wQoH6sJIcv7dhRFfPUFVRndVdKHnWxZjTb8Og==} - - '@zag-js/pagination@1.27.1': - resolution: {integrity: sha512-tg23cP07weOtjNeEqOqS3Ilic3ikaIYQ7s9wi/JjvKUPMS+bi/LZRM12cfLD1tIwSUYjjLkr/reTnrYU3s2lZQ==} - '@zag-js/popover@1.22.1': resolution: {integrity: sha512-27VVkhaEOtiHJYj2j++AzYlAzpMcW0ED05TV9wIT1q0EYzASWxweSBajbnCiQf9TIYzCImDiNVDaCMl5D+TamQ==} '@zag-js/popper@1.22.1': resolution: {integrity: sha512-vBI5WpvE/3ugsimjZaNisOwcECiYfzc+3LIJwaU8od62kInZ1XF6m096BvV7JGwP0FjkMPJrgjcv7weDtY2iDQ==} - '@zag-js/popper@1.27.1': - resolution: {integrity: sha512-ac9YpcIvhd9+QDhm85E0BzPmjTuAWc/FRQOhG/EWKSUDvFZGNAcXLSVS6zxIWPEdY2vDG3Ri9jpCNv89bzzpoQ==} - - '@zag-js/progress@1.27.1': - resolution: {integrity: sha512-mBat/fn7jhuZXg+UBZMAQv3uL9W/HoDcrzm606PNycD+QyHFe8J1kpzHr8hFUFtCTzSDzmd5kf9zP0hieVPQTQ==} - - '@zag-js/radio-group@1.27.1': - resolution: {integrity: sha512-P1IHUrXv9iMHwLNdcIRoW0DQGfJ2DoMREWOohy8Gdekoo4/+xOGbr9qi9++QN//HMLsbB1cnD7+7WqqEZU1Hkw==} - - '@zag-js/rating-group@1.27.1': - resolution: {integrity: sha512-bjsWkUwMtlm0Jsajz4j14ULFEeYtGTShqPZvFuVDrEX8xSuZXujfxOdY0dphc0EreyjL1YjvYboAXvYsVbOaVw==} - '@zag-js/rect-utils@1.22.1': resolution: {integrity: sha512-jtI03SR9kF0AcBffoFI/TKXn5KyhjNCtsGlqbWw0dKbhWTNy1v432FDC5opmmnH8W5LjjWebIzo4QtO5+632QQ==} @@ -2174,9 +2112,6 @@ packages: '@zag-js/remove-scroll@1.32.0': resolution: {integrity: sha512-gWx8z/8ayNo624ibET4cNWbdVLc8A0Vyy9L9t67KT/slrBjcOA/zp9JL3Ngw/TZKgtR+wDh7P8ZoNAe0euaLSQ==} - '@zag-js/slider@1.27.1': - resolution: {integrity: sha512-p6zyMLU6cBtubk5673q2wk9JqCHf0l8my7nWY16KWqLWd68IFBSp7wrOyTYa0ifZ9CvmZZSjdL/w2TFJ4SHVKQ==} - '@zag-js/store@1.22.1': resolution: {integrity: sha512-KrMWi/Fa4cqOjx2zDSMIu6vztFYik+V3K6VPWRVONM4FkboLpTqAEayzwgTTNqMK9iYYZIYjhiPhAVLW9iLuBg==} @@ -2185,42 +2120,24 @@ packages: peerDependencies: svelte: ^5.0.0-next.1 - '@zag-js/switch@1.27.1': - resolution: {integrity: sha512-IuYacFHlQsxkFCU8ABGjwhtvWARexAYS/BMQkD5W8s0cwNHR3uOLPGjrJhSkJqvx3ENpSTRRKqTwawXVeFg2BQ==} - '@zag-js/tabs@1.22.1': resolution: {integrity: sha512-B0WHW36uuR+pu/24X0yI4eyvSwo7WmqOc5C3ohZHOf03zkmMJdtMtVQSotKr7qhGMt5updCgs68MR7jAmmc1Lw==} - '@zag-js/tags-input@1.27.1': - resolution: {integrity: sha512-+XUOwKFWfGwmJpl3hWK8kHq5IeuZUZaR1FgNzbO029ll3p5unBOGzEL51oCH1dpeuMY/FyXEssNLrvVgId6E3Q==} - - '@zag-js/toast@1.27.1': - resolution: {integrity: sha512-XfVxYlMkV8K1inmSwF+VKXkEPG/fDZcpjlj2PfbrWQu2PSD5mrJakBi1FzKfcCybp/OOD0V9Nfki1AFkhKKEwg==} - '@zag-js/toggle-group@1.22.1': resolution: {integrity: sha512-StxnGsPwzB60pGHTD7sNOqIMXjEPMl3lYQk0i2F5MIQWlTRkYdp4ivh73xBRYVtqK15gqacuWXw87EDzKcNwcA==} - '@zag-js/tooltip@1.27.1': - resolution: {integrity: sha512-RVMFFdqX7HaSoO4GEHikRzrScelwVMr+mbTfrCcGx1i/9AWf7YmNh1P9p3QMIkP2/9TB9FfQfqU7Flyz0qc3Iw==} - '@zag-js/tree-view@1.22.1': resolution: {integrity: sha512-AQmOn1mB+nLJEaq0xdSVnTI8Vt3nB3OweqdB12jkbdIOcWI9eY0RfhiNHC0k0mgAw+dMjyn84op/gOd9VVdtmA==} '@zag-js/types@1.22.1': resolution: {integrity: sha512-lvpDSMR96e7H7TdwOiVpMzj6css5Ydix1nBi7BlmjME6v5OPR0KZwVDGD6h5UtTeVjPq8dPaqM8TJWw+QwbQSw==} - '@zag-js/types@1.27.1': - resolution: {integrity: sha512-t7AtcXElKEjvvRlC1gcDmKgtIeiOtCL3vWf8K8kq7nVhq29q7PHtXB5ywn+R+CWnqJOmIDezKZjC1emXZKF0OA==} - '@zag-js/types@1.32.0': resolution: {integrity: sha512-9UV5mFnTgJRyFCCH9g/TIqtxxfw6FUphpe7E0AHK9+WSq3PRvflMeaRT/O6HgdWo0Rc+JN9vVwkFYyGD81uAbw==} '@zag-js/utils@1.22.1': resolution: {integrity: sha512-VXY4gjHaTENHW+wjnKKENZ2jcaW0vnG2a5lYEMuZR4dpNCKH217yFr/bCNrI44y2s1W3LWhWmpEjfZluP6udYg==} - '@zag-js/utils@1.27.1': - resolution: {integrity: sha512-AHSd3VeiBvVoa8lAUe7YsCzk37X9zn2jKiYv13k5Ac+NnbYRbpWzZZZUfNGbFAsu5cSE4X1HUw38H1GTuBeWNQ==} - '@zag-js/utils@1.32.0': resolution: {integrity: sha512-IRe1yFbMXBp8Q1Xp916Lq9P91s65K5KRNFZIv2EdQHRynLaT+QrPp5hmzkWoXJn8oOYd6PZeNb7K6v7vk8+k1g==} @@ -4997,6 +4914,12 @@ snapshots: '@floating-ui/utils@0.2.10': {} + '@fontsource-variable/public-sans@5.2.7': {} + + '@fontsource-variable/roboto-mono@5.2.9': {} + + '@fontsource-variable/space-grotesk@5.2.10': {} + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -5745,31 +5668,6 @@ snapshots: - encoding - supports-color - '@skeletonlabs/skeleton-svelte@1.5.1(svelte@5.55.7)': - dependencies: - '@zag-js/accordion': 1.27.1 - '@zag-js/avatar': 1.27.1 - '@zag-js/combobox': 1.27.1 - '@zag-js/dialog': 1.32.0 - '@zag-js/file-upload': 1.27.1 - '@zag-js/pagination': 1.27.1 - '@zag-js/popover': 1.22.1 - '@zag-js/progress': 1.27.1 - '@zag-js/radio-group': 1.27.1 - '@zag-js/rating-group': 1.27.1 - '@zag-js/slider': 1.27.1 - '@zag-js/svelte': 1.22.1(svelte@5.55.7) - '@zag-js/switch': 1.27.1 - '@zag-js/tabs': 1.22.1 - '@zag-js/tags-input': 1.27.1 - '@zag-js/toast': 1.27.1 - '@zag-js/tooltip': 1.27.1 - svelte: 5.55.7 - - '@skeletonlabs/skeleton@3.2.0(tailwindcss@4.1.13)': - dependencies: - tailwindcss: 4.1.13 - '@stablelib/base64@1.0.1': {} '@standard-schema/spec@1.1.0': {} @@ -6220,7 +6118,14 @@ snapshots: runed: 0.29.2(svelte@5.55.7) svelte: 5.55.7 - '@viamrobotics/tweakpane-config@0.1.1(svelte-tweakpane-ui@1.5.16(svelte@5.55.7))(svelte@5.55.7)': + '@viamrobotics/tailwind-config@1.1.0(tailwindcss@4.1.13)': + dependencies: + '@fontsource-variable/public-sans': 5.2.7 + '@fontsource-variable/roboto-mono': 5.2.9 + '@fontsource-variable/space-grotesk': 5.2.10 + tailwindcss: 4.1.13 + + '@viamrobotics/tweakpane-config@0.2.1(svelte-tweakpane-ui@1.5.16(svelte@5.55.7))(svelte@5.55.7)': dependencies: svelte: 5.55.7 svelte-tweakpane-ui: 1.5.16(svelte@5.55.7) @@ -6331,42 +6236,16 @@ snapshots: '@webgpu/types@0.1.66': {} - '@zag-js/accordion@1.27.1': - dependencies: - '@zag-js/anatomy': 1.27.1 - '@zag-js/core': 1.27.1 - '@zag-js/dom-query': 1.27.1 - '@zag-js/types': 1.27.1 - '@zag-js/utils': 1.27.1 - '@zag-js/anatomy@1.22.1': {} - '@zag-js/anatomy@1.27.1': {} - '@zag-js/anatomy@1.32.0': {} '@zag-js/aria-hidden@1.22.1': {} - '@zag-js/aria-hidden@1.27.1': - dependencies: - '@zag-js/dom-query': 1.27.1 - '@zag-js/aria-hidden@1.32.0': dependencies: '@zag-js/dom-query': 1.32.0 - '@zag-js/auto-resize@1.27.1': - dependencies: - '@zag-js/dom-query': 1.27.1 - - '@zag-js/avatar@1.27.1': - dependencies: - '@zag-js/anatomy': 1.27.1 - '@zag-js/core': 1.27.1 - '@zag-js/dom-query': 1.27.1 - '@zag-js/types': 1.27.1 - '@zag-js/utils': 1.27.1 - '@zag-js/collapsible@1.22.1': dependencies: '@zag-js/anatomy': 1.22.1 @@ -6379,32 +6258,11 @@ snapshots: dependencies: '@zag-js/utils': 1.22.1 - '@zag-js/collection@1.27.1': - dependencies: - '@zag-js/utils': 1.27.1 - - '@zag-js/combobox@1.27.1': - dependencies: - '@zag-js/anatomy': 1.27.1 - '@zag-js/aria-hidden': 1.27.1 - '@zag-js/collection': 1.27.1 - '@zag-js/core': 1.27.1 - '@zag-js/dismissable': 1.27.1 - '@zag-js/dom-query': 1.27.1 - '@zag-js/popper': 1.27.1 - '@zag-js/types': 1.27.1 - '@zag-js/utils': 1.27.1 - '@zag-js/core@1.22.1': dependencies: '@zag-js/dom-query': 1.22.1 '@zag-js/utils': 1.22.1 - '@zag-js/core@1.27.1': - dependencies: - '@zag-js/dom-query': 1.27.1 - '@zag-js/utils': 1.27.1 - '@zag-js/core@1.32.0': dependencies: '@zag-js/dom-query': 1.32.0 @@ -6428,12 +6286,6 @@ snapshots: '@zag-js/interact-outside': 1.22.1 '@zag-js/utils': 1.22.1 - '@zag-js/dismissable@1.27.1': - dependencies: - '@zag-js/dom-query': 1.27.1 - '@zag-js/interact-outside': 1.27.1 - '@zag-js/utils': 1.27.1 - '@zag-js/dismissable@1.32.0': dependencies: '@zag-js/dom-query': 1.32.0 @@ -6444,28 +6296,10 @@ snapshots: dependencies: '@zag-js/types': 1.22.1 - '@zag-js/dom-query@1.27.1': - dependencies: - '@zag-js/types': 1.27.1 - '@zag-js/dom-query@1.32.0': dependencies: '@zag-js/types': 1.32.0 - '@zag-js/file-upload@1.27.1': - dependencies: - '@zag-js/anatomy': 1.27.1 - '@zag-js/core': 1.27.1 - '@zag-js/dom-query': 1.27.1 - '@zag-js/file-utils': 1.27.1 - '@zag-js/i18n-utils': 1.27.1 - '@zag-js/types': 1.27.1 - '@zag-js/utils': 1.27.1 - - '@zag-js/file-utils@1.27.1': - dependencies: - '@zag-js/i18n-utils': 1.27.1 - '@zag-js/floating-panel@1.22.1': dependencies: '@zag-js/anatomy': 1.22.1 @@ -6486,39 +6320,16 @@ snapshots: dependencies: '@zag-js/dom-query': 1.32.0 - '@zag-js/focus-visible@1.27.1': - dependencies: - '@zag-js/dom-query': 1.27.1 - - '@zag-js/i18n-utils@1.27.1': - dependencies: - '@zag-js/dom-query': 1.27.1 - '@zag-js/interact-outside@1.22.1': dependencies: '@zag-js/dom-query': 1.22.1 '@zag-js/utils': 1.22.1 - '@zag-js/interact-outside@1.27.1': - dependencies: - '@zag-js/dom-query': 1.27.1 - '@zag-js/utils': 1.27.1 - '@zag-js/interact-outside@1.32.0': dependencies: '@zag-js/dom-query': 1.32.0 '@zag-js/utils': 1.32.0 - '@zag-js/live-region@1.27.1': {} - - '@zag-js/pagination@1.27.1': - dependencies: - '@zag-js/anatomy': 1.27.1 - '@zag-js/core': 1.27.1 - '@zag-js/dom-query': 1.27.1 - '@zag-js/types': 1.27.1 - '@zag-js/utils': 1.27.1 - '@zag-js/popover@1.22.1': dependencies: '@zag-js/anatomy': 1.22.1 @@ -6538,37 +6349,6 @@ snapshots: '@zag-js/dom-query': 1.22.1 '@zag-js/utils': 1.22.1 - '@zag-js/popper@1.27.1': - dependencies: - '@floating-ui/dom': 1.7.4 - '@zag-js/dom-query': 1.27.1 - '@zag-js/utils': 1.27.1 - - '@zag-js/progress@1.27.1': - dependencies: - '@zag-js/anatomy': 1.27.1 - '@zag-js/core': 1.27.1 - '@zag-js/dom-query': 1.27.1 - '@zag-js/types': 1.27.1 - '@zag-js/utils': 1.27.1 - - '@zag-js/radio-group@1.27.1': - dependencies: - '@zag-js/anatomy': 1.27.1 - '@zag-js/core': 1.27.1 - '@zag-js/dom-query': 1.27.1 - '@zag-js/focus-visible': 1.27.1 - '@zag-js/types': 1.27.1 - '@zag-js/utils': 1.27.1 - - '@zag-js/rating-group@1.27.1': - dependencies: - '@zag-js/anatomy': 1.27.1 - '@zag-js/core': 1.27.1 - '@zag-js/dom-query': 1.27.1 - '@zag-js/types': 1.27.1 - '@zag-js/utils': 1.27.1 - '@zag-js/rect-utils@1.22.1': {} '@zag-js/remove-scroll@1.22.1': @@ -6579,14 +6359,6 @@ snapshots: dependencies: '@zag-js/dom-query': 1.32.0 - '@zag-js/slider@1.27.1': - dependencies: - '@zag-js/anatomy': 1.27.1 - '@zag-js/core': 1.27.1 - '@zag-js/dom-query': 1.27.1 - '@zag-js/types': 1.27.1 - '@zag-js/utils': 1.27.1 - '@zag-js/store@1.22.1': dependencies: proxy-compare: 3.0.1 @@ -6598,15 +6370,6 @@ snapshots: '@zag-js/utils': 1.22.1 svelte: 5.55.7 - '@zag-js/switch@1.27.1': - dependencies: - '@zag-js/anatomy': 1.27.1 - '@zag-js/core': 1.27.1 - '@zag-js/dom-query': 1.27.1 - '@zag-js/focus-visible': 1.27.1 - '@zag-js/types': 1.27.1 - '@zag-js/utils': 1.27.1 - '@zag-js/tabs@1.22.1': dependencies: '@zag-js/anatomy': 1.22.1 @@ -6615,26 +6378,6 @@ snapshots: '@zag-js/types': 1.22.1 '@zag-js/utils': 1.22.1 - '@zag-js/tags-input@1.27.1': - dependencies: - '@zag-js/anatomy': 1.27.1 - '@zag-js/auto-resize': 1.27.1 - '@zag-js/core': 1.27.1 - '@zag-js/dom-query': 1.27.1 - '@zag-js/interact-outside': 1.27.1 - '@zag-js/live-region': 1.27.1 - '@zag-js/types': 1.27.1 - '@zag-js/utils': 1.27.1 - - '@zag-js/toast@1.27.1': - dependencies: - '@zag-js/anatomy': 1.27.1 - '@zag-js/core': 1.27.1 - '@zag-js/dismissable': 1.27.1 - '@zag-js/dom-query': 1.27.1 - '@zag-js/types': 1.27.1 - '@zag-js/utils': 1.27.1 - '@zag-js/toggle-group@1.22.1': dependencies: '@zag-js/anatomy': 1.22.1 @@ -6643,16 +6386,6 @@ snapshots: '@zag-js/types': 1.22.1 '@zag-js/utils': 1.22.1 - '@zag-js/tooltip@1.27.1': - dependencies: - '@zag-js/anatomy': 1.27.1 - '@zag-js/core': 1.27.1 - '@zag-js/dom-query': 1.27.1 - '@zag-js/focus-visible': 1.27.1 - '@zag-js/popper': 1.27.1 - '@zag-js/types': 1.27.1 - '@zag-js/utils': 1.27.1 - '@zag-js/tree-view@1.22.1': dependencies: '@zag-js/anatomy': 1.22.1 @@ -6666,18 +6399,12 @@ snapshots: dependencies: csstype: 3.1.3 - '@zag-js/types@1.27.1': - dependencies: - csstype: 3.1.3 - '@zag-js/types@1.32.0': dependencies: csstype: 3.2.3 '@zag-js/utils@1.22.1': {} - '@zag-js/utils@1.27.1': {} - '@zag-js/utils@1.32.0': {} '@zappar/msdf-generator@1.2.4': diff --git a/src/app.css b/src/app.css index 3d205fa1e..c7c4d9483 100644 --- a/src/app.css +++ b/src/app.css @@ -1,19 +1,72 @@ @import 'tailwindcss'; -@config "../tailwind.config.ts"; +@import '@viamrobotics/tailwind-config'; +@import '@viamrobotics/tailwind-config/dark'; @plugin '@tailwindcss/forms'; -@source '../node_modules/@skeletonlabs/skeleton-svelte'; +/* + * prime-core ships its components under node_modules, which Tailwind v4 skips + * during automatic source detection. Register it so the utility classes those + * components rely on are generated against the @viamrobotics/tailwind-config + * theme (which is the native-v4 port of prime-core's own theme/plugins). + */ +@source '../node_modules/@viamrobotics/prime-core/src'; -@import '@skeletonlabs/skeleton'; -@import '@skeletonlabs/skeleton/optional/presets'; -@import '@skeletonlabs/skeleton/themes/cerberus'; +/* + * "medium-light" status steps sit between the -light and -medium tokens. They + * back the hover fill prime-core's outline-* button variants use (prime-core + * hardcodes the hex, e.g. #f5dfdc for outline-danger). The values are the + * midpoint of the surrounding light/medium tokens; we flip them in the dark + * scopes exactly like @viamrobotics/tailwind-config/dark does for the rest of + * the palette so they adapt to system preference (and the .dark/.light escape + * hatches keep working). + */ +@theme { + --color-success-medium-light: #cdebd0; + --color-danger-medium-light: #f5d6d5; +} -@import '@viamrobotics/prime-core/prime.css'; +@media (prefers-color-scheme: dark) { + :root:not(.light) { + --color-success-medium-light: #335037; + --color-danger-medium-light: #542f2d; + } +} + +.dark { + --color-success-medium-light: #335037; + --color-danger-medium-light: #542f2d; +} body { height: 100dvh; font-family: system-ui; + color: var(--color-default); +} + +/* + * @viamrobotics/tailwind-config/dark sets `color-scheme: light dark` on :root, so + * when the OS prefers dark the browser paints unstyled text and form controls with + * its dark defaults. useDarkMode toggles an explicit .light/.dark class on , + * so pin color-scheme to that class — otherwise toggling to light while the OS is in + * dark mode leaves default-colored text white (illegible) on the light surfaces. + */ +.light { + color-scheme: light; +} +.dark { + color-scheme: dark; +} + +/* + * Ported from @viamrobotics/prime-core/prime.css. Form controls don't inherit + * font-family by default, so without this prime-core inputs/buttons fall back + * to the user-agent font. + */ +*, +input, +button { + font-family: inherit; } /* diff --git a/src/app.html b/src/app.html index 738822f0c..94986194a 100644 --- a/src/app.html +++ b/src/app.html @@ -12,10 +12,7 @@ /> %sveltekit.head% - +
%sveltekit.body%
diff --git a/src/lib/components/App.svelte b/src/lib/components/App.svelte index db09320e3..a6318b803 100644 --- a/src/lib/components/App.svelte +++ b/src/lib/components/App.svelte @@ -6,8 +6,8 @@ import { PortalTarget } from '@threlte/extras' import { useXR } from '@threlte/xr' import { provideToast, ToastContainer } from '@viamrobotics/prime-core' - import { primeTheme } from '@viamrobotics/tweakpane-config' - import { type Component, onMount, type Snippet } from 'svelte' + import { primeTheme, primeThemeDark } from '@viamrobotics/tweakpane-config' + import { type Component, type Snippet } from 'svelte' import { ThemeUtils } from 'svelte-tweakpane-ui' import type { FragmentInfo } from '$lib/hooks/usePartConfig.svelte' @@ -24,6 +24,7 @@ import { createPartIDContext } from '$lib/hooks/usePartID.svelte' import { provideSettings } from '$lib/hooks/useSettings.svelte' import { provideWeblabs } from '$lib/hooks/useWeblabs.svelte' + import { provideDarkMode } from '$lib/plugins/DarkMode/useDarkMode.svelte' import { domPortal } from '$lib/portal' import FileDrop from './FileDrop/FileDrop.svelte' @@ -78,7 +79,7 @@ details?: Snippet<[{ entity: Entity }]> } - let { + const { partID = '', inputBindingsEnabled = true, localConfigProps, @@ -96,6 +97,7 @@ const currentRobotCameraWidgets = $derived(settings.current.openCameraWidgets[partID] || []) const currentFramePovWidgets = $derived(settings.current.openFramePovWidgets[partID] || []) const { isPresenting } = useXR() + const darkMode = provideDarkMode() provideCameraControls(() => cameraPose) createPartIDContext(() => partID) @@ -115,15 +117,15 @@ environment.current.isStandalone = !localConfigProps }) - onMount(() => { - ThemeUtils.setGlobalDefaultTheme(primeTheme) + $effect(() => { + ThemeUtils.setGlobalDefaultTheme(darkMode.isDark ? primeThemeDark : primeTheme) }) const selected = useQuery(traits.Selected)
diff --git a/src/lib/components/Scene.svelte b/src/lib/components/Scene.svelte index 140cb7f3f..225d0101f 100644 --- a/src/lib/components/Scene.svelte +++ b/src/lib/components/Scene.svelte @@ -13,6 +13,7 @@ import StaticGeometries from '$lib/components/StaticGeometries.svelte' import { bvh } from '$lib/hooks/plugins/bvh.svelte' import { useSettings } from '$lib/hooks/useSettings.svelte' + import { useDarkMode } from '$lib/plugins/DarkMode/useDarkMode.svelte' import hdrImage from '../assets/ferndale_studio_11_1k.hdr' import BatchedArrows from './BatchedArrows.svelte' @@ -28,6 +29,20 @@ const threlte = useThrelte() const settings = useSettings() + const darkMode = useDarkMode() + + /* + * The canvas renderer is transparent (alpha), so the viewport backdrop is the + * DOM container behind it (App.svelte), which adapts to dark mode via a CSS + * token. The grid is drawn in WebGL, though, so its line colors can't use + * Tailwind tokens — flip them in JS to stay legible on the dark backdrop. + * Light-mode values match the previous threlte defaults exactly. + */ + const gridColors = $derived( + darkMode.isDark + ? { cell: '#3a3a3d', section: '#5c5c62', background: '#1c1c1e' } + : { cell: '#000000', section: '#333333', background: '#dadada' } + ) // @ts-expect-error This is for debugging globalThis.__threlte__ = threlte @@ -77,7 +92,9 @@ raycast={() => null} bvh={{ enabled: false }} plane="xy" - sectionColor="#333" + cellColor={gridColors.cell} + sectionColor={gridColors.section} + backgroundColor={gridColors.background} infiniteGrid renderOrder={999} cellSize={settings.current.gridCellSize} diff --git a/src/lib/components/Selected.svelte b/src/lib/components/Selected.svelte index b893a0d6c..840ac120e 100644 --- a/src/lib/components/Selected.svelte +++ b/src/lib/components/Selected.svelte @@ -4,15 +4,23 @@ import { OBB } from 'three/addons/math/OBB.js' import { traits, useQuery } from '$lib/ecs' + import { useDarkMode } from '$lib/plugins/DarkMode/useDarkMode.svelte' import { OBBHelper } from '$lib/three/OBBHelper' const box3 = new Box3() const obb = new OBB() + const darkMode = useDarkMode() const { scene, invalidate } = useThrelte() const selected = useQuery(traits.Selected) const obbHelpers = $derived(selected.current.map((entity) => [entity, new OBBHelper()] as const)) + const outlineColor = $derived(darkMode.isDark ? 0xffffff : 0x000000) + + $effect(() => { + for (const [, helper] of obbHelpers) helper.setColor(outlineColor) + invalidate() + }) useTask( () => { diff --git a/src/lib/components/hover/HoveredEntityTooltip.svelte b/src/lib/components/hover/HoveredEntityTooltip.svelte index 07cd79625..7c36a4883 100644 --- a/src/lib/components/hover/HoveredEntityTooltip.svelte +++ b/src/lib/components/hover/HoveredEntityTooltip.svelte @@ -18,11 +18,11 @@ center >
diff --git a/src/lib/components/overlay/AddFrames.svelte b/src/lib/components/overlay/AddFrames.svelte index 118030eae..bdf967506 100644 --- a/src/lib/components/overlay/AddFrames.svelte +++ b/src/lib/components/overlay/AddFrames.svelte @@ -39,7 +39,7 @@
{#if framelessComponents.current.length > 0}