Compare commits

..

6 Commits

Author SHA1 Message Date
michael
741ee69079 Fix package-lock.json sync for CI
Regenerate lock file without --legacy-peer-deps to include
all resolved peer dependencies (react, react-native, etc.)
that npm ci requires.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 10:22:05 -07:00
michael
89aaac9548 Merge branch 'main' of https://github.com/dangerous-tac0s/dt-design-system 2026-03-08 10:07:23 -07:00
michael
09143edab8 Add hex background to showcase with interactive controls
- Render HexGridBackground globally on every page
- Add dedicated Hex Background page with sliders for all 8 props
- Lift hex props state to App for real-time global updates
- Add three, @react-three/fiber, hex-background deps

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 10:05:47 -07:00
michael
5d2b245f5a Update docs to reflect current 6-package architecture
- README: add all 6 packages, React/Tailwind usage sections, remove glows refs
- CLAUDE.md: rewrite for 6 packages, 4-layer parity, build.cjs, storefront migration as implemented reference
- Package READMEs: fix monorepo links, remove glows references

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 10:04:41 -07:00
michael
0cfa83259e Remove glows, refine feature legend, forms, and showcase pages
- Delete glows.css and GlowsPage (unused)
- Enhance DTFeatureLegend and feature-legend.css with improved layout
- Update DTRadioGroup styling
- Refine bevels.css, forms-dt.css
- Update AnimationsPage, CardsAdvancedPage, FormsPage, HomePage
- Add type:module to web package.json

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 10:03:48 -07:00
dangerous-tac0s
3d9ec57523 Merge pull request #4 from dangerous-tac0s/changeset-release/main
chore: version packages
2026-03-07 11:25:43 -08:00
23 changed files with 774 additions and 946 deletions

276
CLAUDE.md
View File

@@ -2,11 +2,14 @@
## Project Overview
Monorepo for the Dangerous Things design system. Three npm packages publish under `@dangerousthings/*`:
Monorepo for the Dangerous Things design system. Six npm packages publish under `@dangerousthings/*`:
- **tokens** — canonical design tokens in TypeScript, compiled to JS and auto-generated as CSS custom properties
- **web** — CSS themes (bevels, glows, forms) consumed by web apps
- **web** — CSS themes (bevels, forms, animations) consumed by web apps
- **react** — React web components wrapping web CSS
- **react-native** — React Native components built on React Native Paper
- **tailwind-preset** — Tailwind CSS v3 preset mapping DT tokens
- **hex-background** — 3D hexagon grid background (Three.js)
Two brand themes: **dt** (neon cyberpunk / beveled), **classic** (dark navy / magenta).
@@ -16,7 +19,7 @@ Two brand themes: **dt** (neon cyberpunk / beveled), **classic** (dark navy / ma
- Build everything: `npm run build`
- Typecheck everything: `npm run typecheck`
- Tokens build: `tsc` then `node dist/scripts/generate-css.js`
- Web build: `node scripts/build.js` (copies CSS, assembles brand token CSS, copies fonts)
- Web build: `node scripts/build.cjs` (copies CSS, assembles brand token CSS, copies fonts)
- React Native build: `tsc`
- Versioning: Changesets (`npx changeset`, `npm run version-packages`, `npm run release`)
@@ -28,20 +31,22 @@ Two brand themes: **dt** (neon cyberpunk / beveled), **classic** (dark navy / ma
## Feature Parity
When adding or modifying a design token, component, or visual behavior, keep all three layers in sync:
When adding or modifying a design token, component, or visual behavior, keep all four layers in sync:
1. **Tokens** — add/update the value in every brand file (`packages/tokens/src/brands/{dt,classic}.ts`) and in the shared type (`packages/tokens/src/types.ts`).
2. **Web** — if the token maps to a CSS component, update the relevant CSS file under `packages/web/src/components/`. Ensure the CSS custom property name matches the token path (e.g., `--dt-color-brand-primary`).
3. **React Native** — update the corresponding component(s) under `packages/react-native/src/components/` and ensure the theme provider maps the new token.
3. **React** — if there is a React web component wrapper, update or create the corresponding component in `packages/react/src/components/`.
4. **React Native** — update the corresponding component(s) under `packages/react-native/src/components/` and ensure the theme provider maps the new token.
### Parity Checklist (use before closing any PR)
- [ ] New tokens exist in all three brand files with correct values
- [ ] New tokens exist in all brand files with correct values
- [ ] Token types are updated if the shape of the token object changed
- [ ] CSS generation script produces the expected custom properties (`npm run build:tokens` then inspect `dist/css/`)
- [ ] Web CSS files reference the new/changed custom properties
- [ ] React web components are updated if applicable
- [ ] React Native components consume the new tokens via the theme
- [ ] All three packages build cleanly (`npm run build`)
- [ ] All packages build cleanly (`npm run build`)
- [ ] TypeScript typechecks pass (`npm run typecheck`)
## Testing
@@ -65,10 +70,10 @@ When adding or modifying a design token, component, or visual behavior, keep all
### Adding a New Component
1. Create the React Native component in `packages/react-native/src/components/DTFoo.tsx`.
2. Re-export it from `packages/react-native/src/index.ts`.
3. If it needs new tokens, add them to all brand files and types first.
4. If there is a web equivalent (CSS-only), add a CSS file in `packages/web/src/components/`.
1. If it needs new tokens, add them to all brand files and types first.
2. If there is a web equivalent, add a CSS file in `packages/web/src/components/`.
3. Create a React web component wrapper in `packages/react/src/components/DTFoo.tsx` and re-export from `packages/react/src/index.ts`.
4. Create the React Native component in `packages/react-native/src/components/DTFoo.tsx` and re-export from `packages/react-native/src/index.ts`.
5. Create a changeset: `npx changeset` — select affected packages and describe the change.
### Adding a New Brand
@@ -99,19 +104,28 @@ GitHub Actions runs on push to `main` and on PRs:
- React Native components are prefixed `DT` (e.g., `DTButton`).
- Keep files focused — one component per file, one brand per file.
## Storefront Component Migration — Features to Extend
## Storefront Component Migration — Implementation Reference
The DT Shopify Storefront (`dt-shopify-storefront`) is the genesis project for this design system. Several
storefront features are not yet represented in the design system and need to be baked in. This section
documents everything that must be supported before the storefront can fully adopt design system components.
The DT Shopify Storefront (`dt-shopify-storefront`) is the genesis project for this design system.
The storefront features documented below have been migrated into the design system. This section
serves as a reference for the implemented components and their locations.
### Card Color Modes (per-card `--mode`)
### Implemented Features
The storefront assigns each card a color mode that controls border, header background, glow, and accent colors.
The design system's current `.card` component only uses `--color-primary` (always cyan). Cards must support
per-instance mode overrides.
| Feature | CSS (web) | React Component | Notes |
|---|---|---|---|
| Card Color Modes | `.mode-normal`, `.mode-emphasis`, `.mode-warning`, `.mode-success`, `.mode-other` in `bevels.css` | `DTCard` | Five modes with `*-selected` variants at 70% opacity |
| Card Progress Bar | `.dt-card-progress` in `bevels.css` | `DTCard` (`progress` prop) | Vertical left-edge bar, 0-100%, fills on hover |
| Card Chip Badges | `.dt-badge-overlay` in `bevels.css` | `DTBadgeOverlay` | Positioned bottom-right, mode-color-aware |
| Card Hover/Selected | `.card.selected` and hover states in `bevels.css` | `DTCard` | Fills with `var(--dt-card-color-selected)` |
| Staggered Container | `.dt-stagger-container` in `animations.css` | `DTStaggerContainer` | CSS `nth-child` staggered `animation-delay` |
| Animation Library | `animations.css` | -- | `dt-scale-in`, `dt-fade-in`, `dt-slide-up`, `dt-pulse`, `dt-ping`, `dt-spin`, transition utilities |
| Filter System UI | `.dt-menu-item`, `.dt-filter-header`, `.dt-filter-overlay` in `forms-dt.css` | `DTMenu`, `DTMobileFilterOverlay` | Nested menus, filter accordions, mobile overlay |
| Feature Legend | `feature-legend.css` | `DTFeatureLegend` | Icon grid with rotated labels, mode-colored header, feature states |
| Theme Branching | `[data-brand="dt"]` / `[data-brand="classic"]` CSS scoping | -- | No JS conditionals needed; CSS handles visual differences |
| Scrollbar Styling | `scrollbar.css` (`.dt-scrollbar`) | -- | Thin scrollbar with mode-normal color |
**Five modes (tokens already exist, need component support):**
**Card color modes reference:**
| Mode | Token | Default (DT dark) | Use Case |
|---|---|---|---|
@@ -121,144 +135,20 @@ per-instance mode overrides.
| `mode-success` | `--color-accent` | `#00FF00` green | Positive/success states |
| `mode-other` | `--color-other` | `#FF00FF` magenta | Bundles, alternative states |
Each mode also has a `*-selected` variant at 70% opacity for hover/active states.
**Theme branching differences (handled by CSS, not JS):**
**Implementation pattern in storefront:**
```css
.mode-normal {
--mode: var(--mode-normal);
--accent-mode: var(--mode-emphasis);
--selected: var(--mode-normal-selected);
}
.selected {
background-color: var(--selected) !important;
color: black !important;
}
```
**Recommendation:** Add a `--dt-card-color` custom property that defaults to `--color-primary` and can be
overridden per-card. All card sub-components (header bg, border, glow, progress bar) should reference this
variable instead of `--color-primary` directly.
### Card Progress Bar (thick left edge)
Product cards have a vertical progress bar on the left edge that fills based on a percentage value.
This does not exist in the design system.
**Storefront implementation:**
- CSS variable: `--card-thick-edge-width: 0.75em`
- Class: `.progress` — left-side vertical bar inheriting the card's `--mode` color
- Fill: Dynamic height (0-100%) controlled by `progress` prop
- Hover: Fills to 100% on hover
- States: `.progress-indicator` (semi-transparent), `.progress-indicator-1` (darker with bevel clip)
**Recommendation:** Add an optional `.dt-card-progress` child element to the card component, driven by
a `--dt-card-progress` custom property (0-100).
### Card Chip Badges
Product cards display contextual badges (e.g., "LAB", "BUNDLE") positioned absolute bottom-right of the
image area, with background color matching the card's mode.
**Recommendation:** The existing `.badge` component in `bevels.css` could be extended with positioning
utilities (`.badge-overlay`, `.badge-bottom-right`) and mode-color-aware variants.
### Card Hover / Selected State
On hover, the storefront card fills with the mode's selected color and the progress bar fills to 100%.
The entire card gets a `.selected` class. The design system's `.card:hover` only applies a `drop-shadow`
but doesn't change fill color.
**Recommendation:** Add `.card.selected` and `.card:hover` states that fill with `var(--dt-card-color-selected)`
(defaulting to `rgba(var(--color-primary-rgb), 0.7)`).
### Staggered Card Container (Sequential Loading Animation)
The storefront animates product cards with staggered scale-in entrances — each card scales from 0 to 1 with
an incrementing delay. This creates a sequential "loading" effect across the grid.
**Current storefront pattern (per-card, using framer-motion):**
```tsx
<motion.div
initial={{scale: 0}}
animate={{scale: 1}}
transition={{ ease: 'easeInOut', duration: 0.33, delay: index * 0.075 }}
/>
```
**Recommendation:** Create a `dt-stagger-container` component that automatically assigns staggered
`animation-delay` values to its children. This should work with pure CSS (using `nth-child` or CSS
custom properties) so consumers don't need a JS animation library. Example:
```css
.dt-stagger-container {
--dt-stagger-duration: 0.33s;
--dt-stagger-interval: 75ms;
}
.dt-stagger-container > * {
animation: dt-scale-in var(--dt-stagger-duration) ease-in-out both;
}
.dt-stagger-container > :nth-child(1) { animation-delay: calc(0 * var(--dt-stagger-interval)); }
.dt-stagger-container > :nth-child(2) { animation-delay: calc(1 * var(--dt-stagger-interval)); }
/* ... up to a reasonable limit, or use @property for dynamic calculation */
@keyframes dt-scale-in {
from { transform: scale(0); }
to { transform: scale(1); }
}
```
### Animation Library
The storefront uses several animation patterns that should be standardized in the design system:
**Entrance animations:**
- `dt-scale-in` — scale 0→1 (card entrance, staggered)
- `dt-fade-in` — opacity 0→1
- `dt-slide-up` — translateY(100%)→0 (mobile menus, modals)
**Interactive animations:**
- `dt-pulse` — opacity oscillation for hover glow (`2s cubic-bezier(0.4, 0, 0.6, 1) infinite`)
- `dt-ping` — scale + fade-out ping effect (used on active filter indicators)
- `dt-spin` — 360deg rotation (loading spinners)
**Transition utilities:**
- `dt-accordion-expand` — max-height 0→auto with 250ms ease-in-out
- `dt-chevron-rotate` — 180deg rotation for expand/collapse indicators
- `dt-progress-fill` — height transition for progress bars
**Recommendation:** Add `packages/web/src/components/animations.css` with `@keyframes` definitions
and utility classes. These should be brand-independent (no color references in animations).
### Filter System UI
The storefront's filter system includes several components that are not in the design system:
**NestedMenu:** Hierarchical filter navigation with recursive submenus.
- Level 0: Large icons (4xl) with vertical rotated labels below
- Level 1+: `.menu-item-clipped` buttons with bevel clip-path and thick top border
- Submenus: Portal-rendered, absolute positioned, hover-activated with 120ms close delay
- Selected state: `mode-emphasis` color, `menu-item-clipped-selected` class
**FilterAccordion:** Expandable filter panel with nested levels.
- Thick top border (5px in cyberpunk, 1px in clean)
- Active state: border and text color switch to `--mode-emphasis`
- Indent per level: `paddingLeft: (level + 1) * 16px`
- Chevron: MdExpandMore with 180deg rotation on expand
**MobileFilterMenu:** Full-screen overlay with spring animation.
- Backdrop: `rgba(0,0,0,0.5)` with blur(4px)
- Slide-up animation: `y: 100%``y: 0`
- Active filter count badge in header
- "Clear All Filters" button when filters are active
**Recommendation:** These are complex interactive components. Start with CSS patterns
(`.dt-menu-item`, `.dt-filter-header`) and leave JS behavior to consumers.
| Aspect | Cyberpunk (`data-brand="dt"`) | Clean (`data-brand="classic"`) |
|---|---|---|
| Clip-paths | Heavy bevels | `border-radius: 0.75rem` |
| Colors | Neon (#00FFFF, #FF00FF) | B&W or muted tones |
| Hover effects | Pulse glow animations | None |
| Typography | Tektur | system-ui sans-serif |
| Text shadows | `0 0 8px var(--mode)` | None |
| Borders | Thick neon (5px top) | Subtle 1px |
### Icon Set — Product Features & Filtering
The storefront uses a specific set of icons from `react-icons` for product features and UI controls.
These should be documented as the canonical icon set for the DT design system.
Canonical icon set for the DT design system, sourced from `react-icons`.
**Product Feature Icons (react-icons/md — Material Design):**
@@ -271,7 +161,7 @@ These should be documented as the canonical icon set for the DT design system.
| Payment/NFC | `MdOutlineCreditCard` | react-icons/md |
| Smartphone Compat | `MdOutlinePhonelinkRing` | react-icons/md |
| Access Control | `MdOutlineVpnKey` | react-icons/md |
| Temperature | `MdOutlineThermostat` | react-icons/md |
| Sensors | `MdOutlineThermostat` | react-icons/md |
| NFC Sensing | `MdOutlineSensors` | react-icons/md |
| Fitness/Bio | `MdOutlineFitbit` | react-icons/md |
| Vibration | `MdOutlineVibration` | react-icons/md |
@@ -306,80 +196,6 @@ These should be documented as the canonical icon set for the DT design system.
**Icon display standard:** Feature icons render at 42px in the UseCaseLegend grid. UI icons at 1.5em.
Icons inherit color from their parent's `--mode` variable.
**Recommendation:** The design system should document this icon set as the canonical reference.
Consider bundling an icon sprite or re-exporting the specific react-icons subset as
`@dangerousthings/icons` for consistency across projects.
### Feature Legend Component
The storefront has a specialized `UseCaseLegend` component that displays product features in a
grid with icons, rotated labels, and a hover-driven detail header. This is a key product page
component that should be represented in the design system.
**Structure:**
- **Header bar:** Full-width, mode-colored background, shows chip name + hovered feature details
- **Icon grid:** 5-column flex layout with icons + vertical rotated labels
- **Labels:** `writing-mode: vertical-rl` + `transform: rotate(180deg)`, 0.85rem, font-weight 600
- **Feature states:** Supported (mode-normal/cyan), disabled (mode-other/magenta), unsupported (mode-warning/red)
**Color assignment in the legend uses hardcoded hex values** in `app/lib/cyberTheme.ts` — these
need to be migrated to use CSS variables. This is a known pre-existing issue in the storefront.
### Theme Branching Pattern
Many storefront components have two rendering paths: cyberpunk and clean. The design system should
support this via the two-axis theme system (`data-brand` + `data-theme`) so that components
automatically adapt without conditional JS logic.
**Key differences between branches:**
| Aspect | Cyberpunk (DT brand) | Clean |
|---|---|---|
| Clip-paths | Heavy bevels | `border-radius: 0.75rem` |
| Colors | Neon (#00FFFF, #FF00FF) | B&W or muted tones |
| Hover effects | Pulse glow animations | None |
| Typography | Tektur | system-ui sans-serif |
| Text shadows | `0 0 8px var(--mode)` | None |
| Borders | Thick neon (5px top) | Subtle 1px |
**Recommendation:** The clean theme should be a brand variant (e.g., `data-brand="classic"`) that
nullifies bevels, glows, and animations via CSS. Components should not need JS `if (isClean)` branches
if the design system CSS handles the visual differences.
### Scrollbar Styling
The storefront styles scrollbars in cyberpunk mode:
```css
scrollbar-width: thin;
scrollbar-color: var(--mode-normal) transparent;
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-thumb { background: var(--mode-normal); border-radius: 3px; }
```
**Recommendation:** Add to design system as a utility: `.dt-scrollbar` or scope under `[data-brand="dt"]`.
## Known Issues
### Link hover glow ignores per-element mode colors (glows.css)
`packages/web/dist/components/glows.css` line 37-38 applies a blanket link hover glow:
```css
[data-brand="dt"] a:hover {
text-shadow: 0 0 8px var(--color-primary);
}
```
`--color-primary` is always the brand's primary color (cyan for DT). This means **every** link glows cyan on hover, even inside elements that define their own mode color (e.g., a product card with `--mode: var(--mode-warning)` still gets a cyan glow instead of red).
**Workaround (dt-shopify-storefront):** The storefront overrides this with a higher-specificity rule:
```css
[data-brand="dt"] .card-container a:hover {
text-shadow: 0 0 8px var(--mode);
}
```
**Proper fix:** The link glow rule should respect a local color variable. Options:
1. Use `currentColor` instead of `var(--color-primary)` — glows whatever color the text already is
2. Introduce a `--glow-color` custom property defaulting to `--color-primary`, which components can override locally
3. Scope the rule more narrowly so it doesn't apply inside component containers that define their own color context
(none currently)

View File

@@ -7,8 +7,10 @@ Shared design tokens, web CSS, and React Native components for the Dangerous Thi
| Package | Description |
| -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| [`@dangerousthings/tokens`](packages/tokens) | Canonical design tokens (colors, typography, shape) defined in TypeScript and exported as CSS custom properties |
| [`@dangerousthings/web`](packages/web) | Web CSS themes — bevels, glows, animations, forms, and filter styles |
| [`@dangerousthings/web`](packages/web) | Web CSS themes — bevels, animations, forms, and component styles |
| [`@dangerousthings/react`](packages/react) | React web components wrapping @dangerousthings/web CSS |
| [`@dangerousthings/react-native`](packages/react-native) | React Native components built on React Native Paper |
| [`@dangerousthings/tailwind-preset`](packages/tailwind-preset) | Tailwind CSS v3 preset mapping DT tokens to Tailwind theme |
| [`@dangerousthings/hex-background`](packages/hex-background) | 3D hexagon grid background using Three.js + React Three Fiber (web and React Native) |
## Brand Themes
@@ -50,6 +52,25 @@ npm run build:react-native
@import "@dangerousthings/web/components/bevels.css";
```
### React (Web)
```tsx
import "@dangerousthings/web";
import { DTWebThemeProvider, DTButton, DTCard } from "@dangerousthings/react";
export default function App() {
return (
<DTWebThemeProvider brand="dt" theme="dark">
<DTCard title="PRODUCT" variant="emphasis" progress={60}>
<DTButton variant="normal" onClick={handleClick}>
Scan NFC
</DTButton>
</DTCard>
</DTWebThemeProvider>
);
}
```
### React Native
```tsx
@@ -76,6 +97,17 @@ import { dt } from "@dangerousthings/tokens/brands/dt";
console.log(dt.color.brand.primary); // "#00ffff"
```
### Tailwind
```js
// tailwind.config.js
import dtPreset from "@dangerousthings/tailwind-preset";
export default {
presets: [dtPreset],
content: ["./src/**/*.{tsx,ts,html}"],
};
```
## Development
### Scripts
@@ -85,7 +117,6 @@ console.log(dt.color.brand.primary); // "#00ffff"
| `npm run build` | Build all packages (Turbo) |
| `npm run clean` | Remove all `dist/` directories |
| `npm run typecheck` | Run TypeScript type checking |
| `npm run lint` | Lint all packages |
### Release
@@ -97,6 +128,12 @@ npm run version-packages # bump versions from pending changesets
npm run release # build + publish to npm
```
## React Web Components
The `@dangerousthings/react` package provides React web components that wrap the CSS from `@dangerousthings/web`. The component API mirrors the React Native package for consistency:
DTAccordion, DTBadgeOverlay, DTButton, DTCard, DTCheckbox, DTChip, DTDrawer, DTFeatureLegend, DTGallery, DTHexagon, DTLabel, DTMediaFrame, DTMenu, DTMobileFilterOverlay, DTModal, DTProgressBar, DTQuantityStepper, DTRadioGroup, DTSearchInput, DTStaggerContainer, DTSwitch, DTTextInput
## React Native Components
DTAccordion, DTBadgeOverlay, DTButton, DTCard, DTCheckbox, DTChip, DTDrawer, DTFeatureLegend, DTGallery, DTHexagon, DTLabel, DTMediaFrame, DTMenu, DTMobileFilterOverlay, DTModal, DTProgressBar, DTQuantityStepper, DTRadioGroup, DTSearchInput, DTStaggerContainer, DTSwitch, DTTextInput
@@ -106,7 +143,6 @@ DTAccordion, DTBadgeOverlay, DTButton, DTCard, DTCheckbox, DTChip, DTDrawer, DTF
## Web CSS Modules
- `bevels.css` — angular clip-path bevels, card modes, selected states, progress bars, badge overlays, interactive bevel buttons
- `glows.css` — neon drop-shadow and text-shadow effects (mode-aware)
- `forms-dt.css` — text inputs, checkboxes, radio buttons, switches, menu items, filter headers
- `animations.css` — entrance animations, stagger containers, transition utilities
- `scrollbar.css` — thin neon scrollbar styling
@@ -119,7 +155,9 @@ dt-design-system/
├── packages/
│ ├── tokens/ # Design token definitions + CSS generation
│ ├── web/ # CSS themes and components
│ ├── react/ # React web components
│ ├── react-native/ # React Native Paper components
│ ├── tailwind-preset/ # Tailwind CSS v3 preset
│ ├── hex-background/ # 3D hexagon grid (Three.js)
│ └── showcase/ # Demo apps (desktop + mobile)
├── turbo.json # Turbo pipeline config

594
package-lock.json generated
View File

@@ -5489,6 +5489,128 @@
"nanoid": "^3.3.11"
}
},
"node_modules/@react-three/fiber": {
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.5.0.tgz",
"integrity": "sha512-FiUzfYW4wB1+PpmsE47UM+mCads7j2+giRBltfwH7SNhah95rqJs3ltEs9V3pP8rYdS0QlNne+9Aj8dS/SiaIA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.17.8",
"@types/webxr": "*",
"base64-js": "^1.5.1",
"buffer": "^6.0.3",
"its-fine": "^2.0.0",
"react-use-measure": "^2.1.7",
"scheduler": "^0.27.0",
"suspend-react": "^0.1.3",
"use-sync-external-store": "^1.4.0",
"zustand": "^5.0.3"
},
"peerDependencies": {
"expo": ">=43.0",
"expo-asset": ">=8.4",
"expo-file-system": ">=11.0",
"expo-gl": ">=11.0",
"react": ">=19 <19.3",
"react-dom": ">=19 <19.3",
"react-native": ">=0.78",
"three": ">=0.156"
},
"peerDependenciesMeta": {
"expo": {
"optional": true
},
"expo-asset": {
"optional": true
},
"expo-file-system": {
"optional": true
},
"expo-gl": {
"optional": true
},
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/@react-three/fiber/node_modules/@types/react-reconciler": {
"version": "0.28.9",
"resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz",
"integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*"
}
},
"node_modules/@react-three/fiber/node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/@react-three/fiber/node_modules/its-fine": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz",
"integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==",
"license": "MIT",
"dependencies": {
"@types/react-reconciler": "^0.28.9"
},
"peerDependencies": {
"react": "^19.0.0"
}
},
"node_modules/@react-three/fiber/node_modules/zustand": {
"version": "5.0.11",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.11.tgz",
"integrity": "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.27",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
@@ -6090,7 +6212,6 @@
"version": "19.2.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
@@ -6179,7 +6300,6 @@
"version": "0.5.24",
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
"integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/yargs": {
@@ -6636,85 +6756,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/archiver": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz",
"integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"archiver-utils": "^2.1.0",
"async": "^3.2.4",
"buffer-crc32": "^0.2.1",
"readable-stream": "^3.6.0",
"readdir-glob": "^1.1.2",
"tar-stream": "^2.2.0",
"zip-stream": "^4.1.0"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/archiver-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz",
"integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"glob": "^7.1.4",
"graceful-fs": "^4.2.0",
"lazystream": "^1.0.0",
"lodash.defaults": "^4.2.0",
"lodash.difference": "^4.5.0",
"lodash.flatten": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.union": "^4.6.0",
"normalize-path": "^3.0.0",
"readable-stream": "^2.0.0"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/archiver-utils/node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/archiver-utils/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/archiver-utils/node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/are-we-there-yet": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz",
@@ -8125,23 +8166,6 @@
"node": ">=0.10.0"
}
},
"node_modules/compress-commons": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz",
"integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"buffer-crc32": "^0.2.13",
"crc32-stream": "^4.0.2",
"normalize-path": "^3.0.0",
"readable-stream": "^3.6.0"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/compressible": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
@@ -8397,7 +8421,8 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
"dev": true,
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/cosmiconfig": {
"version": "9.0.1",
@@ -8457,35 +8482,6 @@
"buffer": "^5.1.0"
}
},
"node_modules/crc-32": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"crc32": "bin/crc32.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/crc32-stream": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz",
"integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"crc-32": "^1.2.0",
"readable-stream": "^3.4.0"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -8567,7 +8563,6 @@
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"devOptional": true,
"license": "MIT"
},
"node_modules/data-uri-to-buffer": {
@@ -9148,61 +9143,6 @@
"node": ">=14.0.0"
}
},
"node_modules/electron-builder-squirrel-windows": {
"version": "25.1.8",
"resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-25.1.8.tgz",
"integrity": "sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"app-builder-lib": "25.1.8",
"archiver": "^5.3.1",
"builder-util": "25.1.7",
"fs-extra": "^10.1.0"
}
},
"node_modules/electron-builder-squirrel-windows/node_modules/fs-extra": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/electron-builder-squirrel-windows/node_modules/jsonfile": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/electron-builder-squirrel-windows/node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/electron-builder/node_modules/fs-extra": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
@@ -10219,14 +10159,6 @@
"node": ">= 0.6"
}
},
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/fs-extra": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
@@ -11159,14 +11091,6 @@
"node": ">=8"
}
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/isbinaryfile": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz",
@@ -11605,56 +11529,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/lazystream": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz",
"integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"readable-stream": "^2.0.5"
},
"engines": {
"node": ">= 0.6.3"
}
},
"node_modules/lazystream/node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/lazystream/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/lazystream/node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/leven": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
@@ -11982,38 +11856,6 @@
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"license": "MIT"
},
"node_modules/lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/lodash.difference": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
"integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/lodash.flatten": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
"integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/lodash.startcase": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz",
@@ -12027,14 +11869,6 @@
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==",
"license": "MIT"
},
"node_modules/lodash.union": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
"integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/log-symbols": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@@ -13733,14 +13567,6 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
@@ -14224,6 +14050,21 @@
"node": ">=0.10.0"
}
},
"node_modules/react-use-measure": {
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz",
"integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==",
"license": "MIT",
"peerDependencies": {
"react": ">=16.13",
"react-dom": ">=16.13"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
},
"node_modules/read-binary-file-arch": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz",
@@ -14288,42 +14129,6 @@
"node": ">= 6"
}
},
"node_modules/readdir-glob": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz",
"integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"minimatch": "^5.1.0"
}
},
"node_modules/readdir-glob/node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/readdir-glob/node_modules/minimatch": {
"version": "5.1.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
"dev": true,
"license": "ISC",
"peer": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -14725,8 +14530,7 @@
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/semver": {
"version": "7.7.4",
@@ -15403,7 +15207,6 @@
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz",
"integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"react": ">=17.0"
@@ -15507,24 +15310,6 @@
"streamx": "^2.15.0"
}
},
"node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/tar/node_modules/minipass": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
@@ -15703,7 +15488,6 @@
"version": "0.160.1",
"resolved": "https://registry.npmjs.org/three/-/three-0.160.1.tgz",
"integrity": "sha512-Bgl2wPJypDOZ1stAxwfWAcJ0WQf7QzlptsxkjYiURPz+n5k4RBDLsq+6f9Y75TYxn6aHLcWz+JNmwTOXWrQTBQ==",
"dev": true,
"license": "MIT"
},
"node_modules/throat": {
@@ -16654,45 +16438,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zip-stream": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz",
"integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"archiver-utils": "^3.0.4",
"compress-commons": "^4.1.2",
"readable-stream": "^3.6.0"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/zip-stream/node_modules/archiver-utils": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz",
"integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"glob": "^7.2.3",
"graceful-fs": "^4.2.0",
"lazystream": "^1.0.0",
"lodash.defaults": "^4.2.0",
"lodash.difference": "^4.5.0",
"lodash.flatten": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.union": "^4.6.0",
"normalize-path": "^3.0.0",
"readable-stream": "^3.6.0"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
@@ -16723,7 +16468,7 @@
},
"packages/hex-background": {
"name": "@dangerousthings/hex-background",
"version": "0.1.0",
"version": "0.2.0",
"license": "MIT",
"devDependencies": {
"@react-three/fiber": "^8.18.0",
@@ -16733,6 +16478,9 @@
"three": "^0.160.0",
"typescript": "^5.7.0"
},
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@react-three/fiber": ">=8",
"react": ">=18",
@@ -16860,22 +16608,6 @@
"react": "^18.0.0"
}
},
"packages/hex-background/node_modules/react-use-measure": {
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz",
"integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"react": ">=16.13",
"react-dom": ">=16.13"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
},
"packages/hex-background/node_modules/scheduler": {
"version": "0.21.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz",
@@ -16888,7 +16620,7 @@
},
"packages/react": {
"name": "@dangerousthings/react",
"version": "0.1.0",
"version": "1.0.0",
"license": "MIT",
"devDependencies": {
"@dangerousthings/tokens": "*",
@@ -16899,6 +16631,9 @@
"react-dom": "^18.0.0",
"typescript": "^5.8.0"
},
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dangerousthings/tokens": "*",
"@dangerousthings/web": "*",
@@ -16908,7 +16643,7 @@
},
"packages/react-native": {
"name": "@dangerousthings/react-native",
"version": "0.2.0",
"version": "0.3.0",
"license": "MIT",
"dependencies": {
"@dangerousthings/tokens": "*"
@@ -16921,6 +16656,9 @@
"react-native-svg": "^15.15.3",
"typescript": "^5.9.3"
},
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-native": ">=0.72.0",
@@ -16995,13 +16733,17 @@
"packages/showcase/desktop": {
"name": "@dangerousthings/showcase-desktop",
"version": "0.0.0",
"license": "MIT",
"dependencies": {
"@dangerousthings/hex-background": "^0.2.0",
"@dangerousthings/react": "*",
"@dangerousthings/tokens": "*",
"@dangerousthings/web": "*",
"@react-three/fiber": "^9.5.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-icons": "^5.6.0"
"react-icons": "^5.6.0",
"three": "^0.183.2"
},
"devDependencies": {
"@dangerousthings/tailwind-preset": "*",
@@ -17074,9 +16816,16 @@
"loose-envify": "^1.1.0"
}
},
"packages/showcase/desktop/node_modules/three": {
"version": "0.183.2",
"resolved": "https://registry.npmjs.org/three/-/three-0.183.2.tgz",
"integrity": "sha512-di3BsL2FEQ1PA7Hcvn4fyJOlxRRgFYBpMTcyOgkwJIaDOdJMebEFPA+t98EvjuljDx4hNulAGwF6KIjtwI5jgQ==",
"license": "MIT"
},
"packages/showcase/mobile": {
"name": "@dangerousthings/showcase-mobile",
"version": "0.0.0",
"license": "MIT",
"dependencies": {
"@dangerousthings/react-native": "*",
"@dangerousthings/tokens": "*",
@@ -17379,31 +17128,40 @@
},
"packages/tailwind-preset": {
"name": "@dangerousthings/tailwind-preset",
"version": "0.1.0",
"version": "0.1.1",
"license": "MIT",
"devDependencies": {
"tailwindcss": "^3.4.0",
"typescript": "^5.8.0"
},
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"tailwindcss": "^3.4.0"
}
},
"packages/tokens": {
"name": "@dangerousthings/tokens",
"version": "0.2.0",
"version": "0.3.0",
"license": "MIT",
"devDependencies": {
"@types/node": "^25.3.3",
"typescript": "^5.7.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"packages/web": {
"name": "@dangerousthings/web",
"version": "0.2.0",
"version": "0.3.0",
"license": "MIT",
"dependencies": {
"@dangerousthings/tokens": "*"
},
"engines": {
"node": ">=18.0.0"
}
}
}

View File

@@ -77,7 +77,7 @@ function Background() {
## Monorepo
Part of the [DT Design System](https://github.com/nicholasgriffintn/dt-design-system) monorepo.
Part of the [DT Design System](https://github.com/dangerous-tac0s/dt-design-system) monorepo.
## License

View File

@@ -124,7 +124,7 @@ variantToClassName("warning"); // token-level variant mapping
## Monorepo
Part of the [DT Design System](https://github.com/nicholasgriffintn/dt-design-system) monorepo.
Part of the [DT Design System](https://github.com/dangerous-tac0s/dt-design-system) monorepo.
## License

View File

@@ -1,20 +1,25 @@
/**
* DTFeatureLegend — Grid of product feature icons with rotated labels.
* DTFeatureLegend — Interactive grid of product feature icons with rotated labels.
*
* CSS reference: feature-legend.css .dt-feature-legend, .dt-feature-legend-header,
* .dt-feature-legend-grid, .dt-feature-legend-item, .dt-feature-supported, etc.
* Matches dt-shopify-storefront UseCaseLegend: header bar with hover details,
* rotated vertical labels (above for row 0, below for row 1), ? toggle,
* bordered grid with dark cell backgrounds, and pulse-on-hover icons.
*
* CSS reference: feature-legend.css
*/
import type { ReactNode, CSSProperties } from 'react';
import { useState, type ReactNode, type CSSProperties } from 'react';
import type { DTVariant } from '@dangerousthings/tokens';
import { cx } from '../utils/cx';
import { getVariantClass, featureStateToVariant } from '../utils/variantClasses';
import { getVariantClass } from '../utils/variantClasses';
export interface DTFeatureItem {
key: string;
name: string;
icon: ReactNode;
state: 'supported' | 'disabled' | 'unsupported';
/** Optional detail text shown in the header on hover */
detail?: string;
}
const stateClassMap: Record<DTFeatureItem['state'], string> = {
@@ -32,6 +37,8 @@ interface DTFeatureLegendProps {
columns?: number;
/** Icon size in px @default 42 */
iconSize?: number;
/** Show rotated labels initially @default true */
showLabels?: boolean;
className?: string;
style?: CSSProperties;
}
@@ -42,35 +49,93 @@ export function DTFeatureLegend({
variant = 'normal',
columns = 5,
iconSize = 42,
showLabels: initialShowLabels = true,
className,
style,
}: DTFeatureLegendProps) {
// Suppress unused import warning — featureStateToVariant is available for consumers
void featureStateToVariant;
const [showLabels, setShowLabels] = useState(initialShowLabels);
const [hoveredFeature, setHoveredFeature] = useState<string | null>(null);
const hoveredItem = hoveredFeature
? features.find(f => f.key === hoveredFeature)
: null;
const rowCount = Math.ceil(features.length / columns);
const hasBottomRow = rowCount >= 2;
return (
<div
className={cx('dt-feature-legend', getVariantClass(variant), className)}
style={style}>
{/* Header bar — title + hovered feature detail + ? toggle */}
{title && (
<div className="dt-feature-legend-header">{title}</div>
)}
<div
className="dt-feature-legend-grid"
style={{ '--dt-feature-columns': columns } as CSSProperties}>
{features.map(feature => (
<div
key={feature.key}
className={cx('dt-feature-legend-item', stateClassMap[feature.state])}
style={{ width: `${100 / columns}%` }}>
<div
className="dt-feature-legend-icon"
style={{ width: `${iconSize}px`, height: `${iconSize}px` }}>
{feature.icon}
<div className="dt-feature-legend-header">
<div className="dt-feature-legend-header-content">
<div className="dt-feature-legend-title">{title}</div>
<div className="dt-feature-legend-detail">
{hoveredItem ? (
<span className={stateClassMap[hoveredItem.state]}>
{hoveredItem.name}{hoveredItem.detail ? ': ' : ''}
{hoveredItem.detail && <strong>{hoveredItem.detail}</strong>}
</span>
) : (
<span className="dt-feature-legend-hint">
Hover a feature for details
</span>
)}
</div>
<div className="dt-feature-legend-label">{feature.name}</div>
</div>
))}
<button
className="dt-feature-legend-toggle"
onClick={() => setShowLabels(v => !v)}
aria-label={showLabels ? 'Hide feature labels' : 'Show feature labels'}
title={showLabels ? 'Hide labels' : 'Show labels'}
type="button">
?
</button>
</div>
)}
{/* Icon grid with border */}
<div className="dt-feature-legend-body">
<div
className={cx(
'dt-feature-legend-grid',
showLabels && 'with-labels',
showLabels && hasBottomRow && 'with-labels-bottom',
)}
style={{ '--dt-feature-columns': columns } as CSSProperties}>
{features.map((feature, index) => {
const row = Math.floor(index / columns);
const labelPosition = rowCount >= 2 ? (row === 0 ? 'above' : 'below') : 'above';
return (
<div
key={feature.key}
className={cx('dt-feature-legend-item', stateClassMap[feature.state])}
style={{ width: `${100 / columns}%` }}
onMouseEnter={() => setHoveredFeature(feature.key)}
onMouseLeave={() => setHoveredFeature(null)}>
{/* Rotated vertical label — always in DOM, visibility via CSS */}
<div className={cx(
'dt-feature-legend-rotated-label',
labelPosition === 'below' ? 'label-below' : 'label-above',
showLabels && 'label-visible',
)}>
<span>{feature.name}</span>
</div>
<div
className="dt-feature-legend-icon"
style={{ width: `${iconSize}px`, height: `${iconSize}px` }}>
{feature.icon}
</div>
</div>
);
})}
</div>
</div>
</div>
);

View File

@@ -1,7 +1,10 @@
/**
* DTRadioGroup — Hexagonal radio buttons.
*
* CSS reference: forms-dt.css input[type="radio"], .dt-radio-option
* Uses a custom span for the hexagon indicator since ::before pseudo-elements
* on <input> are unreliable in Chromium/Electron.
*
* CSS reference: forms-dt.css .dt-radio-hex, .dt-radio-option
*/
import { useId, type CSSProperties } from 'react';
@@ -39,20 +42,27 @@ export function DTRadioGroup({
className={cx('dt-radio-group', className)}
style={{ display: 'flex', flexDirection: 'column', gap: '4px', ...style }}
role="radiogroup">
{options.map(option => (
<label
key={option.value}
className={cx('dt-radio-option', value === option.value && 'selected')}>
<input
type="radio"
name={groupName}
value={option.value}
checked={value === option.value}
onChange={() => onChange(option.value)}
/>
<span>{option.label}</span>
</label>
))}
{options.map(option => {
const checked = value === option.value;
return (
<label
key={option.value}
className={cx('dt-radio-option', checked && 'selected')}>
<input
type="radio"
name={groupName}
value={option.value}
checked={checked}
onChange={() => onChange(option.value)}
style={{ position: 'absolute', opacity: 0, width: 0, height: 0 }}
/>
<span className={cx('dt-radio-hex', checked && 'checked')}>
<span className="dt-radio-hex-inner" />
</span>
<span>{option.label}</span>
</label>
);
})}
</div>
);
}

View File

@@ -25,12 +25,15 @@
"clean": "rm -rf dist release"
},
"dependencies": {
"@dangerousthings/hex-background": "^0.2.0",
"@dangerousthings/react": "*",
"@dangerousthings/tokens": "*",
"@dangerousthings/web": "*",
"@react-three/fiber": "^9.5.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-icons": "^5.6.0"
"react-icons": "^5.6.0",
"three": "^0.183.2"
},
"devDependencies": {
"@dangerousthings/tailwind-preset": "*",

View File

@@ -1,21 +1,23 @@
import { useState, useEffect } from 'react';
import { themes } from '@dangerousthings/tokens';
import type { ThemeBrand, ThemeMode } from '@dangerousthings/tokens';
import { HexGridBackground } from '@dangerousthings/hex-background';
import type { HexGridBackgroundProps } from '@dangerousthings/hex-background';
import { HomePage } from './pages/HomePage';
import { BevelsPage } from './pages/BevelsPage';
import { GlowsPage } from './pages/GlowsPage';
import { FormsPage } from './pages/FormsPage';
import { AnimationsPage } from './pages/AnimationsPage';
import { CardsAdvancedPage } from './pages/CardsAdvancedPage';
import { TokensPage } from './pages/TokensPage';
import { HexBackgroundPage } from './pages/HexBackgroundPage';
const navPages = [
{ hash: '', label: 'Home' },
{ hash: 'bevels', label: 'Bevels' },
{ hash: 'glows', label: 'Glows' },
{ hash: 'forms', label: 'Forms' },
{ hash: 'animations', label: 'Animations' },
{ hash: 'cards-advanced', label: 'Advanced Cards' },
{ hash: 'hex-background', label: 'Hex Background' },
{ hash: 'tokens', label: 'Tokens' },
];
@@ -23,6 +25,7 @@ export function App() {
const [brand, setBrand] = useState<ThemeBrand>('dt');
const [theme, setTheme] = useState<ThemeMode>('dark');
const [currentHash, setCurrentHash] = useState('');
const [hexProps, setHexProps] = useState<HexGridBackgroundProps>({});
// Sync brand/theme to <html> so CSS custom properties cascade everywhere
useEffect(() => {
@@ -43,10 +46,10 @@ export function App() {
function renderPage() {
switch (currentHash) {
case 'bevels': return <BevelsPage />;
case 'glows': return <GlowsPage />;
case 'forms': return <FormsPage />;
case 'animations': return <AnimationsPage />;
case 'cards-advanced': return <CardsAdvancedPage />;
case 'hex-background': return <HexBackgroundPage hexProps={hexProps} onHexPropsChange={setHexProps} />;
case 'tokens': return <TokensPage brand={brand} />;
default: return <HomePage />;
}
@@ -92,6 +95,7 @@ export function App() {
))}
</div>
</nav>
<HexGridBackground {...hexProps} />
<main id="content">
{renderPage()}
</main>

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect, useRef } from 'react';
import { DTCard, DTStaggerContainer } from '@dangerousthings/react';
import { Section, Row, CodeLabel } from '../components/Section';
@@ -25,6 +25,69 @@ function AnimBox({ className, label }: { className: string; label: string }) {
);
}
const VARIANTS = ['normal', 'emphasis', 'warning', 'success', 'other'] as const;
function ProgressBarDemo() {
const [progress, setProgress] = useState<number[]>([0, 0, 0, 0, 0]);
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
const [running, setRunning] = useState(false);
const animate = () => {
// Reset then animate up
setProgress([0, 0, 0, 0, 0]);
setRunning(true);
let tick = 0;
if (intervalRef.current) clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => {
tick++;
setProgress(prev =>
prev.map((_, i) => Math.min(100, Math.round(tick * (1.5 + i * 0.4))))
);
if (tick > 80) {
if (intervalRef.current) clearInterval(intervalRef.current);
setRunning(false);
}
}, 40);
};
useEffect(() => {
// Auto-run on mount
const t = setTimeout(animate, 500);
return () => {
clearTimeout(t);
if (intervalRef.current) clearInterval(intervalRef.current);
};
}, []);
return (
<div>
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap', marginBottom: 16 }}>
{VARIANTS.map((v, i) => (
<DTCard
key={v}
variant={v}
title={`${v.toUpperCase()} ${progress[i]}%`}
progress={progress[i]}
style={{ width: 160, transition: 'all 0.1s ease-out' }}
>
<div className="card-body" style={{ fontSize: '0.75rem', minHeight: 60, display: 'flex', alignItems: 'center' }}>
Progress fills the left edge from bottom to top
</div>
</DTCard>
))}
</div>
<button
className="btn-secondary"
onClick={animate}
disabled={running}
type="button"
>
{running ? 'ANIMATING...' : 'REPLAY PROGRESS'}
</button>
</div>
);
}
export function AnimationsPage() {
const [entranceKey, setEntranceKey] = useState(0);
const [staggerKey, setStaggerKey] = useState(0);
@@ -126,6 +189,11 @@ export function AnimationsPage() {
<CodeLabel text=".dt-transition-accordion | .dt-transition-chevron | .dt-transition-progress" />
</Section>
<Section title="Card Progress Bar" description="Cards have a vertical progress bar on the left edge. Animate it by transitioning --dt-card-progress from 0 to 100.">
<ProgressBarDemo />
<CodeLabel text="<DTCard progress={value} /> — CSS ::after gradient driven by --dt-card-progress" />
</Section>
<Section title="Scrollbar Styling" description="Thin neon scrollbar scoped under [data-brand='dt'].">
<div
className="dt-scrollbar"

View File

@@ -33,30 +33,31 @@ const modes: DTVariant[] = ['normal', 'emphasis', 'warning', 'success', 'other']
const ico = (C: any) => createElement(C, { style: { fontSize: '2rem' } });
// Full chip feature legend (9 features — from storefront UseCaseLegend)
// detail text simulates what the storefront shows for an NExT implant
const chipFeatures: DTFeatureItem[] = [
{ key: 'smartphone', name: 'Smartphone', icon: ico(MdOutlinePhonelinkRing), state: 'supported' },
{ key: 'access_control', name: 'Access Control', icon: ico(MdOutlineVpnKey), state: 'supported' },
{ key: 'digital_security', name: 'Digital Security', icon: ico(FaUserShield), state: 'supported' },
{ key: 'cryptography', name: 'Cryptography', icon: ico(LuBinary), state: 'unsupported' },
{ key: 'data_sharing', name: 'Data Sharing', icon: ico(MdOutlineMobileScreenShare), state: 'supported' },
{ key: 'payment', name: 'Payment', icon: ico(MdOutlineCreditCard), state: 'disabled' },
{ key: 'magic', name: 'UID Magic', icon: ico(MdOutlineCopyAll), state: 'supported' },
{ key: 'illumination', name: 'Illumination', icon: ico(MdOutlineLightbulb), state: 'unsupported' },
{ key: 'temperature', name: 'Temperature', icon: ico(MdOutlineThermostat), state: 'unsupported' },
{ key: 'smartphone', name: 'Smartphone', icon: ico(MdOutlinePhonelinkRing), state: 'supported', detail: 'Full NFC smartphone support' },
{ key: 'access_control', name: 'Access Control', icon: ico(MdOutlineVpnKey), state: 'supported', detail: 'DESFire, MIFARE Classic, iCLASS' },
{ key: 'digital_security', name: 'Digital Security', icon: ico(FaUserShield), state: 'supported', detail: 'FIDO2 / WebAuthn' },
{ key: 'cryptography', name: 'Cryptography', icon: ico(LuBinary), state: 'unsupported', detail: 'Not Supported' },
{ key: 'data_sharing', name: 'Data Sharing', icon: ico(MdOutlineMobileScreenShare), state: 'supported', detail: 'NDEF records, vCard, URL' },
{ key: 'payment', name: 'Payment', icon: ico(MdOutlineCreditCard), state: 'disabled', detail: 'Apex required' },
{ key: 'magic', name: 'Magic', icon: ico(MdOutlineCopyAll), state: 'supported', detail: 'Gen2 Magic UID' },
{ key: 'Illumination', name: 'Illumination', icon: ico(MdOutlineLightbulb), state: 'unsupported', detail: 'None' },
{ key: 'temperature', name: 'Sensors', icon: ico(MdOutlineThermostat), state: 'unsupported', detail: 'None' },
];
// Full biomagnet feature legend (4 features — from storefront MagnetUseCaseLegend)
const magnetFeatures: DTFeatureItem[] = [
{ key: 'sensing', name: 'Sensing', icon: ico(MdOutlineSensors), state: 'supported' },
{ key: 'lifting', name: 'Lifting', icon: ico(MdOutlineFitbit), state: 'supported' },
{ key: 'haptics', name: 'Haptics', icon: ico(MdOutlineVibration), state: 'supported' },
{ key: 'polarity', name: 'Polarity Detection', icon: ico(MdOutlineExplore), state: 'unsupported' },
{ key: 'sensing', name: 'Sensing', icon: ico(MdOutlineSensors), state: 'supported', detail: 'Electromagnetic field detection' },
{ key: 'lifting', name: 'Lifting', icon: ico(MdOutlineFitbit), state: 'supported', detail: '2.1 kg lifting force' },
{ key: 'haptics', name: 'Haptics', icon: ico(MdOutlineVibration), state: 'supported', detail: 'Tactile vibration feedback' },
{ key: 'polarity', name: 'Polarity Detection', icon: ico(MdOutlineExplore), state: 'unsupported', detail: 'Not Supported' },
];
// Full aesthetic feature legend (2 features — from storefront AestheticUseCaseLegend)
const aestheticFeatures: DTFeatureItem[] = [
{ key: 'illumination', name: 'Illumination', icon: ico(MdLightbulbOutline), state: 'supported' },
{ key: 'prominence', name: 'Prominence', icon: ico(MdOutlineVisibility), state: 'supported' },
{ key: 'illumination', name: 'Illumination', icon: ico(MdLightbulbOutline), state: 'supported', detail: 'LED: Red, Green, Blue, White' },
{ key: 'prominence', name: 'Prominence', icon: ico(MdOutlineVisibility), state: 'supported', detail: 'High visibility under skin' },
];
export function CardsAdvancedPage() {
@@ -108,30 +109,36 @@ export function CardsAdvancedPage() {
<CodeLabel text=".dt-badge-parent > .dt-card-badge — badge positioned on card image container" />
</Section>
<Section title="Buttons" description="Beveled buttons with active/selected states, mode colors, and level indentation.">
<Section title="Buttons" description="Beveled buttons with active/selected states and mode colors. Nested levels use inline paddingLeft.">
<div style={{ maxWidth: 300 }}>
{['All Products', 'NFC Implants', 'RFID Tags', 'Accessories', 'Lab Products'].map((item, i) => (
{[
{ name: 'All Products', cls: ' active', pad: 0 },
{ name: 'NFC Implants', cls: '', pad: 0 },
{ name: 'RFID Tags', cls: '', pad: 32 },
{ name: 'Accessories', cls: '', pad: 32 },
{ name: 'Lab Products', cls: ' mode-warning', pad: 0 },
].map(item => (
<button
key={item}
className={`dt-menu-item${i === 0 ? ' active' : ''}${i === 4 ? ' mode-warning' : ''}`}
style={i > 1 && i < 4 ? { '--dt-menu-level': '1' } as CSSProperties : undefined}
key={item.name}
className={`dt-menu-item${item.cls}`}
style={item.pad ? { paddingLeft: item.pad } : undefined}
type="button"
>
{item}
{item.name}
</button>
))}
</div>
<CodeLabel text=".dt-menu-item | .dt-menu-item.active | --dt-menu-level: 1" />
<CodeLabel text=".dt-menu-item | .active | paddingLeft for nesting" />
</Section>
<Section title="Feature Legends" description="Product feature grids from the storefront. Icons indicate feature capabilities; color indicates state.">
<DTFeatureLegend features={chipFeatures} title="NFC CHIP FEATURES" variant="normal" columns={5} />
<Section title="Feature Legends" description="Interactive product feature grids from the storefront. Hover icons for details. Toggle labels with the ? button.">
<DTFeatureLegend features={chipFeatures} title="NExT Features" variant="normal" columns={5} />
<div style={{ height: 'var(--space-6)' }} />
<DTFeatureLegend features={magnetFeatures} title="BIOMAGNET FEATURES" variant="emphasis" columns={4} />
<DTFeatureLegend features={magnetFeatures} title="Titan Features" variant="emphasis" columns={4} />
<div style={{ height: 'var(--space-6)' }} />
<DTFeatureLegend features={aestheticFeatures} title="AESTHETIC FEATURES" variant="other" columns={2} />
<DTFeatureLegend features={aestheticFeatures} title="xLED Features" variant="other" columns={2} />
<div style={{ height: 'var(--space-4)' }} />
<CodeLabel text="<DTFeatureLegend features={chipFeatures} title='NFC CHIP FEATURES' variant='normal' />" />
<CodeLabel text="<DTFeatureLegend features={chipFeatures} title='NExT Features' variant='normal' />" />
<CodeLabel text="<DTFeatureLegend features={magnetFeatures} variant='emphasis' columns={4} />" />
<CodeLabel text="<DTFeatureLegend features={aestheticFeatures} variant='other' columns={2} />" />
<div style={{ marginTop: 'var(--space-3)', display: 'flex', gap: 'var(--space-4)', fontSize: '0.75rem' }}>

View File

@@ -102,20 +102,26 @@ export function FormsPage() {
<CodeLabel text=".dt-filter-header | .dt-filter-header.active" />
</Section>
<Section title="Buttons" description="Beveled buttons with active state, mode colors, and level indentation.">
<Section title="Buttons" description="Beveled buttons with active state and mode colors. Nested levels use inline paddingLeft.">
<div style={{ maxWidth: 300 }}>
{['All Products', 'NFC Implants', 'RFID Tags', 'Accessories', 'Lab Products'].map((name, i) => (
{[
{ name: 'All Products', cls: ' active', pad: 0 },
{ name: 'NFC Implants', cls: '', pad: 0 },
{ name: 'RFID Tags', cls: '', pad: 32 },
{ name: 'Accessories', cls: '', pad: 32 },
{ name: 'Lab Products', cls: ' mode-warning', pad: 0 },
].map(item => (
<button
key={name}
className={`dt-menu-item${i === 0 ? ' active' : ''}${i === 4 ? ' mode-warning' : ''}`}
style={(i === 2 || i === 3) ? { '--dt-menu-level': '1' } as React.CSSProperties : undefined}
key={item.name}
className={`dt-menu-item${item.cls}`}
style={item.pad ? { paddingLeft: item.pad } : undefined}
type="button"
>
{name}
{item.name}
</button>
))}
</div>
<CodeLabel text=".dt-menu-item | .active | --dt-menu-level: 1" />
<CodeLabel text=".dt-menu-item | .active | paddingLeft for nesting" />
</Section>
</>
);

View File

@@ -1,81 +0,0 @@
import { DTCard } from '@dangerousthings/react';
import { Section, Row, CodeLabel } from '../components/Section';
export function GlowsPage() {
return (
<>
<h1 className="page-title">Glows</h1>
<p className="page-subtitle">Neon drop-shadow and text-shadow effects. Active on the DT brand.</p>
<Section title="Button Glows" description="filter: drop-shadow() follows clip-path shape. Hover for enhanced glow.">
<Row>
<button className="btn-primary" type="button">PRIMARY GLOW</button>
<button className="btn-danger" type="button">DANGER GLOW</button>
</Row>
<CodeLabel text={'.btn-primary / .btn-danger — automatic on [data-brand="dt"]'} />
<br />
<Row>
<button className="btn-secondary" type="button">SECONDARY (HOVER)</button>
</Row>
<CodeLabel text=".btn-secondary — glow on hover" />
</Section>
<Section title="Link Glow" description="text-shadow on hover. Respects --dt-glow-color when set by a parent mode.">
<Row>
<a href="#" style={{ color: 'var(--color-primary)', fontWeight: 600, fontSize: '1.125rem' }}>
Hover this link for text glow
</a>
</Row>
<CodeLabel text="a:hover — uses var(--dt-glow-color, var(--color-primary))" />
</Section>
<Section title="Mode-Aware Link Glow" description="Links inside mode containers glow with that mode's color.">
<Row>
{(['normal', 'emphasis', 'warning', 'success', 'other'] as const).map(mode => (
<div key={mode} className={`mode-${mode}`} style={{ padding: 'var(--space-3)' }}>
<a href="#" style={{ fontWeight: 600, fontSize: '1rem' }}>{mode.toUpperCase()}</a>
</div>
))}
</Row>
<CodeLabel text=".mode-emphasis a:hover — glows yellow via --dt-glow-color" />
</Section>
<Section title="Terminal Inset Glow" description="Inset + outer box-shadow. No clip-path so box-shadow works directly.">
<div className="terminal dt-accent-top">
<code>{'$ dt-web-theme --install\n> Installing design tokens...\n> Applying cyberpunk aesthetic...\n> Done. Welcome to the future.'}</code>
</div>
<CodeLabel text={'.terminal — automatic on [data-brand="dt"]'} />
</Section>
<Section title="Card Hover Glow" description="filter: drop-shadow() respects the beveled clip-path.">
<DTCard title="HOVER ME" style={{ cursor: 'pointer' }}>
<div className="card-body">Cards get a drop-shadow glow on hover.</div>
</DTCard>
<CodeLabel text={'.card:hover — automatic on [data-brand="dt"]'} />
</Section>
<Section title="Input Focus Glow" description="box-shadow: 0 4px 0 1px — bright bar beneath the input.">
<input
type="text"
className="input"
placeholder="Click to focus — see the glow bar"
style={{ maxWidth: 400 }}
/>
<CodeLabel text={'.input:focus — automatic on [data-brand="dt"]'} />
</Section>
<Section title="Glow Utilities" description="Generic utility classes for applying glow effects to any element.">
<Row>
<div className="dt-glow" style={{ background: 'var(--color-primary)', padding: 'var(--space-4) var(--space-6)', display: 'inline-block', fontWeight: 700, color: 'var(--color-bg)' }}>.dt-glow</div>
<div className="dt-glow-strong" style={{ background: 'var(--color-primary)', padding: 'var(--space-4) var(--space-6)', display: 'inline-block', fontWeight: 700, color: 'var(--color-bg)' }}>.dt-glow-strong</div>
</Row>
<CodeLabel text=".dt-glow | .dt-glow-strong" />
<Row>
<div className="dt-glow-inset" style={{ padding: 'var(--space-4) var(--space-6)', display: 'inline-block', fontWeight: 700, border: '1px solid var(--color-border)' }}>.dt-glow-inset</div>
<div className="dt-text-glow" style={{ fontSize: '1.25rem', fontWeight: 700, color: 'var(--color-primary)', display: 'inline-block' }}>.dt-text-glow</div>
</Row>
<CodeLabel text=".dt-glow-inset | .dt-text-glow" />
</Section>
</>
);
}

View File

@@ -0,0 +1,112 @@
import type { HexGridBackgroundProps } from '@dangerousthings/hex-background';
import { Section } from '../components/Section';
const DEFAULTS: Required<HexGridBackgroundProps> = {
opacity: 0.5,
hexRadius: 0.5,
margin: 0.05,
maxHeight: 3,
animationInterval: 1500,
cameraSpeed: 0.02,
cameraRadius: 8,
fov: 40,
};
interface SliderConfig {
key: keyof HexGridBackgroundProps;
label: string;
min: number;
max: number;
step: number;
}
const sliders: SliderConfig[] = [
{ key: 'opacity', label: 'Opacity', min: 0, max: 1, step: 0.05 },
{ key: 'hexRadius', label: 'Hex Radius', min: 0.1, max: 2, step: 0.1 },
{ key: 'margin', label: 'Margin', min: 0, max: 0.5, step: 0.01 },
{ key: 'maxHeight', label: 'Max Height', min: 0.5, max: 10, step: 0.5 },
{ key: 'animationInterval', label: 'Animation Interval (ms)', min: 200, max: 5000, step: 100 },
{ key: 'cameraSpeed', label: 'Camera Speed', min: 0, max: 0.2, step: 0.005 },
{ key: 'cameraRadius', label: 'Camera Radius', min: 3, max: 20, step: 0.5 },
{ key: 'fov', label: 'Field of View', min: 20, max: 90, step: 1 },
];
interface HexBackgroundPageProps {
hexProps: HexGridBackgroundProps;
onHexPropsChange: (props: HexGridBackgroundProps) => void;
}
export function HexBackgroundPage({ hexProps, onHexPropsChange }: HexBackgroundPageProps) {
const handleChange = (key: keyof HexGridBackgroundProps, value: number) => {
onHexPropsChange({ ...hexProps, [key]: value });
};
const handleReset = () => {
onHexPropsChange({ ...DEFAULTS });
};
return (
<>
<h1 className="page-title">Hex Background</h1>
<p className="page-subtitle">
3D hexagon grid background powered by Three.js + React Three Fiber.
Adjust parameters below changes apply to the global background in real time.
</p>
<Section title="Parameters" description="All HexGridBackground props exposed as controls.">
<div style={{ display: 'grid', gap: 'var(--space-4)' }}>
{sliders.map(({ key, label, min, max, step }) => (
<div key={key} style={{ display: 'grid', gridTemplateColumns: '1fr auto', alignItems: 'center', gap: 'var(--space-4)' }}>
<label style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
<span style={{ fontSize: '0.8rem', color: 'var(--color-text-muted)', textTransform: 'uppercase', letterSpacing: '0.1em' }}>
{label}
</span>
<input
type="range"
min={min}
max={max}
step={step}
value={hexProps[key] ?? DEFAULTS[key]}
onChange={e => handleChange(key, parseFloat(e.target.value))}
style={{ width: '100%', accentColor: 'var(--color-primary)' }}
/>
</label>
<span style={{
fontFamily: 'monospace',
fontSize: '0.85rem',
color: 'var(--color-primary)',
minWidth: '4.5em',
textAlign: 'right',
}}>
{hexProps[key] ?? DEFAULTS[key]}
</span>
</div>
))}
</div>
<button
className="dt-btn mode-normal"
onClick={handleReset}
type="button"
style={{ marginTop: 'var(--space-6)' }}
>
Reset to Defaults
</button>
</Section>
<Section title="Usage" description="Drop-in component for any React app with Three.js.">
<div className="terminal dt-accent-top">
<code>{`import { HexGridBackground } from '@dangerousthings/hex-background';
// Full-viewport fixed background (renders behind content)
<HexGridBackground
opacity={${hexProps.opacity ?? DEFAULTS.opacity}}
hexRadius={${hexProps.hexRadius ?? DEFAULTS.hexRadius}}
maxHeight={${hexProps.maxHeight ?? DEFAULTS.maxHeight}}
cameraSpeed={${hexProps.cameraSpeed ?? DEFAULTS.cameraSpeed}}
/>`}</code>
</div>
</Section>
</>
);
}

View File

@@ -4,7 +4,6 @@ import { Section, CodeLabel } from '../components/Section';
const categories: { hash: string; title: string; desc: string; mode: DTVariant; count: number }[] = [
{ hash: 'bevels', title: 'Bevels', desc: 'Angular clip-path patterns — cards, buttons, badges, media frames, modals, drawers', mode: 'normal', count: 8 },
{ hash: 'glows', title: 'Glows', desc: 'Neon drop-shadow and text-shadow effects for the DT brand', mode: 'emphasis', count: 6 },
{ hash: 'forms', title: 'Forms', desc: 'Text inputs, checkboxes, switches, radios, progress bars, accordions, steppers', mode: 'success', count: 7 },
{ hash: 'animations', title: 'Animations', desc: 'Entrance animations, stagger containers, transition utilities, scrollbar styling', mode: 'other', count: 5 },
{ hash: 'cards-advanced', title: 'Advanced Cards', desc: 'Card color modes, progress bars, badge overlays, interactive bevel buttons, feature legend', mode: 'warning', count: 6 },

View File

@@ -102,7 +102,7 @@ Because the preset uses `var()` references, switching brands at runtime requires
## Monorepo
Part of the [DT Design System](https://github.com/nicholasgriffintn/dt-design-system) monorepo.
Part of the [DT Design System](https://github.com/dangerous-tac0s/dt-design-system) monorepo.
## License

View File

@@ -1,6 +1,6 @@
# @dangerousthings/web
Web CSS themes and components for the Dangerous Things design system — bevels, glows, and form styles powered by CSS custom properties.
Web CSS themes and components for the Dangerous Things design system — bevels, animations, and form styles powered by CSS custom properties.
## Install
@@ -24,7 +24,6 @@ Or import individual pieces:
/* Individual components */
@import "@dangerousthings/web/components/bevels.css";
@import "@dangerousthings/web/components/glows.css";
@import "@dangerousthings/web/components/forms-dt.css";
```
@@ -53,7 +52,6 @@ import { themes, brands } from "@dangerousthings/web/theme-registry";
| File | Description |
|------|-------------|
| `bevels.css` | Angular clip-path bevels for cards, buttons, labels, modals, drawers. Card color modes, selected states, progress bars, badge overlays, interactive bevel buttons |
| `glows.css` | Neon drop-shadow and text-shadow effects — mode-aware via `--dt-glow-color` |
| `forms-dt.css` | Text inputs, checkboxes, switches, radio buttons, progress bars, accordions, steppers, menu items, filter headers, filter overlays |
| `animations.css` | Entrance animations (scale-in, fade-in, slide-up), interactive animations (pulse, ping, spin), stagger container, transition utilities |
| `scrollbar.css` | Thin neon scrollbar styling scoped under `[data-brand="dt"]` |
@@ -67,8 +65,6 @@ import { themes, brands } from "@dangerousthings/web/theme-registry";
**Interactive Buttons**`.dt-btn` (outlined rectangle, bevels on hover/select)
**Glows**`.dt-glow`, `.dt-glow-strong`, `.dt-glow-inset`, `.dt-text-glow`
**Animations**`.dt-animate-scale-in`, `.dt-animate-fade-in`, `.dt-animate-slide-up`, `.dt-animate-pulse`, `.dt-animate-ping`, `.dt-animate-spin`, `.dt-stagger-container`
**Scrollbar**`.dt-scrollbar`, `.dt-scrollbar-mode`

View File

@@ -28,6 +28,7 @@
"main": "dist/index.css",
"exports": {
".": "./dist/index.css",
"./dist/index.css": "./dist/index.css",
"./tokens/*": "./dist/tokens/*",
"./components/*": "./dist/components/*",
"./fonts/*": "./dist/fonts/*",

View File

@@ -450,14 +450,13 @@
/* ============================================================================
Card Color Modes — per-instance color overrides
Sets --dt-card-color, --dt-card-color-selected, --dt-card-color-rgb,
--dt-glow-color, and --accent-mode for use by all card sub-components.
and --accent-mode for use by all card sub-components.
Source: dt-shopify-storefront .mode-* classes
============================================================================ */
[data-brand="dt"] .mode-normal {
--dt-card-color: var(--mode-normal);
--dt-card-color-rgb: var(--mode-normal-rgb);
--dt-card-color-selected: var(--mode-normal-selected);
--dt-glow-color: var(--mode-normal);
--accent-mode: var(--mode-emphasis);
}
@@ -465,7 +464,6 @@
--dt-card-color: var(--mode-emphasis);
--dt-card-color-rgb: var(--mode-emphasis-rgb);
--dt-card-color-selected: var(--mode-emphasis-selected);
--dt-glow-color: var(--mode-emphasis);
--accent-mode: var(--mode-normal);
}
@@ -473,7 +471,6 @@
--dt-card-color: var(--mode-warning);
--dt-card-color-rgb: var(--mode-warning-rgb);
--dt-card-color-selected: var(--mode-warning-selected);
--dt-glow-color: var(--mode-warning);
--accent-mode: var(--mode-emphasis);
}
@@ -481,7 +478,6 @@
--dt-card-color: var(--mode-success);
--dt-card-color-rgb: var(--mode-success-rgb);
--dt-card-color-selected: var(--mode-success-selected);
--dt-glow-color: var(--mode-success);
--accent-mode: var(--mode-emphasis);
}
@@ -489,7 +485,6 @@
--dt-card-color: var(--mode-other);
--dt-card-color-rgb: var(--mode-other-rgb);
--dt-card-color-selected: var(--mode-other-selected);
--dt-glow-color: var(--mode-other);
--accent-mode: var(--mode-normal);
}

View File

@@ -2,55 +2,184 @@
/* Source: dt-shopify-storefront UseCaseLegend component */
/* ============================================================================
Feature Legend — icon grid with rotated labels
Feature Legend — interactive icon grid with rotated labels, hover details,
? toggle, bordered grid with dark cell backgrounds.
============================================================================ */
.dt-feature-legend {
display: flex;
flex-direction: column;
gap: 0;
}
/* ---- Header bar ---- */
.dt-feature-legend-header {
padding: 8px 16px;
display: flex;
flex-direction: row;
background: var(--dt-card-color, var(--color-primary));
color: var(--color-bg);
transition: height 0.2s ease-out, opacity 0.2s ease-out;
}
.dt-feature-legend-header-content {
flex: 1;
display: flex;
flex-direction: column;
padding: 8px 12px 6px;
}
.dt-feature-legend-title {
font-weight: 700;
font-size: 1.75rem;
line-height: 1.1;
color: var(--color-bg);
}
.dt-feature-legend-detail {
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-top: 2px;
min-height: 1.25em;
}
.dt-feature-legend-detail .dt-feature-supported { color: var(--color-bg); }
.dt-feature-legend-detail .dt-feature-disabled { color: var(--color-bg); opacity: 0.7; }
.dt-feature-legend-detail .dt-feature-unsupported { color: #7f1d1d; }
.dt-feature-legend-hint {
opacity: 0.5;
}
/* ? toggle button on right edge */
.dt-feature-legend-toggle {
display: flex;
justify-content: center;
align-items: center;
min-width: 30px;
background: var(--dt-card-color, var(--color-primary));
color: var(--color-bg);
border: none;
font-weight: 700;
font-size: 1.25rem;
cursor: pointer;
transition: opacity 0.15s;
}
.dt-feature-legend-toggle:hover {
opacity: 0.8;
}
/* ---- Grid body ---- */
.dt-feature-legend-body {
display: flex;
flex-direction: row;
overflow: hidden;
}
.dt-feature-legend-grid {
display: flex;
flex-wrap: wrap;
gap: 0;
justify-content: space-around;
flex: 1;
border: 2px solid var(--dt-card-color, var(--color-primary));
padding-top: 8px;
padding-bottom: 8px;
overflow: visible;
transition: padding 0.3s ease-out;
}
.dt-feature-legend-grid.with-labels {
padding-top: 160px;
padding-bottom: 8px;
}
.dt-feature-legend-grid.with-labels.with-labels-bottom {
padding-bottom: 160px;
}
/* ---- Grid item / cell ---- */
.dt-feature-legend-item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
justify-content: center;
padding: 8px 4px;
flex: 0 0 20%; /* 5 columns */
background: var(--color-bg);
cursor: pointer;
}
/* ---- Icon ---- */
.dt-feature-legend-icon {
display: flex;
align-items: center;
justify-content: center;
font-size: 42px;
line-height: 1;
color: var(--dt-feature-color, var(--color-primary));
transition: opacity 0.15s;
}
.dt-feature-legend-label {
font-size: 0.75rem;
.dt-feature-legend-item:hover .dt-feature-legend-icon {
animation: dt-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
/* ---- Rotated vertical label ---- */
.dt-feature-legend-rotated-label {
position: absolute;
left: 0;
right: 0;
display: flex;
justify-content: center;
padding: 8px 2px;
z-index: 1;
opacity: 0;
transition: opacity 0.3s ease-out 0.15s; /* delay so padding opens first */
pointer-events: none;
}
.dt-feature-legend-rotated-label.label-visible {
opacity: 1;
pointer-events: auto;
}
.dt-feature-legend-rotated-label.label-above {
bottom: 100%;
}
.dt-feature-legend-rotated-label.label-below {
top: 100%;
}
.dt-feature-legend-rotated-label span {
writing-mode: vertical-rl;
transform: rotate(180deg);
color: var(--dt-feature-color, var(--color-primary));
font-size: 1rem;
font-weight: 600;
color: var(--dt-feature-color, var(--color-text-primary));
text-align: center;
line-height: 1.2;
overflow-wrap: break-word;
white-space: nowrap;
padding: 4px 2px;
}
/* Feature state color classes */
/* ---- Feature state color classes ---- */
.dt-feature-supported { --dt-feature-color: var(--mode-normal, var(--color-primary)); }
.dt-feature-disabled { --dt-feature-color: var(--mode-emphasis, var(--color-secondary)); }
.dt-feature-unsupported { --dt-feature-color: var(--mode-warning, var(--color-error)); }
/* ---- Variant header colors ---- */
.mode-normal .dt-feature-legend-header,
.mode-normal .dt-feature-legend-toggle { background: var(--mode-normal, var(--color-primary)); }
.mode-normal .dt-feature-legend-grid { border-color: var(--mode-normal, var(--color-primary)); }
.mode-emphasis .dt-feature-legend-header,
.mode-emphasis .dt-feature-legend-toggle { background: var(--mode-emphasis, var(--color-secondary)); }
.mode-emphasis .dt-feature-legend-grid { border-color: var(--mode-emphasis, var(--color-secondary)); }
.mode-warning .dt-feature-legend-header,
.mode-warning .dt-feature-legend-toggle { background: var(--mode-warning, var(--color-error)); }
.mode-warning .dt-feature-legend-grid { border-color: var(--mode-warning, var(--color-error)); }
.mode-success .dt-feature-legend-header,
.mode-success .dt-feature-legend-toggle { background: var(--mode-success, var(--color-accent)); }
.mode-success .dt-feature-legend-grid { border-color: var(--mode-success, var(--color-accent)); }
.mode-other .dt-feature-legend-header,
.mode-other .dt-feature-legend-toggle { background: var(--mode-other, var(--color-other)); }
.mode-other .dt-feature-legend-grid { border-color: var(--mode-other, var(--color-other)); }

View File

@@ -143,49 +143,38 @@
/* ============================================================================
Radio Button — Hexagonal indicator
Matches RN DTRadioGroup: outlined hexagon with inner filled dot on check.
Outer hexagon is always stroked (accent border); inner dot appears on check.
Uses a custom span (.dt-radio-hex) since ::before on <input> is unreliable.
============================================================================ */
[data-brand="dt"] input[type="radio"] {
appearance: none;
-webkit-appearance: none;
display: grid;
place-content: center;
[data-brand="dt"] .dt-radio-hex {
position: relative;
display: inline-block;
width: 22px;
height: 22px;
flex-shrink: 0;
margin: 0;
border: none;
background: var(--color-primary);
cursor: pointer;
clip-path: polygon(
50% 0%, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%
);
transition: background var(--transition-fast);
}
/* Inner surface — creates the outlined hexagon look (accent border + transparent center) */
[data-brand="dt"] input[type="radio"]::before {
content: '';
/* Inner surface — outlined hexagon look (accent border + dark center) */
[data-brand="dt"] .dt-radio-hex-inner {
position: absolute;
inset: 2px;
background: var(--color-bg);
clip-path: polygon(
50% 0%, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%
);
transition: background var(--transition-fast);
transition: all var(--transition-fast);
}
/* Checked: inner dot fills with accent color (smaller inset for the dot effect) */
[data-brand="dt"] input[type="radio"]:checked::before {
/* Checked: inner dot fills with accent color (smaller inset) */
[data-brand="dt"] .dt-radio-hex.checked .dt-radio-hex-inner {
inset: 5px;
background: var(--color-primary);
}
[data-brand="dt"] input[type="radio"]:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Radio group option container */
.dt-radio-option {
display: flex;
@@ -403,11 +392,6 @@
animation: dt-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
/* Nested level indentation */
.dt-menu-item[style*="--dt-menu-level"] {
padding-left: calc(0.75rem + var(--dt-menu-level, 0) * 1rem);
}
/* Classic brand: rounded, thinner borders */
[data-brand="classic"] .dt-menu-item {
border-top-width: 2px;

View File

@@ -1,81 +0,0 @@
/* dt-web-theme: Glow & Neon Effects */
/* Source: dt-shopify-storefront glow patterns */
/* */
/* Elements with clip-path use filter: drop-shadow() instead of box-shadow */
/* because clip-path clips box-shadow but filter follows the clipped shape. */
/* ============================================================================
Button Glows — filter: drop-shadow (buttons have clip-path)
============================================================================ */
[data-brand="dt"] .btn-primary {
box-shadow: none;
filter: drop-shadow(0 0 10px rgba(var(--color-primary-rgb), 0.3));
}
[data-brand="dt"] .btn-primary:hover {
box-shadow: none;
filter: drop-shadow(0 0 15px rgba(var(--color-accent-rgb), 0.4));
}
[data-brand="dt"] .btn-secondary:hover {
filter: drop-shadow(0 0 8px rgba(var(--color-primary-rgb), 0.3));
}
[data-brand="dt"] .btn-danger {
box-shadow: none;
filter: drop-shadow(0 0 10px rgba(var(--color-error-rgb), 0.3));
}
[data-brand="dt"] .btn-danger:hover {
box-shadow: none;
filter: drop-shadow(0 0 15px rgba(var(--color-error-rgb), 0.4));
}
/* ============================================================================
Link Glow
============================================================================ */
[data-brand="dt"] a:hover {
text-shadow: 0 0 8px var(--dt-glow-color, var(--color-primary));
}
/* ============================================================================
Terminal Inset Glow (no clip-path, box-shadow OK)
============================================================================ */
[data-brand="dt"] .terminal {
box-shadow: inset 0 0 10px rgba(var(--color-accent-rgb), 0.05),
0 0 10px rgba(var(--color-accent-rgb), 0.1);
}
/* ============================================================================
Card Hover Glow — filter: drop-shadow (cards have clip-path)
============================================================================ */
[data-brand="dt"] .card:hover,
[data-brand="dt"] .dt-bevel-card:hover {
filter: drop-shadow(0 0 8px rgba(var(--dt-card-color-rgb, var(--color-primary-rgb)), 0.3));
}
/* ============================================================================
Input Focus Glow (no clip-path on inputs, box-shadow OK)
============================================================================ */
[data-brand="dt"] .input:focus {
box-shadow: 0 4px 0 1px var(--color-primary);
}
/* ============================================================================
Generic Glow Utilities
============================================================================ */
.dt-glow {
filter: drop-shadow(0 0 10px rgba(var(--color-primary-rgb), 0.3));
}
.dt-glow-strong {
filter: drop-shadow(0 0 15px rgba(var(--color-primary-rgb), 0.5));
}
.dt-glow-inset {
box-shadow: inset 0 0 10px rgba(var(--color-primary-rgb), 0.1);
}
.dt-text-glow {
text-shadow: 0 0 8px currentColor;
}

View File

@@ -13,7 +13,6 @@
/* Component styles — DT visual identity */
@import './components/bevels.css';
@import './components/glows.css';
@import './components/forms-dt.css';
@import './components/animations.css';
@import './components/scrollbar.css';