Compare commits
23 Commits
changeset-
...
@dangerous
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1be0070abf | ||
|
|
c8c92d187d | ||
|
|
195c47602d | ||
|
|
d1e2bf1964 | ||
|
|
926a282535 | ||
|
|
2dcf86c346 | ||
|
|
8a91b21a11 | ||
|
|
766266b799 | ||
|
|
36420868f5 | ||
|
|
c8afd90527 | ||
|
|
34be1d0b9e | ||
|
|
6816ef4825 | ||
|
|
b319f15092 | ||
|
|
68234e66f4 | ||
|
|
2c9f7b8495 | ||
|
|
b137a99c78 | ||
|
|
4f477a50d8 | ||
|
|
741ee69079 | ||
|
|
89aaac9548 | ||
|
|
09143edab8 | ||
|
|
5d2b245f5a | ||
|
|
0cfa83259e | ||
|
|
3d9ec57523 |
108
.github/workflows/showcase-release.yml
vendored
108
.github/workflows/showcase-release.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
|||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: Build design system packages
|
- name: Build design system packages
|
||||||
run: npx turbo run build --filter='@dangerousthings/tokens' --filter='@dangerousthings/web'
|
run: npx turbo run build
|
||||||
|
|
||||||
- name: Build desktop showcase
|
- name: Build desktop showcase
|
||||||
working-directory: packages/showcase/desktop
|
working-directory: packages/showcase/desktop
|
||||||
@@ -60,6 +60,81 @@ jobs:
|
|||||||
packages/showcase/desktop/release/*.deb
|
packages/showcase/desktop/release/*.deb
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
|
|
||||||
|
# ─── Desktop Windows Build ──────────────────────────────────────
|
||||||
|
desktop-windows:
|
||||||
|
if: inputs.build_desktop
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: npm
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build design system packages
|
||||||
|
run: npx turbo run build
|
||||||
|
|
||||||
|
- name: Build desktop showcase
|
||||||
|
working-directory: packages/showcase/desktop
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Package desktop (Windows)
|
||||||
|
working-directory: packages/showcase/desktop
|
||||||
|
run: npx electron-builder --win
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Upload desktop artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: desktop-windows
|
||||||
|
path: |
|
||||||
|
packages/showcase/desktop/release/*.exe
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
# ─── Desktop macOS Build ────────────────────────────────────────
|
||||||
|
desktop-macos:
|
||||||
|
if: inputs.build_desktop
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: npm
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build design system packages
|
||||||
|
run: npx turbo run build
|
||||||
|
|
||||||
|
- name: Build desktop showcase
|
||||||
|
working-directory: packages/showcase/desktop
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Package desktop (macOS)
|
||||||
|
working-directory: packages/showcase/desktop
|
||||||
|
run: npx electron-builder --mac --x64 --arm64
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
# No CSC_LINK / CSC_KEY_PASSWORD = unsigned build
|
||||||
|
# Users will need to right-click > Open to bypass Gatekeeper
|
||||||
|
|
||||||
|
- name: Upload desktop artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: desktop-macos
|
||||||
|
path: |
|
||||||
|
packages/showcase/desktop/release/*.dmg
|
||||||
|
packages/showcase/desktop/release/*.zip
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
# ─── Mobile Android Build ──────────────────────────────────────
|
# ─── Mobile Android Build ──────────────────────────────────────
|
||||||
mobile-android:
|
mobile-android:
|
||||||
if: inputs.build_mobile
|
if: inputs.build_mobile
|
||||||
@@ -84,7 +159,7 @@ jobs:
|
|||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: Build design system packages
|
- name: Build design system packages
|
||||||
run: npx turbo run build --filter='@dangerousthings/tokens'
|
run: npx turbo run build
|
||||||
|
|
||||||
- name: Expo prebuild
|
- name: Expo prebuild
|
||||||
working-directory: packages/showcase/mobile
|
working-directory: packages/showcase/mobile
|
||||||
@@ -112,8 +187,8 @@ jobs:
|
|||||||
|
|
||||||
# ─── Create GitHub Release ─────────────────────────────────────
|
# ─── Create GitHub Release ─────────────────────────────────────
|
||||||
create-release:
|
create-release:
|
||||||
needs: [desktop-linux, mobile-android]
|
needs: [desktop-linux, desktop-windows, desktop-macos, mobile-android]
|
||||||
if: always() && (needs.desktop-linux.result == 'success' || needs.mobile-android.result == 'success')
|
if: always() && (needs.desktop-linux.result == 'success' || needs.desktop-windows.result == 'success' || needs.desktop-macos.result == 'success' || needs.mobile-android.result == 'success')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -131,7 +206,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mkdir -p final-artifacts
|
mkdir -p final-artifacts
|
||||||
|
|
||||||
# Desktop artifacts — replace 0.0.0 with release version
|
# Desktop Linux artifacts
|
||||||
if [ -d release-artifacts/desktop-linux ]; then
|
if [ -d release-artifacts/desktop-linux ]; then
|
||||||
for f in release-artifacts/desktop-linux/*; do
|
for f in release-artifacts/desktop-linux/*; do
|
||||||
filename=$(basename "$f")
|
filename=$(basename "$f")
|
||||||
@@ -140,7 +215,25 @@ jobs:
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Mobile APK — give it a descriptive name
|
# Desktop Windows artifacts
|
||||||
|
if [ -d release-artifacts/desktop-windows ]; then
|
||||||
|
for f in release-artifacts/desktop-windows/*; do
|
||||||
|
filename=$(basename "$f")
|
||||||
|
newname=$(echo "$filename" | sed "s/0\.0\.0/${RELEASE_VERSION}/g")
|
||||||
|
cp "$f" "final-artifacts/$newname"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Desktop macOS artifacts
|
||||||
|
if [ -d release-artifacts/desktop-macos ]; then
|
||||||
|
for f in release-artifacts/desktop-macos/*; do
|
||||||
|
filename=$(basename "$f")
|
||||||
|
newname=$(echo "$filename" | sed "s/0\.0\.0/${RELEASE_VERSION}/g")
|
||||||
|
cp "$f" "final-artifacts/$newname"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Mobile APK
|
||||||
if [ -f release-artifacts/mobile-android/app-release.apk ]; then
|
if [ -f release-artifacts/mobile-android/app-release.apk ]; then
|
||||||
cp release-artifacts/mobile-android/app-release.apk \
|
cp release-artifacts/mobile-android/app-release.apk \
|
||||||
"final-artifacts/dt-showcase-mobile-${RELEASE_VERSION}.apk"
|
"final-artifacts/dt-showcase-mobile-${RELEASE_VERSION}.apk"
|
||||||
@@ -160,6 +253,9 @@ jobs:
|
|||||||
### Desktop (Electron)
|
### Desktop (Electron)
|
||||||
- **AppImage** (Linux, portable) — download and `chmod +x` to run
|
- **AppImage** (Linux, portable) — download and `chmod +x` to run
|
||||||
- **deb** (Debian/Ubuntu) — install with `sudo dpkg -i <file>.deb`
|
- **deb** (Debian/Ubuntu) — install with `sudo dpkg -i <file>.deb`
|
||||||
|
- **exe** (Windows) — NSIS installer + portable exe
|
||||||
|
- **dmg** (macOS) — unsigned, right-click > Open to bypass Gatekeeper
|
||||||
|
- **zip** (macOS) — portable archive
|
||||||
|
|
||||||
### Mobile (Android)
|
### Mobile (Android)
|
||||||
- **APK** — sideload on any Android device (Settings > Install unknown apps)
|
- **APK** — sideload on any Android device (Settings > Install unknown apps)
|
||||||
|
|||||||
276
CLAUDE.md
276
CLAUDE.md
@@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
## Project Overview
|
## 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
|
- **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
|
- **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).
|
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`
|
- Build everything: `npm run build`
|
||||||
- Typecheck everything: `npm run typecheck`
|
- Typecheck everything: `npm run typecheck`
|
||||||
- Tokens build: `tsc` then `node dist/scripts/generate-css.js`
|
- 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`
|
- React Native build: `tsc`
|
||||||
- Versioning: Changesets (`npx changeset`, `npm run version-packages`, `npm run release`)
|
- 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
|
## 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`).
|
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`).
|
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)
|
### 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
|
- [ ] 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/`)
|
- [ ] 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
|
- [ ] 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
|
- [ ] 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`)
|
- [ ] TypeScript typechecks pass (`npm run typecheck`)
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
@@ -65,10 +70,10 @@ When adding or modifying a design token, component, or visual behavior, keep all
|
|||||||
|
|
||||||
### Adding a New Component
|
### Adding a New Component
|
||||||
|
|
||||||
1. Create the React Native component in `packages/react-native/src/components/DTFoo.tsx`.
|
1. If it needs new tokens, add them to all brand files and types first.
|
||||||
2. Re-export it from `packages/react-native/src/index.ts`.
|
2. If there is a web equivalent, add a CSS file in `packages/web/src/components/`.
|
||||||
3. If it needs new tokens, add them to all brand files and types first.
|
3. Create a React web component wrapper in `packages/react/src/components/DTFoo.tsx` and re-export from `packages/react/src/index.ts`.
|
||||||
4. If there is a web equivalent (CSS-only), add a CSS file in `packages/web/src/components/`.
|
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.
|
5. Create a changeset: `npx changeset` — select affected packages and describe the change.
|
||||||
|
|
||||||
### Adding a New Brand
|
### 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`).
|
- React Native components are prefixed `DT` (e.g., `DTButton`).
|
||||||
- Keep files focused — one component per file, one brand per file.
|
- 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
|
The DT Shopify Storefront (`dt-shopify-storefront`) is the genesis project for this design system.
|
||||||
storefront features are not yet represented in the design system and need to be baked in. This section
|
The storefront features documented below have been migrated into the design system. This section
|
||||||
documents everything that must be supported before the storefront can fully adopt design system components.
|
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.
|
| Feature | CSS (web) | React Component | Notes |
|
||||||
The design system's current `.card` component only uses `--color-primary` (always cyan). Cards must support
|
|---|---|---|---|
|
||||||
per-instance mode overrides.
|
| 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 |
|
| 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-success` | `--color-accent` | `#00FF00` green | Positive/success states |
|
||||||
| `mode-other` | `--color-other` | `#FF00FF` magenta | Bundles, alternative 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:**
|
| Aspect | Cyberpunk (`data-brand="dt"`) | Clean (`data-brand="classic"`) |
|
||||||
```css
|
|---|---|---|
|
||||||
.mode-normal {
|
| Clip-paths | Heavy bevels | `border-radius: 0.75rem` |
|
||||||
--mode: var(--mode-normal);
|
| Colors | Neon (#00FFFF, #FF00FF) | B&W or muted tones |
|
||||||
--accent-mode: var(--mode-emphasis);
|
| Hover effects | Pulse glow animations | None |
|
||||||
--selected: var(--mode-normal-selected);
|
| Typography | Tektur | system-ui sans-serif |
|
||||||
}
|
| Text shadows | `0 0 8px var(--mode)` | None |
|
||||||
.selected {
|
| Borders | Thick neon (5px top) | Subtle 1px |
|
||||||
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.
|
|
||||||
|
|
||||||
### Icon Set — Product Features & Filtering
|
### Icon Set — Product Features & Filtering
|
||||||
|
|
||||||
The storefront uses a specific set of icons from `react-icons` for product features and UI controls.
|
Canonical icon set for the DT design system, sourced from `react-icons`.
|
||||||
These should be documented as the canonical icon set for the DT design system.
|
|
||||||
|
|
||||||
**Product Feature Icons (react-icons/md — Material Design):**
|
**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 |
|
| Payment/NFC | `MdOutlineCreditCard` | react-icons/md |
|
||||||
| Smartphone Compat | `MdOutlinePhonelinkRing` | react-icons/md |
|
| Smartphone Compat | `MdOutlinePhonelinkRing` | react-icons/md |
|
||||||
| Access Control | `MdOutlineVpnKey` | 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 |
|
| NFC Sensing | `MdOutlineSensors` | react-icons/md |
|
||||||
| Fitness/Bio | `MdOutlineFitbit` | react-icons/md |
|
| Fitness/Bio | `MdOutlineFitbit` | react-icons/md |
|
||||||
| Vibration | `MdOutlineVibration` | 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.
|
**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.
|
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
|
## Known Issues
|
||||||
|
|
||||||
### Link hover glow ignores per-element mode colors (glows.css)
|
(none currently)
|
||||||
|
|
||||||
`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
|
|
||||||
|
|||||||
46
README.md
46
README.md
@@ -7,15 +7,17 @@ Shared design tokens, web CSS, and React Native components for the Dangerous Thi
|
|||||||
| Package | Description |
|
| Package | Description |
|
||||||
| -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
| -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||||
| [`@dangerousthings/tokens`](packages/tokens) | Canonical design tokens (colors, typography, shape) defined in TypeScript and exported as CSS custom properties |
|
| [`@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/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) |
|
| [`@dangerousthings/hex-background`](packages/hex-background) | 3D hexagon grid background using Three.js + React Three Fiber (web and React Native) |
|
||||||
|
|
||||||
## Brand Themes
|
## Brand Themes
|
||||||
|
|
||||||
Two brand themes ship out of the box:
|
Two brand themes ship out of the box:
|
||||||
|
|
||||||
- **DT** — neon cyberpunk aesthetic with beveled corners and glow effects (Tektur font)
|
- **DT** — neon cyberpunk aesthetic with beveled corners and baked in animations (Tektur font)
|
||||||
- **Classic** — dark navy palette with magenta accents and standard border radius
|
- **Classic** — dark navy palette with magenta accents and standard border radius
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
@@ -50,6 +52,25 @@ npm run build:react-native
|
|||||||
@import "@dangerousthings/web/components/bevels.css";
|
@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
|
### React Native
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
@@ -76,6 +97,17 @@ import { dt } from "@dangerousthings/tokens/brands/dt";
|
|||||||
console.log(dt.color.brand.primary); // "#00ffff"
|
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
|
## Development
|
||||||
|
|
||||||
### Scripts
|
### Scripts
|
||||||
@@ -85,7 +117,6 @@ console.log(dt.color.brand.primary); // "#00ffff"
|
|||||||
| `npm run build` | Build all packages (Turbo) |
|
| `npm run build` | Build all packages (Turbo) |
|
||||||
| `npm run clean` | Remove all `dist/` directories |
|
| `npm run clean` | Remove all `dist/` directories |
|
||||||
| `npm run typecheck` | Run TypeScript type checking |
|
| `npm run typecheck` | Run TypeScript type checking |
|
||||||
| `npm run lint` | Lint all packages |
|
|
||||||
|
|
||||||
### Release
|
### Release
|
||||||
|
|
||||||
@@ -97,6 +128,12 @@ npm run version-packages # bump versions from pending changesets
|
|||||||
npm run release # build + publish to npm
|
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
|
## 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
|
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
|
## Web CSS Modules
|
||||||
|
|
||||||
- `bevels.css` — angular clip-path bevels, card modes, selected states, progress bars, badge overlays, interactive bevel buttons
|
- `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
|
- `forms-dt.css` — text inputs, checkboxes, radio buttons, switches, menu items, filter headers
|
||||||
- `animations.css` — entrance animations, stagger containers, transition utilities
|
- `animations.css` — entrance animations, stagger containers, transition utilities
|
||||||
- `scrollbar.css` — thin neon scrollbar styling
|
- `scrollbar.css` — thin neon scrollbar styling
|
||||||
@@ -119,7 +155,9 @@ dt-design-system/
|
|||||||
├── packages/
|
├── packages/
|
||||||
│ ├── tokens/ # Design token definitions + CSS generation
|
│ ├── tokens/ # Design token definitions + CSS generation
|
||||||
│ ├── web/ # CSS themes and components
|
│ ├── web/ # CSS themes and components
|
||||||
|
│ ├── react/ # React web components
|
||||||
│ ├── react-native/ # React Native Paper components
|
│ ├── react-native/ # React Native Paper components
|
||||||
|
│ ├── tailwind-preset/ # Tailwind CSS v3 preset
|
||||||
│ ├── hex-background/ # 3D hexagon grid (Three.js)
|
│ ├── hex-background/ # 3D hexagon grid (Three.js)
|
||||||
│ └── showcase/ # Demo apps (desktop + mobile)
|
│ └── showcase/ # Demo apps (desktop + mobile)
|
||||||
├── turbo.json # Turbo pipeline config
|
├── turbo.json # Turbo pipeline config
|
||||||
|
|||||||
594
package-lock.json
generated
594
package-lock.json
generated
@@ -5489,6 +5489,128 @@
|
|||||||
"nanoid": "^3.3.11"
|
"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": {
|
"node_modules/@rolldown/pluginutils": {
|
||||||
"version": "1.0.0-beta.27",
|
"version": "1.0.0-beta.27",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
||||||
@@ -6090,7 +6212,6 @@
|
|||||||
"version": "19.2.14",
|
"version": "19.2.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
||||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
@@ -6179,7 +6300,6 @@
|
|||||||
"version": "0.5.24",
|
"version": "0.5.24",
|
||||||
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
|
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
|
||||||
"integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
|
"integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/yargs": {
|
"node_modules/@types/yargs": {
|
||||||
@@ -6636,85 +6756,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"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": {
|
"node_modules/are-we-there-yet": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz",
|
"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": ">=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": {
|
"node_modules/compressible": {
|
||||||
"version": "2.0.18",
|
"version": "2.0.18",
|
||||||
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||||
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
|
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/cosmiconfig": {
|
"node_modules/cosmiconfig": {
|
||||||
"version": "9.0.1",
|
"version": "9.0.1",
|
||||||
@@ -8457,35 +8482,6 @@
|
|||||||
"buffer": "^5.1.0"
|
"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": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
@@ -8567,7 +8563,6 @@
|
|||||||
"version": "3.2.3",
|
"version": "3.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/data-uri-to-buffer": {
|
"node_modules/data-uri-to-buffer": {
|
||||||
@@ -9148,61 +9143,6 @@
|
|||||||
"node": ">=14.0.0"
|
"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": {
|
"node_modules/electron-builder/node_modules/fs-extra": {
|
||||||
"version": "10.1.0",
|
"version": "10.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||||
@@ -10219,14 +10159,6 @@
|
|||||||
"node": ">= 0.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": {
|
"node_modules/fs-extra": {
|
||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
|
||||||
@@ -11159,14 +11091,6 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/isbinaryfile": {
|
||||||
"version": "5.0.7",
|
"version": "5.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz",
|
||||||
@@ -11605,56 +11529,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/leven": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
|
||||||
@@ -11982,38 +11856,6 @@
|
|||||||
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
|
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/lodash.startcase": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz",
|
||||||
@@ -12027,14 +11869,6 @@
|
|||||||
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==",
|
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/log-symbols": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
|
"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": "^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": {
|
"node_modules/progress": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
||||||
@@ -14224,6 +14050,21 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/read-binary-file-arch": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz",
|
||||||
@@ -14288,42 +14129,6 @@
|
|||||||
"node": ">= 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": {
|
"node_modules/readdirp": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||||
@@ -14725,8 +14530,7 @@
|
|||||||
"version": "0.27.0",
|
"version": "0.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||||
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.4",
|
"version": "7.7.4",
|
||||||
@@ -15403,7 +15207,6 @@
|
|||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz",
|
||||||
"integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==",
|
"integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">=17.0"
|
"react": ">=17.0"
|
||||||
@@ -15507,24 +15310,6 @@
|
|||||||
"streamx": "^2.15.0"
|
"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": {
|
"node_modules/tar/node_modules/minipass": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
|
||||||
@@ -15703,7 +15488,6 @@
|
|||||||
"version": "0.160.1",
|
"version": "0.160.1",
|
||||||
"resolved": "https://registry.npmjs.org/three/-/three-0.160.1.tgz",
|
"resolved": "https://registry.npmjs.org/three/-/three-0.160.1.tgz",
|
||||||
"integrity": "sha512-Bgl2wPJypDOZ1stAxwfWAcJ0WQf7QzlptsxkjYiURPz+n5k4RBDLsq+6f9Y75TYxn6aHLcWz+JNmwTOXWrQTBQ==",
|
"integrity": "sha512-Bgl2wPJypDOZ1stAxwfWAcJ0WQf7QzlptsxkjYiURPz+n5k4RBDLsq+6f9Y75TYxn6aHLcWz+JNmwTOXWrQTBQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/throat": {
|
"node_modules/throat": {
|
||||||
@@ -16654,45 +16438,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/zod": {
|
||||||
"version": "3.25.76",
|
"version": "3.25.76",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||||
@@ -16723,7 +16468,7 @@
|
|||||||
},
|
},
|
||||||
"packages/hex-background": {
|
"packages/hex-background": {
|
||||||
"name": "@dangerousthings/hex-background",
|
"name": "@dangerousthings/hex-background",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-three/fiber": "^8.18.0",
|
"@react-three/fiber": "^8.18.0",
|
||||||
@@ -16733,6 +16478,9 @@
|
|||||||
"three": "^0.160.0",
|
"three": "^0.160.0",
|
||||||
"typescript": "^5.7.0"
|
"typescript": "^5.7.0"
|
||||||
},
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@react-three/fiber": ">=8",
|
"@react-three/fiber": ">=8",
|
||||||
"react": ">=18",
|
"react": ">=18",
|
||||||
@@ -16860,22 +16608,6 @@
|
|||||||
"react": "^18.0.0"
|
"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": {
|
"packages/hex-background/node_modules/scheduler": {
|
||||||
"version": "0.21.0",
|
"version": "0.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz",
|
||||||
@@ -16888,7 +16620,7 @@
|
|||||||
},
|
},
|
||||||
"packages/react": {
|
"packages/react": {
|
||||||
"name": "@dangerousthings/react",
|
"name": "@dangerousthings/react",
|
||||||
"version": "0.1.0",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dangerousthings/tokens": "*",
|
"@dangerousthings/tokens": "*",
|
||||||
@@ -16899,6 +16631,9 @@
|
|||||||
"react-dom": "^18.0.0",
|
"react-dom": "^18.0.0",
|
||||||
"typescript": "^5.8.0"
|
"typescript": "^5.8.0"
|
||||||
},
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@dangerousthings/tokens": "*",
|
"@dangerousthings/tokens": "*",
|
||||||
"@dangerousthings/web": "*",
|
"@dangerousthings/web": "*",
|
||||||
@@ -16908,7 +16643,7 @@
|
|||||||
},
|
},
|
||||||
"packages/react-native": {
|
"packages/react-native": {
|
||||||
"name": "@dangerousthings/react-native",
|
"name": "@dangerousthings/react-native",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dangerousthings/tokens": "*"
|
"@dangerousthings/tokens": "*"
|
||||||
@@ -16921,6 +16656,9 @@
|
|||||||
"react-native-svg": "^15.15.3",
|
"react-native-svg": "^15.15.3",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
},
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">=18.0.0",
|
"react": ">=18.0.0",
|
||||||
"react-native": ">=0.72.0",
|
"react-native": ">=0.72.0",
|
||||||
@@ -16995,13 +16733,17 @@
|
|||||||
"packages/showcase/desktop": {
|
"packages/showcase/desktop": {
|
||||||
"name": "@dangerousthings/showcase-desktop",
|
"name": "@dangerousthings/showcase-desktop",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@dangerousthings/hex-background": "^0.2.0",
|
||||||
"@dangerousthings/react": "*",
|
"@dangerousthings/react": "*",
|
||||||
"@dangerousthings/tokens": "*",
|
"@dangerousthings/tokens": "*",
|
||||||
"@dangerousthings/web": "*",
|
"@dangerousthings/web": "*",
|
||||||
|
"@react-three/fiber": "^9.5.0",
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
"react-dom": "^18.0.0",
|
"react-dom": "^18.0.0",
|
||||||
"react-icons": "^5.6.0"
|
"react-icons": "^5.6.0",
|
||||||
|
"three": "^0.183.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dangerousthings/tailwind-preset": "*",
|
"@dangerousthings/tailwind-preset": "*",
|
||||||
@@ -17074,9 +16816,16 @@
|
|||||||
"loose-envify": "^1.1.0"
|
"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": {
|
"packages/showcase/mobile": {
|
||||||
"name": "@dangerousthings/showcase-mobile",
|
"name": "@dangerousthings/showcase-mobile",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dangerousthings/react-native": "*",
|
"@dangerousthings/react-native": "*",
|
||||||
"@dangerousthings/tokens": "*",
|
"@dangerousthings/tokens": "*",
|
||||||
@@ -17379,31 +17128,40 @@
|
|||||||
},
|
},
|
||||||
"packages/tailwind-preset": {
|
"packages/tailwind-preset": {
|
||||||
"name": "@dangerousthings/tailwind-preset",
|
"name": "@dangerousthings/tailwind-preset",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"tailwindcss": "^3.4.0",
|
"tailwindcss": "^3.4.0",
|
||||||
"typescript": "^5.8.0"
|
"typescript": "^5.8.0"
|
||||||
},
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"tailwindcss": "^3.4.0"
|
"tailwindcss": "^3.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/tokens": {
|
"packages/tokens": {
|
||||||
"name": "@dangerousthings/tokens",
|
"name": "@dangerousthings/tokens",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^25.3.3",
|
"@types/node": "^25.3.3",
|
||||||
"typescript": "^5.7.0"
|
"typescript": "^5.7.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@dangerousthings/web",
|
"name": "@dangerousthings/web",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dangerousthings/tokens": "*"
|
"@dangerousthings/tokens": "*"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ function Background() {
|
|||||||
|
|
||||||
## Monorepo
|
## 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
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
# @dangerousthings/react-native
|
# @dangerousthings/react-native
|
||||||
|
|
||||||
|
## 0.4.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Video letterboxing for DTMediaFrame, card badge styling with bevel-aware positioning, restored DTStaggerContainer.
|
||||||
|
|
||||||
|
## 0.4.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- Video letterboxing for DTMediaFrame, card badge styling with bevel-aware positioning, restored DTStaggerContainer in react-native, updated DTGallery/DTModal/DTMobileFilterOverlay components.
|
||||||
|
|
||||||
## 0.3.0
|
## 0.3.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@dangerousthings/react-native",
|
"name": "@dangerousthings/react-native",
|
||||||
"version": "0.3.0",
|
"version": "0.4.1",
|
||||||
"description": "React Native themed components for the Dangerous Things design system",
|
"description": "React Native themed components for the Dangerous Things design system",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
@@ -251,7 +251,6 @@ const styles = StyleSheet.create({
|
|||||||
title: {
|
title: {
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
letterSpacing: 0.5,
|
letterSpacing: 0.5,
|
||||||
textTransform: 'uppercase',
|
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
},
|
},
|
||||||
contentWrapper: {},
|
contentWrapper: {},
|
||||||
|
|||||||
@@ -243,6 +243,5 @@ const styles = StyleSheet.create({
|
|||||||
label: {
|
label: {
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
letterSpacing: 1,
|
letterSpacing: 1,
|
||||||
textTransform: 'uppercase',
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -49,6 +49,11 @@ interface DTCardProps {
|
|||||||
* @default 0
|
* @default 0
|
||||||
*/
|
*/
|
||||||
progress?: number;
|
progress?: number;
|
||||||
|
/**
|
||||||
|
* Which area expands when the card grows to fill space
|
||||||
|
* @default 'body'
|
||||||
|
*/
|
||||||
|
growArea?: 'body' | 'title';
|
||||||
/**
|
/**
|
||||||
* Additional styles for the card container
|
* Additional styles for the card container
|
||||||
*/
|
*/
|
||||||
@@ -81,6 +86,10 @@ interface DTCardProps {
|
|||||||
* Background color (defaults to theme background)
|
* Background color (defaults to theme background)
|
||||||
*/
|
*/
|
||||||
backgroundColor?: string;
|
backgroundColor?: string;
|
||||||
|
/**
|
||||||
|
* Element rendered at the right edge of the header
|
||||||
|
*/
|
||||||
|
headerRight?: ReactNode;
|
||||||
/**
|
/**
|
||||||
* Press handler
|
* Press handler
|
||||||
*/
|
*/
|
||||||
@@ -215,6 +224,7 @@ export function DTCard({
|
|||||||
title,
|
title,
|
||||||
showHeader,
|
showHeader,
|
||||||
progress = 0,
|
progress = 0,
|
||||||
|
growArea = 'body',
|
||||||
style,
|
style,
|
||||||
contentStyle,
|
contentStyle,
|
||||||
padding = 16,
|
padding = 16,
|
||||||
@@ -222,6 +232,7 @@ export function DTCard({
|
|||||||
bevelSize = 32,
|
bevelSize = 32,
|
||||||
bevelSizeSmall = 16,
|
bevelSizeSmall = 16,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
|
headerRight,
|
||||||
onPress,
|
onPress,
|
||||||
}: DTCardProps) {
|
}: DTCardProps) {
|
||||||
const theme = useDTTheme();
|
const theme = useDTTheme();
|
||||||
@@ -327,7 +338,7 @@ export function DTCard({
|
|||||||
)}
|
)}
|
||||||
<View style={[styles.innerContainer, useBevels && {paddingLeft: bevelSizeSmall - borderWidth}]}>
|
<View style={[styles.innerContainer, useBevels && {paddingLeft: bevelSizeSmall - borderWidth}]}>
|
||||||
{shouldShowHeader && (
|
{shouldShowHeader && (
|
||||||
<View style={[styles.header, {backgroundColor: accentColor}]}>
|
<View style={[styles.header, {backgroundColor: accentColor}, growArea === 'title' && {flex: 1}, !!(title && headerRight) && styles.headerRow]}>
|
||||||
{title && (
|
{title && (
|
||||||
<Text
|
<Text
|
||||||
variant="titleMedium"
|
variant="titleMedium"
|
||||||
@@ -335,9 +346,10 @@ export function DTCard({
|
|||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
{headerRight}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<View style={[styles.content, {padding}, contentStyle]}>{children}</View>
|
<View style={[styles.content, {padding}, growArea === 'body' && {flex: 1}, contentStyle]}>{children}</View>
|
||||||
</View>
|
</View>
|
||||||
{/* Frame overlay (above content) — beveled mode only.
|
{/* Frame overlay (above content) — beveled mode only.
|
||||||
Uses evenodd with 3 sub-paths: outer + inner + progressArea.
|
Uses evenodd with 3 sub-paths: outer + inner + progressArea.
|
||||||
@@ -400,12 +412,18 @@ const styles = StyleSheet.create({
|
|||||||
position: 'relative',
|
position: 'relative',
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
|
flex: 1,
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
paddingLeft: 8,
|
paddingLeft: 8,
|
||||||
paddingRight: 16,
|
paddingRight: 16,
|
||||||
paddingVertical: 12,
|
paddingVertical: 12,
|
||||||
},
|
},
|
||||||
|
headerRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
headerText: {
|
headerText: {
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
letterSpacing: 0.5,
|
letterSpacing: 0.5,
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ const styles = StyleSheet.create({
|
|||||||
headerText: {
|
headerText: {
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
letterSpacing: 0.5,
|
letterSpacing: 0.5,
|
||||||
textTransform: 'uppercase',
|
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|||||||
@@ -79,6 +79,11 @@ interface DTGalleryProps {
|
|||||||
* @default 3
|
* @default 3
|
||||||
*/
|
*/
|
||||||
borderWidth?: number;
|
borderWidth?: number;
|
||||||
|
/**
|
||||||
|
* Aspect ratio (width / height) for the display area
|
||||||
|
* @default 1
|
||||||
|
*/
|
||||||
|
aspectRatio?: number;
|
||||||
/**
|
/**
|
||||||
* Additional styles
|
* Additional styles
|
||||||
*/
|
*/
|
||||||
@@ -108,6 +113,7 @@ export function DTGallery({
|
|||||||
thumbnailSize = 64,
|
thumbnailSize = 64,
|
||||||
bevelSize = 24,
|
bevelSize = 24,
|
||||||
borderWidth = 3,
|
borderWidth = 3,
|
||||||
|
aspectRatio = 1,
|
||||||
style,
|
style,
|
||||||
}: DTGalleryProps) {
|
}: DTGalleryProps) {
|
||||||
const theme = useDTTheme();
|
const theme = useDTTheme();
|
||||||
@@ -276,6 +282,7 @@ export function DTGallery({
|
|||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
styles.mainImageContainer,
|
styles.mainImageContainer,
|
||||||
|
{ aspectRatio },
|
||||||
!useBevels && {
|
!useBevels && {
|
||||||
borderWidth,
|
borderWidth,
|
||||||
borderColor: accentColor,
|
borderColor: accentColor,
|
||||||
@@ -355,7 +362,6 @@ const styles = StyleSheet.create({
|
|||||||
gap: 12,
|
gap: 12,
|
||||||
},
|
},
|
||||||
mainImageContainer: {
|
mainImageContainer: {
|
||||||
aspectRatio: 1,
|
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
},
|
},
|
||||||
mainImageContent: {
|
mainImageContent: {
|
||||||
|
|||||||
@@ -341,7 +341,6 @@ const styles = StyleSheet.create({
|
|||||||
menuItemTitle: {
|
menuItemTitle: {
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
letterSpacing: 0.5,
|
letterSpacing: 0.5,
|
||||||
textTransform: 'uppercase',
|
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
},
|
},
|
||||||
chevron: {
|
chevron: {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
* - Content in beveled card container
|
* - Content in beveled card container
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {StyleSheet, ViewStyle, StyleProp, KeyboardAvoidingView, Platform} from 'react-native';
|
import {StyleSheet, ViewStyle, StyleProp, KeyboardAvoidingView, Platform, Pressable} from 'react-native';
|
||||||
import {Portal, Modal} from 'react-native-paper';
|
import {Portal, Modal, Text} from 'react-native-paper';
|
||||||
import {useDTTheme} from '../theme/DTThemeProvider';
|
import {useDTTheme} from '../theme/DTThemeProvider';
|
||||||
import {type DTVariant} from '../utils/variantColors';
|
import {type DTVariant} from '../utils/variantColors';
|
||||||
import {DTCard} from './DTCard';
|
import {DTCard} from './DTCard';
|
||||||
@@ -97,6 +97,17 @@ export function DTModal({
|
|||||||
mode={variant}
|
mode={variant}
|
||||||
title={title}
|
title={title}
|
||||||
showHeader={!!title}
|
showHeader={!!title}
|
||||||
|
headerRight={dismissable ? (
|
||||||
|
<Pressable
|
||||||
|
onPress={onDismiss}
|
||||||
|
hitSlop={8}
|
||||||
|
accessibilityLabel="Close"
|
||||||
|
accessibilityRole="button">
|
||||||
|
<Text style={{ color: theme.colors.onPrimary, fontSize: 18, lineHeight: 18 }}>
|
||||||
|
{'\u2715'}
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
|
) : undefined}
|
||||||
contentStyle={contentStyle}>
|
contentStyle={contentStyle}>
|
||||||
{children}
|
{children}
|
||||||
</DTCard>
|
</DTCard>
|
||||||
|
|||||||
@@ -1,5 +1,24 @@
|
|||||||
# @dangerousthings/react
|
# @dangerousthings/react
|
||||||
|
|
||||||
|
## 2.0.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Video letterboxing for DTMediaFrame, card badge styling with bevel-aware positioning, restored DTStaggerContainer.
|
||||||
|
- Updated dependencies
|
||||||
|
- @dangerousthings/web@0.4.1
|
||||||
|
|
||||||
|
## 2.0.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- Video letterboxing for DTMediaFrame, card badge styling with bevel-aware positioning, restored DTStaggerContainer in react-native, updated DTGallery/DTModal/DTMobileFilterOverlay components.
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies
|
||||||
|
- @dangerousthings/web@0.4.0
|
||||||
|
|
||||||
## 1.0.0
|
## 1.0.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ variantToClassName("warning"); // token-level variant mapping
|
|||||||
|
|
||||||
## Monorepo
|
## 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
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@dangerousthings/react",
|
"name": "@dangerousthings/react",
|
||||||
"version": "1.0.0",
|
"version": "2.0.1",
|
||||||
"description": "React web components for the Dangerous Things design system",
|
"description": "React web components for the Dangerous Things design system",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
*
|
*
|
||||||
* The progress bar is a structural element on the left edge (0 to bevel-sm).
|
* The progress bar is a structural element on the left edge (0 to bevel-sm).
|
||||||
* It is always present. At 0 progress it shows surface color (empty bar).
|
* It is always present. At 0 progress it shows surface color (empty bar).
|
||||||
* As progress increases, accent color fills from the bottom up.
|
* As progress increases, accent color fills from top to bottom.
|
||||||
|
* When progress is omitted, the bar stays empty (full card, no fill).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ReactNode, CSSProperties } from 'react';
|
import type { ReactNode, CSSProperties } from 'react';
|
||||||
@@ -22,10 +23,18 @@ interface DTCardProps {
|
|||||||
title?: string;
|
title?: string;
|
||||||
/** Whether to show the accent header bar @default true when title is provided */
|
/** Whether to show the accent header bar @default true when title is provided */
|
||||||
showHeader?: boolean;
|
showHeader?: boolean;
|
||||||
/** Progress value (0–100) for left-edge vertical progress bar @default 0 */
|
/** Dismiss callback — renders a close button in the header bar */
|
||||||
|
onDismiss?: () => void;
|
||||||
|
/** Progress value (0–100) for left-edge vertical progress bar. Omit for empty bar. */
|
||||||
progress?: number;
|
progress?: number;
|
||||||
|
/** Grow to fill available space in flex/grid containers @default false */
|
||||||
|
grow?: boolean;
|
||||||
|
/** Which area expands when grow is true @default 'body' */
|
||||||
|
growArea?: 'body' | 'title';
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
|
/** Badge content rendered at bottom-right, clipped by card bevel */
|
||||||
|
badge?: ReactNode;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,12 +43,16 @@ export function DTCard({
|
|||||||
variant = 'normal',
|
variant = 'normal',
|
||||||
title,
|
title,
|
||||||
showHeader,
|
showHeader,
|
||||||
progress = 0,
|
onDismiss,
|
||||||
|
progress,
|
||||||
|
grow = false,
|
||||||
|
growArea = 'body',
|
||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
onClick,
|
onClick,
|
||||||
|
badge,
|
||||||
}: DTCardProps) {
|
}: DTCardProps) {
|
||||||
const shouldShowHeader = showHeader ?? !!title;
|
const shouldShowHeader = showHeader ?? !!(title || onDismiss);
|
||||||
const Tag = onClick ? 'button' : 'div';
|
const Tag = onClick ? 'button' : 'div';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -47,20 +60,40 @@ export function DTCard({
|
|||||||
className={cx(
|
className={cx(
|
||||||
'card',
|
'card',
|
||||||
getVariantClass(variant),
|
getVariantClass(variant),
|
||||||
|
grow && 'dt-card-grow',
|
||||||
|
grow && growArea === 'title' && 'dt-card-grow-title',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
...style,
|
...style,
|
||||||
'--dt-card-progress': progress,
|
...(progress != null && { '--dt-card-progress': progress }),
|
||||||
} as CSSProperties}
|
} as CSSProperties}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
type={onClick ? 'button' : undefined}>
|
type={onClick ? 'button' : undefined}>
|
||||||
{shouldShowHeader && (
|
{shouldShowHeader && (
|
||||||
<div className="card-title">
|
<div className="card-title" style={onDismiss ? { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } : undefined}>
|
||||||
{title}
|
<span>{title}</span>
|
||||||
|
{onDismiss && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => { e.stopPropagation(); onDismiss(); }}
|
||||||
|
aria-label="Close"
|
||||||
|
style={{
|
||||||
|
background: 'none',
|
||||||
|
border: 'none',
|
||||||
|
color: 'inherit',
|
||||||
|
cursor: 'pointer',
|
||||||
|
padding: 0,
|
||||||
|
fontSize: '1.25rem',
|
||||||
|
lineHeight: 1,
|
||||||
|
}}>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{children}
|
{children}
|
||||||
|
{badge && <div className="dt-card-badge">{badge}</div>}
|
||||||
</Tag>
|
</Tag>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,7 +160,6 @@ export function DTDrawer({
|
|||||||
fontWeight: 900,
|
fontWeight: 900,
|
||||||
fontSize: '2em',
|
fontSize: '2em',
|
||||||
letterSpacing: '0.05em',
|
letterSpacing: '0.05em',
|
||||||
textTransform: 'uppercase',
|
|
||||||
borderBottom: '1px solid var(--color-bg)',
|
borderBottom: '1px solid var(--color-bg)',
|
||||||
}}>
|
}}>
|
||||||
<span>{heading}</span>
|
<span>{heading}</span>
|
||||||
|
|||||||
@@ -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,
|
* Matches dt-shopify-storefront UseCaseLegend: header bar with hover details,
|
||||||
* .dt-feature-legend-grid, .dt-feature-legend-item, .dt-feature-supported, etc.
|
* 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 type { DTVariant } from '@dangerousthings/tokens';
|
||||||
import { cx } from '../utils/cx';
|
import { cx } from '../utils/cx';
|
||||||
import { getVariantClass, featureStateToVariant } from '../utils/variantClasses';
|
import { getVariantClass } from '../utils/variantClasses';
|
||||||
|
|
||||||
export interface DTFeatureItem {
|
export interface DTFeatureItem {
|
||||||
key: string;
|
key: string;
|
||||||
name: string;
|
name: string;
|
||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
state: 'supported' | 'disabled' | 'unsupported';
|
state: 'supported' | 'disabled' | 'unsupported';
|
||||||
|
/** Optional detail text shown in the header on hover */
|
||||||
|
detail?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stateClassMap: Record<DTFeatureItem['state'], string> = {
|
const stateClassMap: Record<DTFeatureItem['state'], string> = {
|
||||||
@@ -32,6 +37,8 @@ interface DTFeatureLegendProps {
|
|||||||
columns?: number;
|
columns?: number;
|
||||||
/** Icon size in px @default 42 */
|
/** Icon size in px @default 42 */
|
||||||
iconSize?: number;
|
iconSize?: number;
|
||||||
|
/** Show rotated labels initially @default true */
|
||||||
|
showLabels?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
}
|
}
|
||||||
@@ -42,35 +49,93 @@ export function DTFeatureLegend({
|
|||||||
variant = 'normal',
|
variant = 'normal',
|
||||||
columns = 5,
|
columns = 5,
|
||||||
iconSize = 42,
|
iconSize = 42,
|
||||||
|
showLabels: initialShowLabels = true,
|
||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
}: DTFeatureLegendProps) {
|
}: DTFeatureLegendProps) {
|
||||||
// Suppress unused import warning — featureStateToVariant is available for consumers
|
const [showLabels, setShowLabels] = useState(initialShowLabels);
|
||||||
void featureStateToVariant;
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx('dt-feature-legend', getVariantClass(variant), className)}
|
className={cx('dt-feature-legend', getVariantClass(variant), className)}
|
||||||
style={style}>
|
style={style}>
|
||||||
|
|
||||||
|
{/* Header bar — title + hovered feature detail + ? toggle */}
|
||||||
{title && (
|
{title && (
|
||||||
<div className="dt-feature-legend-header">{title}</div>
|
<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>
|
||||||
|
<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
|
<div
|
||||||
className="dt-feature-legend-grid"
|
className={cx(
|
||||||
|
'dt-feature-legend-grid',
|
||||||
|
showLabels && 'with-labels',
|
||||||
|
showLabels && hasBottomRow && 'with-labels-bottom',
|
||||||
|
)}
|
||||||
style={{ '--dt-feature-columns': columns } as CSSProperties}>
|
style={{ '--dt-feature-columns': columns } as CSSProperties}>
|
||||||
{features.map(feature => (
|
{features.map((feature, index) => {
|
||||||
|
const row = Math.floor(index / columns);
|
||||||
|
const labelPosition = rowCount >= 2 ? (row === 0 ? 'above' : 'below') : 'above';
|
||||||
|
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
key={feature.key}
|
key={feature.key}
|
||||||
className={cx('dt-feature-legend-item', stateClassMap[feature.state])}
|
className={cx('dt-feature-legend-item', stateClassMap[feature.state])}
|
||||||
style={{ width: `${100 / columns}%` }}>
|
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
|
<div
|
||||||
className="dt-feature-legend-icon"
|
className="dt-feature-legend-icon"
|
||||||
style={{ width: `${iconSize}px`, height: `${iconSize}px` }}>
|
style={{ width: `${iconSize}px`, height: `${iconSize}px` }}>
|
||||||
{feature.icon}
|
{feature.icon}
|
||||||
</div>
|
</div>
|
||||||
<div className="dt-feature-legend-label">{feature.name}</div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,56 +1,168 @@
|
|||||||
/**
|
/**
|
||||||
* DTGallery — Image gallery with beveled media frame and thumbnails.
|
* DTGallery — Media gallery with beveled frame and thumbnails.
|
||||||
|
* Supports images, 3D models (via <model-viewer>), videos, and external videos.
|
||||||
*
|
*
|
||||||
* CSS reference: bevels.css .dt-bevel-media
|
* CSS reference: bevels.css .dt-bevel-media
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, type CSSProperties } from 'react';
|
import { useState, useEffect, type CSSProperties } from 'react';
|
||||||
import type { DTVariant } from '@dangerousthings/tokens';
|
import type { DTVariant } from '@dangerousthings/tokens';
|
||||||
import { cx } from '../utils/cx';
|
import { cx } from '../utils/cx';
|
||||||
import { getVariantClass } from '../utils/variantClasses';
|
import { getVariantClass } from '../utils/variantClasses';
|
||||||
|
|
||||||
export interface DTGalleryItem {
|
/* ── TypeScript declaration for <model-viewer> web component ── */
|
||||||
key: string;
|
declare global {
|
||||||
src: string;
|
namespace JSX {
|
||||||
|
interface IntrinsicElements {
|
||||||
|
'model-viewer': React.DetailedHTMLProps<
|
||||||
|
React.HTMLAttributes<HTMLElement>,
|
||||||
|
HTMLElement
|
||||||
|
> & {
|
||||||
|
src?: string;
|
||||||
alt?: string;
|
alt?: string;
|
||||||
thumbnail?: string;
|
poster?: string;
|
||||||
|
'camera-controls'?: boolean | string;
|
||||||
|
'auto-rotate'?: boolean | string;
|
||||||
|
'interaction-prompt'?: string;
|
||||||
|
ar?: boolean | string;
|
||||||
|
loading?: 'auto' | 'lazy' | 'eager';
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Gallery item types ── */
|
||||||
|
|
||||||
|
export type DTGalleryItem =
|
||||||
|
| { type?: 'image'; key: string; src: string; alt?: string; thumbnail?: string }
|
||||||
|
| { type: 'model3d'; key: string; src: string; alt?: string; thumbnail?: string; poster?: string }
|
||||||
|
| { type: 'video'; key: string; src: string; alt?: string; thumbnail?: string; poster?: string }
|
||||||
|
| { type: 'external-video'; key: string; src: string; alt?: string; thumbnail?: string };
|
||||||
|
|
||||||
interface DTGalleryProps {
|
interface DTGalleryProps {
|
||||||
items: DTGalleryItem[];
|
items: DTGalleryItem[];
|
||||||
/** Color variant @default 'normal' */
|
/** Color variant @default 'normal' */
|
||||||
variant?: DTVariant;
|
variant?: DTVariant;
|
||||||
|
/** Aspect ratio (width / height) for the display area @default 1 */
|
||||||
|
aspectRatio?: number;
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Model-viewer script loader ── */
|
||||||
|
|
||||||
|
const MODEL_VIEWER_SRC =
|
||||||
|
'https://unpkg.com/@google/model-viewer@v1.12.1/dist/model-viewer.min.js';
|
||||||
|
|
||||||
|
let modelViewerLoaded = false;
|
||||||
|
|
||||||
|
function useModelViewerScript(needed: boolean) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (!needed || modelViewerLoaded || typeof document === 'undefined') return;
|
||||||
|
if (customElements.get('model-viewer')) {
|
||||||
|
modelViewerLoaded = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.type = 'module';
|
||||||
|
script.src = MODEL_VIEWER_SRC;
|
||||||
|
document.head.appendChild(script);
|
||||||
|
modelViewerLoaded = true;
|
||||||
|
}, [needed]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Shared styles ── */
|
||||||
|
|
||||||
|
const fillStyle: CSSProperties = {
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
display: 'block',
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ── Component ── */
|
||||||
|
|
||||||
export function DTGallery({
|
export function DTGallery({
|
||||||
items,
|
items,
|
||||||
variant = 'normal',
|
variant = 'normal',
|
||||||
|
aspectRatio = 1,
|
||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
}: DTGalleryProps) {
|
}: DTGalleryProps) {
|
||||||
const [activeIndex, setActiveIndex] = useState(0);
|
const [activeIndex, setActiveIndex] = useState(0);
|
||||||
|
|
||||||
|
const hasModel = items.some((i) => i.type === 'model3d');
|
||||||
|
useModelViewerScript(hasModel);
|
||||||
|
|
||||||
if (items.length === 0) return null;
|
if (items.length === 0) return null;
|
||||||
|
|
||||||
const current = items[activeIndex];
|
const current = items[activeIndex];
|
||||||
|
const currentType = current.type || 'image';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx(getVariantClass(variant), className)} style={style}>
|
<div className={cx(getVariantClass(variant), className)} style={style}>
|
||||||
{/* Main image */}
|
{/* Main display */}
|
||||||
<div className="dt-bevel-media" style={{ overflow: 'hidden' }}>
|
<div
|
||||||
|
className="dt-bevel-media"
|
||||||
|
style={{ overflow: 'hidden', aspectRatio, position: 'relative' }}
|
||||||
|
>
|
||||||
|
{currentType === 'image' && (
|
||||||
<img
|
<img
|
||||||
src={current.src}
|
src={current.src}
|
||||||
alt={current.alt || ''}
|
alt={current.alt || ''}
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
...fillStyle,
|
||||||
height: 'auto',
|
objectFit: 'contain',
|
||||||
display: 'block',
|
|
||||||
transition: 'opacity 300ms ease-in-out',
|
transition: 'opacity 300ms ease-in-out',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentType === 'model3d' && (
|
||||||
|
<model-viewer
|
||||||
|
src={current.src}
|
||||||
|
alt={current.alt || ''}
|
||||||
|
poster={'poster' in current ? current.poster : undefined}
|
||||||
|
camera-controls=""
|
||||||
|
auto-rotate=""
|
||||||
|
interaction-prompt="auto"
|
||||||
|
loading="lazy"
|
||||||
|
style={{
|
||||||
|
...fillStyle,
|
||||||
|
backgroundColor: 'var(--color-bg, #000)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentType === 'video' && (
|
||||||
|
<video
|
||||||
|
src={current.src}
|
||||||
|
poster={'poster' in current ? current.poster : undefined}
|
||||||
|
controls
|
||||||
|
playsInline
|
||||||
|
preload="metadata"
|
||||||
|
style={{
|
||||||
|
...fillStyle,
|
||||||
|
objectFit: 'contain',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentType === 'external-video' && (
|
||||||
|
<iframe
|
||||||
|
src={current.src}
|
||||||
|
title={current.alt || 'Video'}
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||||
|
allowFullScreen
|
||||||
|
style={{
|
||||||
|
...fillStyle,
|
||||||
|
border: 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Thumbnails */}
|
{/* Thumbnails */}
|
||||||
{items.length > 1 && (
|
{items.length > 1 && (
|
||||||
<div
|
<div
|
||||||
@@ -59,8 +171,14 @@ export function DTGallery({
|
|||||||
gap: '8px',
|
gap: '8px',
|
||||||
marginTop: '8px',
|
marginTop: '8px',
|
||||||
overflowX: 'auto',
|
overflowX: 'auto',
|
||||||
}}>
|
}}
|
||||||
{items.map((item, i) => (
|
>
|
||||||
|
{items.map((item, i) => {
|
||||||
|
const itemType = item.type || 'image';
|
||||||
|
const thumbSrc = item.thumbnail || ('poster' in item ? item.poster : undefined) || item.src;
|
||||||
|
const isMedia = itemType === 'video' || itemType === 'external-video';
|
||||||
|
|
||||||
|
return (
|
||||||
<button
|
<button
|
||||||
key={item.key}
|
key={item.key}
|
||||||
onClick={() => setActiveIndex(i)}
|
onClick={() => setActiveIndex(i)}
|
||||||
@@ -70,20 +188,42 @@ export function DTGallery({
|
|||||||
width: '64px',
|
width: '64px',
|
||||||
height: '64px',
|
height: '64px',
|
||||||
padding: 0,
|
padding: 0,
|
||||||
border: i === activeIndex
|
border:
|
||||||
|
i === activeIndex
|
||||||
? '2px solid var(--dt-card-color, var(--color-primary))'
|
? '2px solid var(--dt-card-color, var(--color-primary))'
|
||||||
: '2px solid transparent',
|
: '2px solid transparent',
|
||||||
background: 'none',
|
background: 'none',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}>
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src={item.thumbnail || item.src}
|
src={thumbSrc}
|
||||||
alt={item.alt || ''}
|
alt={item.alt || ''}
|
||||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||||
/>
|
/>
|
||||||
|
{/* Play icon overlay for video thumbnails */}
|
||||||
|
{isMedia && (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="white"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
margin: 'auto',
|
||||||
|
width: '24px',
|
||||||
|
height: '24px',
|
||||||
|
filter: 'drop-shadow(0 1px 2px rgba(0,0,0,0.6))',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<path d="M8 5v14l11-7z" />
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,25 +2,43 @@
|
|||||||
* DTMediaFrame — Diagonal beveled frame (top-left + bottom-right).
|
* DTMediaFrame — Diagonal beveled frame (top-left + bottom-right).
|
||||||
*
|
*
|
||||||
* CSS reference: bevels.css .dt-bevel-media
|
* CSS reference: bevels.css .dt-bevel-media
|
||||||
|
*
|
||||||
|
* The outer div provides the colored bevel border via clip-path.
|
||||||
|
* A ::before pseudo-element fills the inner surface.
|
||||||
|
* Direct img/video children are auto-clipped to match.
|
||||||
|
* A ::after pseudo-element shows "MISSING MEDIA" when empty.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ReactNode, CSSProperties } from 'react';
|
import type { ReactNode, CSSProperties } from 'react';
|
||||||
|
import type { DTVariant } from '@dangerousthings/tokens';
|
||||||
import { cx } from '../utils/cx';
|
import { cx } from '../utils/cx';
|
||||||
|
import { getVariantClass } from '../utils/variantClasses';
|
||||||
|
|
||||||
interface DTMediaFrameProps {
|
interface DTMediaFrameProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
/** Color variant for the bevel border @default 'normal' */
|
||||||
|
variant?: DTVariant;
|
||||||
|
/** Custom placeholder shown when children is nullish (overrides CSS ::after) */
|
||||||
|
placeholder?: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DTMediaFrame({
|
export function DTMediaFrame({
|
||||||
children,
|
children,
|
||||||
|
variant = 'normal',
|
||||||
|
placeholder,
|
||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
}: DTMediaFrameProps) {
|
}: DTMediaFrameProps) {
|
||||||
|
const isEmpty = children == null || children === false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx('dt-bevel-media', className)} style={style}>
|
<div
|
||||||
{children}
|
className={cx('dt-bevel-media', getVariantClass(variant), className)}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
{isEmpty && placeholder ? placeholder : children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* .dt-filter-overlay-content
|
* .dt-filter-overlay-content
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useCallback, type ReactNode, type CSSProperties } from 'react';
|
import { useEffect, useCallback, useRef, type ReactNode, type CSSProperties } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import type { DTVariant } from '@dangerousthings/tokens';
|
import type { DTVariant } from '@dangerousthings/tokens';
|
||||||
import { cx } from '../utils/cx';
|
import { cx } from '../utils/cx';
|
||||||
@@ -38,6 +38,9 @@ export function DTMobileFilterOverlay({
|
|||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
}: DTMobileFilterOverlayProps) {
|
}: DTMobileFilterOverlayProps) {
|
||||||
|
const panelRef = useRef<HTMLDivElement>(null);
|
||||||
|
const triggerRef = useRef<HTMLElement | null>(null);
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
if (e.key === 'Escape') onDismiss();
|
if (e.key === 'Escape') onDismiss();
|
||||||
@@ -45,6 +48,7 @@ export function DTMobileFilterOverlay({
|
|||||||
[onDismiss],
|
[onDismiss],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Escape key
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
document.addEventListener('keydown', handleKeyDown);
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
@@ -52,12 +56,57 @@ export function DTMobileFilterOverlay({
|
|||||||
}
|
}
|
||||||
}, [visible, handleKeyDown]);
|
}, [visible, handleKeyDown]);
|
||||||
|
|
||||||
|
// Body scroll lock
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
return () => { document.body.style.overflow = ''; };
|
||||||
|
}
|
||||||
|
}, [visible]);
|
||||||
|
|
||||||
|
// Focus trap
|
||||||
|
useEffect(() => {
|
||||||
|
if (!visible) return;
|
||||||
|
triggerRef.current = document.activeElement as HTMLElement;
|
||||||
|
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
const first = panelRef.current?.querySelector<HTMLElement>(
|
||||||
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
|
||||||
|
);
|
||||||
|
first?.focus();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
const handleTab = (e: KeyboardEvent) => {
|
||||||
|
if (e.key !== 'Tab' || !panelRef.current) return;
|
||||||
|
const focusable = panelRef.current.querySelectorAll<HTMLElement>(
|
||||||
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
|
||||||
|
);
|
||||||
|
if (focusable.length === 0) return;
|
||||||
|
const first = focusable[0];
|
||||||
|
const last = focusable[focusable.length - 1];
|
||||||
|
if (e.shiftKey && document.activeElement === first) {
|
||||||
|
e.preventDefault();
|
||||||
|
last.focus();
|
||||||
|
} else if (!e.shiftKey && document.activeElement === last) {
|
||||||
|
e.preventDefault();
|
||||||
|
first.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('keydown', handleTab);
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
document.removeEventListener('keydown', handleTab);
|
||||||
|
triggerRef.current?.focus();
|
||||||
|
};
|
||||||
|
}, [visible]);
|
||||||
|
|
||||||
if (!visible) return null;
|
if (!visible) return null;
|
||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<div className={cx('dt-filter-overlay', getVariantClass(variant), className)} style={style}>
|
<div className={cx('dt-filter-overlay', getVariantClass(variant), className)} style={style}>
|
||||||
<div className="dt-filter-overlay-backdrop" onClick={onDismiss} />
|
<div className="dt-filter-overlay-backdrop" onClick={onDismiss} />
|
||||||
<div className="dt-filter-overlay-content">
|
<div ref={panelRef} className="dt-filter-overlay-content">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -67,7 +116,7 @@ export function DTMobileFilterOverlay({
|
|||||||
padding: '16px',
|
padding: '16px',
|
||||||
borderBottom: '1px solid rgba(var(--color-primary-rgb), 0.2)',
|
borderBottom: '1px solid rgba(var(--color-primary-rgb), 0.2)',
|
||||||
}}>
|
}}>
|
||||||
<span style={{ fontWeight: 700, fontSize: '1.125rem', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
|
<span style={{ fontWeight: 700, fontSize: '1.125rem', letterSpacing: '0.05em' }}>
|
||||||
{heading}
|
{heading}
|
||||||
{activeFilterCount !== undefined && activeFilterCount > 0 && (
|
{activeFilterCount !== undefined && activeFilterCount > 0 && (
|
||||||
<span
|
<span
|
||||||
@@ -88,7 +137,6 @@ export function DTMobileFilterOverlay({
|
|||||||
color: 'var(--color-primary)',
|
color: 'var(--color-primary)',
|
||||||
fontSize: '0.875rem',
|
fontSize: '0.875rem',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
textTransform: 'uppercase',
|
|
||||||
}}>
|
}}>
|
||||||
Clear All
|
Clear All
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -82,13 +82,35 @@ export function DTModal({
|
|||||||
position: 'relative',
|
position: 'relative',
|
||||||
maxWidth: '90vw',
|
maxWidth: '90vw',
|
||||||
maxHeight: '85vh',
|
maxHeight: '85vh',
|
||||||
overflow: 'auto',
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
...style,
|
...style,
|
||||||
}}
|
}}
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true">
|
aria-modal="true">
|
||||||
{title && <div className="card-title">{title}</div>}
|
{title && (
|
||||||
<div style={{ padding: 'var(--space-6, 24px)' }}>{children}</div>
|
<div className="card-title" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexShrink: 0 }}>
|
||||||
|
<span>{title}</span>
|
||||||
|
{dismissable && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onDismiss}
|
||||||
|
aria-label="Close"
|
||||||
|
style={{
|
||||||
|
background: 'none',
|
||||||
|
border: 'none',
|
||||||
|
color: 'inherit',
|
||||||
|
cursor: 'pointer',
|
||||||
|
padding: 0,
|
||||||
|
fontSize: '1.25rem',
|
||||||
|
lineHeight: 1,
|
||||||
|
}}>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div style={{ padding: 'var(--space-6, 24px)', overflow: 'auto', flex: '1 1 auto' }}>{children}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
document.body,
|
document.body,
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* DTRadioGroup — Hexagonal radio buttons.
|
* 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';
|
import { useId, type CSSProperties } from 'react';
|
||||||
@@ -39,20 +42,27 @@ export function DTRadioGroup({
|
|||||||
className={cx('dt-radio-group', className)}
|
className={cx('dt-radio-group', className)}
|
||||||
style={{ display: 'flex', flexDirection: 'column', gap: '4px', ...style }}
|
style={{ display: 'flex', flexDirection: 'column', gap: '4px', ...style }}
|
||||||
role="radiogroup">
|
role="radiogroup">
|
||||||
{options.map(option => (
|
{options.map(option => {
|
||||||
|
const checked = value === option.value;
|
||||||
|
return (
|
||||||
<label
|
<label
|
||||||
key={option.value}
|
key={option.value}
|
||||||
className={cx('dt-radio-option', value === option.value && 'selected')}>
|
className={cx('dt-radio-option', checked && 'selected')}>
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name={groupName}
|
name={groupName}
|
||||||
value={option.value}
|
value={option.value}
|
||||||
checked={value === option.value}
|
checked={checked}
|
||||||
onChange={() => onChange(option.value)}
|
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>
|
<span>{option.label}</span>
|
||||||
</label>
|
</label>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,22 @@
|
|||||||
* DTStaggerContainer — Staggered scale-in animation for children.
|
* DTStaggerContainer — Staggered scale-in animation for children.
|
||||||
*
|
*
|
||||||
* CSS reference: animations.css .dt-stagger-container
|
* CSS reference: animations.css .dt-stagger-container
|
||||||
* CSS handles all stagger timing via nth-child — no JS animation needed.
|
* Each child is wrapped in a div that carries the animationDelay.
|
||||||
|
* Wrapper divs inherit display:contents would break animation, so
|
||||||
|
* the CSS rule targets .dt-stagger-container > * directly.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ReactNode, CSSProperties } from 'react';
|
import { Children, type ReactNode, type CSSProperties } from 'react';
|
||||||
import { cx } from '../utils/cx';
|
import { cx } from '../utils/cx';
|
||||||
|
|
||||||
interface DTStaggerContainerProps {
|
interface DTStaggerContainerProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
/** Duration per child animation @default '0.33s' */
|
/** Duration per child animation @default '0.33s' */
|
||||||
duration?: string;
|
duration?: string;
|
||||||
/** Delay between each child @default '75ms' */
|
/** Delay between each child in ms @default 150 */
|
||||||
interval?: string;
|
interval?: number;
|
||||||
|
/** Base delay before the first child animates in ms @default 0 */
|
||||||
|
delay?: number;
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
}
|
}
|
||||||
@@ -21,19 +25,23 @@ interface DTStaggerContainerProps {
|
|||||||
export function DTStaggerContainer({
|
export function DTStaggerContainer({
|
||||||
children,
|
children,
|
||||||
duration,
|
duration,
|
||||||
interval,
|
interval = 150,
|
||||||
|
delay = 0,
|
||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
}: DTStaggerContainerProps) {
|
}: DTStaggerContainerProps) {
|
||||||
const cssVars: Record<string, string> = {};
|
const cssVars: Record<string, string> = {};
|
||||||
if (duration) cssVars['--dt-stagger-duration'] = duration;
|
if (duration) cssVars['--dt-stagger-duration'] = duration;
|
||||||
if (interval) cssVars['--dt-stagger-interval'] = interval;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx('dt-stagger-container', className)}
|
className={cx('dt-stagger-container', className)}
|
||||||
style={{ ...cssVars, ...style } as CSSProperties}>
|
style={{ ...cssVars, ...style } as CSSProperties}>
|
||||||
{children}
|
{Children.map(children, (child, i) => (
|
||||||
|
<div style={{ animationDelay: `${delay + i * interval}ms` }}>
|
||||||
|
{child}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ export function DTTextInput({
|
|||||||
marginBottom: '4px',
|
marginBottom: '4px',
|
||||||
fontSize: '0.875rem',
|
fontSize: '0.875rem',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
textTransform: 'uppercase',
|
|
||||||
letterSpacing: '0.05em',
|
letterSpacing: '0.05em',
|
||||||
}}>
|
}}>
|
||||||
{label}
|
{label}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
/**
|
/**
|
||||||
* DTWebThemeProvider
|
* DTWebThemeProvider
|
||||||
*
|
*
|
||||||
* Sets data-brand and data-theme attributes on a wrapper div.
|
* Sets data-brand and data-theme attributes on a wrapper div (default)
|
||||||
|
* or on document.documentElement (documentLevel mode).
|
||||||
* Provides React context for child components to read the active brand/theme.
|
* Provides React context for child components to read the active brand/theme.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createContext, type ReactNode } from 'react';
|
import { createContext, useEffect, type ReactNode } from 'react';
|
||||||
import type { ThemeBrand, ThemeMode } from '@dangerousthings/tokens';
|
import type { ThemeBrand, ThemeMode } from '@dangerousthings/tokens';
|
||||||
import { cx } from '../utils/cx';
|
import { cx } from '../utils/cx';
|
||||||
|
|
||||||
@@ -21,7 +22,9 @@ interface DTWebThemeProviderProps {
|
|||||||
brand?: ThemeBrand;
|
brand?: ThemeBrand;
|
||||||
/** Theme mode @default 'dark' */
|
/** Theme mode @default 'dark' */
|
||||||
theme?: ThemeMode;
|
theme?: ThemeMode;
|
||||||
/** Additional className for the wrapper div */
|
/** When true, sets data-brand/data-theme on document.documentElement instead of a wrapper div */
|
||||||
|
documentLevel?: boolean;
|
||||||
|
/** Additional className for the wrapper div (ignored when documentLevel is true) */
|
||||||
className?: string;
|
className?: string;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
@@ -29,14 +32,26 @@ interface DTWebThemeProviderProps {
|
|||||||
export function DTWebThemeProvider({
|
export function DTWebThemeProvider({
|
||||||
brand = 'dt',
|
brand = 'dt',
|
||||||
theme = 'dark',
|
theme = 'dark',
|
||||||
|
documentLevel,
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
}: DTWebThemeProviderProps) {
|
}: DTWebThemeProviderProps) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (documentLevel) {
|
||||||
|
document.documentElement.dataset.brand = brand;
|
||||||
|
document.documentElement.dataset.theme = theme;
|
||||||
|
}
|
||||||
|
}, [documentLevel, brand, theme]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DTWebThemeContext.Provider value={{ brand, theme }}>
|
<DTWebThemeContext.Provider value={{ brand, theme }}>
|
||||||
|
{documentLevel ? (
|
||||||
|
children
|
||||||
|
) : (
|
||||||
<div data-brand={brand} data-theme={theme} className={cx('dt-theme-root', className)}>
|
<div data-brand={brand} data-theme={theme} className={cx('dt-theme-root', className)}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</DTWebThemeContext.Provider>
|
</DTWebThemeContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ export { DTSearchInput } from './components/DTSearchInput';
|
|||||||
export { DTProgressBar } from './components/DTProgressBar';
|
export { DTProgressBar } from './components/DTProgressBar';
|
||||||
export { DTAccordion } from './components/DTAccordion';
|
export { DTAccordion } from './components/DTAccordion';
|
||||||
export type { DTAccordionSection } from './components/DTAccordion';
|
export type { DTAccordionSection } from './components/DTAccordion';
|
||||||
export { DTStaggerContainer } from './components/DTStaggerContainer';
|
|
||||||
export { DTBadgeOverlay } from './components/DTBadgeOverlay';
|
export { DTBadgeOverlay } from './components/DTBadgeOverlay';
|
||||||
|
|
||||||
// Components — filter & feature
|
// Components — filter & feature
|
||||||
@@ -58,3 +57,6 @@ export { DTMobileFilterOverlay } from './components/DTMobileFilterOverlay';
|
|||||||
export { DTGallery } from './components/DTGallery';
|
export { DTGallery } from './components/DTGallery';
|
||||||
export type { DTGalleryItem } from './components/DTGallery';
|
export type { DTGalleryItem } from './components/DTGallery';
|
||||||
export { DTHexagon } from './components/DTHexagon';
|
export { DTHexagon } from './components/DTHexagon';
|
||||||
|
|
||||||
|
// Components — animation
|
||||||
|
export { DTStaggerContainer } from './components/DTStaggerContainer';
|
||||||
|
|||||||
@@ -8,6 +8,11 @@
|
|||||||
"name": "Dangerous Things",
|
"name": "Dangerous Things",
|
||||||
"email": "info@dangerousthings.com"
|
"email": "info@dangerousthings.com"
|
||||||
},
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/dangerous-tac0s/dt-design-system.git",
|
||||||
|
"directory": "packages/showcase/desktop"
|
||||||
|
},
|
||||||
"homepage": "https://github.com/dangerous-tac0s/dt-design-system",
|
"homepage": "https://github.com/dangerous-tac0s/dt-design-system",
|
||||||
"main": "dist/main/main.js",
|
"main": "dist/main/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -25,12 +30,15 @@
|
|||||||
"clean": "rm -rf dist release"
|
"clean": "rm -rf dist release"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@dangerousthings/hex-background": "^0.2.0",
|
||||||
"@dangerousthings/react": "*",
|
"@dangerousthings/react": "*",
|
||||||
"@dangerousthings/tokens": "*",
|
"@dangerousthings/tokens": "*",
|
||||||
"@dangerousthings/web": "*",
|
"@dangerousthings/web": "*",
|
||||||
|
"@react-three/fiber": "^9.5.0",
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
"react-dom": "^18.0.0",
|
"react-dom": "^18.0.0",
|
||||||
"react-icons": "^5.6.0"
|
"react-icons": "^5.6.0",
|
||||||
|
"three": "^0.183.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dangerousthings/tailwind-preset": "*",
|
"@dangerousthings/tailwind-preset": "*",
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { themes } from '@dangerousthings/tokens';
|
import { themes } from '@dangerousthings/tokens';
|
||||||
import type { ThemeBrand, ThemeMode } 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 { HomePage } from './pages/HomePage';
|
||||||
import { BevelsPage } from './pages/BevelsPage';
|
import { BevelsPage } from './pages/BevelsPage';
|
||||||
import { GlowsPage } from './pages/GlowsPage';
|
|
||||||
import { FormsPage } from './pages/FormsPage';
|
import { FormsPage } from './pages/FormsPage';
|
||||||
import { AnimationsPage } from './pages/AnimationsPage';
|
import { AnimationsPage } from './pages/AnimationsPage';
|
||||||
import { CardsAdvancedPage } from './pages/CardsAdvancedPage';
|
import { CardsAdvancedPage } from './pages/CardsAdvancedPage';
|
||||||
import { TokensPage } from './pages/TokensPage';
|
import { TokensPage } from './pages/TokensPage';
|
||||||
|
import { HexBackgroundPage } from './pages/HexBackgroundPage';
|
||||||
|
|
||||||
const navPages = [
|
const navPages = [
|
||||||
{ hash: '', label: 'Home' },
|
{ hash: '', label: 'Home' },
|
||||||
{ hash: 'bevels', label: 'Bevels' },
|
{ hash: 'bevels', label: 'Bevels' },
|
||||||
{ hash: 'glows', label: 'Glows' },
|
|
||||||
{ hash: 'forms', label: 'Forms' },
|
{ hash: 'forms', label: 'Forms' },
|
||||||
{ hash: 'animations', label: 'Animations' },
|
{ hash: 'animations', label: 'Animations' },
|
||||||
{ hash: 'cards-advanced', label: 'Advanced Cards' },
|
{ hash: 'cards-advanced', label: 'Advanced Cards' },
|
||||||
|
{ hash: 'hex-background', label: 'Hex Background' },
|
||||||
{ hash: 'tokens', label: 'Tokens' },
|
{ hash: 'tokens', label: 'Tokens' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -23,6 +25,7 @@ export function App() {
|
|||||||
const [brand, setBrand] = useState<ThemeBrand>('dt');
|
const [brand, setBrand] = useState<ThemeBrand>('dt');
|
||||||
const [theme, setTheme] = useState<ThemeMode>('dark');
|
const [theme, setTheme] = useState<ThemeMode>('dark');
|
||||||
const [currentHash, setCurrentHash] = useState('');
|
const [currentHash, setCurrentHash] = useState('');
|
||||||
|
const [hexProps, setHexProps] = useState<HexGridBackgroundProps>({});
|
||||||
|
|
||||||
// Sync brand/theme to <html> so CSS custom properties cascade everywhere
|
// Sync brand/theme to <html> so CSS custom properties cascade everywhere
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -43,10 +46,10 @@ export function App() {
|
|||||||
function renderPage() {
|
function renderPage() {
|
||||||
switch (currentHash) {
|
switch (currentHash) {
|
||||||
case 'bevels': return <BevelsPage />;
|
case 'bevels': return <BevelsPage />;
|
||||||
case 'glows': return <GlowsPage />;
|
|
||||||
case 'forms': return <FormsPage />;
|
case 'forms': return <FormsPage />;
|
||||||
case 'animations': return <AnimationsPage />;
|
case 'animations': return <AnimationsPage />;
|
||||||
case 'cards-advanced': return <CardsAdvancedPage />;
|
case 'cards-advanced': return <CardsAdvancedPage />;
|
||||||
|
case 'hex-background': return <HexBackgroundPage hexProps={hexProps} onHexPropsChange={setHexProps} />;
|
||||||
case 'tokens': return <TokensPage brand={brand} />;
|
case 'tokens': return <TokensPage brand={brand} />;
|
||||||
default: return <HomePage />;
|
default: return <HomePage />;
|
||||||
}
|
}
|
||||||
@@ -92,6 +95,7 @@ export function App() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
<HexGridBackground {...hexProps} />
|
||||||
<main id="content">
|
<main id="content">
|
||||||
{renderPage()}
|
{renderPage()}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import { DTCard, DTStaggerContainer } from '@dangerousthings/react';
|
import { DTCard } from '@dangerousthings/react';
|
||||||
import { Section, Row, CodeLabel } from '../components/Section';
|
import { Section, Row, CodeLabel } from '../components/Section';
|
||||||
|
|
||||||
function AnimBox({ className, label }: { className: string; label: string }) {
|
function AnimBox({ className, label }: { className: string; label: string }) {
|
||||||
@@ -25,15 +25,77 @@ 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} ${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() {
|
export function AnimationsPage() {
|
||||||
const [entranceKey, setEntranceKey] = useState(0);
|
const [entranceKey, setEntranceKey] = useState(0);
|
||||||
const [staggerKey, setStaggerKey] = useState(0);
|
|
||||||
const [accordionExpanded, setAccordionExpanded] = useState(false);
|
const [accordionExpanded, setAccordionExpanded] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className="page-title">Animations</h1>
|
<h1 className="page-title">Animations</h1>
|
||||||
<p className="page-subtitle">Entrance animations, interactive effects, stagger containers, and transition utilities.</p>
|
<p className="page-subtitle">Entrance animations, interactive effects, and transition utilities.</p>
|
||||||
|
|
||||||
<Section title="Entrance Animations" description="One-shot animations for elements entering the viewport.">
|
<Section title="Entrance Animations" description="One-shot animations for elements entering the viewport.">
|
||||||
<Row key={entranceKey}>
|
<Row key={entranceKey}>
|
||||||
@@ -56,28 +118,6 @@ export function AnimationsPage() {
|
|||||||
<CodeLabel text=".dt-animate-pulse | .dt-animate-ping | .dt-animate-spin" />
|
<CodeLabel text=".dt-animate-pulse | .dt-animate-ping | .dt-animate-spin" />
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="Stagger Container" description="Automatic staggered scale-in animation for child elements. Customizable via --dt-stagger-duration and --dt-stagger-interval.">
|
|
||||||
<DTStaggerContainer
|
|
||||||
key={staggerKey}
|
|
||||||
className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-dt-3"
|
|
||||||
>
|
|
||||||
{Array.from({ length: 12 }, (_, i) => (
|
|
||||||
<DTCard key={i} title={`CARD ${i + 1}`} style={{ textAlign: 'center' }}>
|
|
||||||
<div className="card-body" style={{ fontSize: '0.75rem' }}>Item #{i + 1}</div>
|
|
||||||
</DTCard>
|
|
||||||
))}
|
|
||||||
</DTStaggerContainer>
|
|
||||||
<button
|
|
||||||
className="btn-secondary"
|
|
||||||
style={{ marginTop: 'var(--space-4)' }}
|
|
||||||
onClick={() => setStaggerKey(k => k + 1)}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
REPLAY STAGGER
|
|
||||||
</button>
|
|
||||||
<CodeLabel text="<DTStaggerContainer> — nth-child delays up to 24 children" />
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Section title="Transition Utilities" description="Accordion expand, chevron rotation, and progress bar transitions.">
|
<Section title="Transition Utilities" description="Accordion expand, chevron rotation, and progress bar transitions.">
|
||||||
<div style={{ maxWidth: 400 }}>
|
<div style={{ maxWidth: 400 }}>
|
||||||
<button
|
<button
|
||||||
@@ -93,7 +133,6 @@ export function AnimationsPage() {
|
|||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
color: 'var(--color-text)',
|
color: 'var(--color-text)',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
textTransform: 'uppercase',
|
|
||||||
}}
|
}}
|
||||||
aria-expanded={accordionExpanded}
|
aria-expanded={accordionExpanded}
|
||||||
onClick={() => setAccordionExpanded(!accordionExpanded)}
|
onClick={() => setAccordionExpanded(!accordionExpanded)}
|
||||||
@@ -126,6 +165,11 @@ export function AnimationsPage() {
|
|||||||
<CodeLabel text=".dt-transition-accordion | .dt-transition-chevron | .dt-transition-progress" />
|
<CodeLabel text=".dt-transition-accordion | .dt-transition-chevron | .dt-transition-progress" />
|
||||||
</Section>
|
</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'].">
|
<Section title="Scrollbar Styling" description="Thin neon scrollbar scoped under [data-brand='dt'].">
|
||||||
<div
|
<div
|
||||||
className="dt-scrollbar"
|
className="dt-scrollbar"
|
||||||
|
|||||||
@@ -53,12 +53,12 @@ export function BevelsPage() {
|
|||||||
<Row>
|
<Row>
|
||||||
{modes.map(mode => (
|
{modes.map(mode => (
|
||||||
<button key={mode} className={`dt-menu-item mode-${mode}`} type="button" onClick={() => setModalVariant(mode)}>
|
<button key={mode} className={`dt-menu-item mode-${mode}`} type="button" onClick={() => setModalVariant(mode)}>
|
||||||
{mode.toUpperCase()} MODAL
|
{mode} Modal
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</Row>
|
</Row>
|
||||||
<CodeLabel text="<DTModal variant='normal' visible onDismiss title='...'>content</DTModal>" />
|
<CodeLabel text="<DTModal variant='normal' visible onDismiss title='...'>content</DTModal>" />
|
||||||
<DTModal visible={modalVariant !== null} onDismiss={() => setModalVariant(null)} variant={modalVariant ?? 'normal'} title={`${(modalVariant ?? 'normal').toUpperCase()} MODAL`}>
|
<DTModal visible={modalVariant !== null} onDismiss={() => setModalVariant(null)} variant={modalVariant ?? 'normal'} title={`${modalVariant ?? 'normal'} Modal`}>
|
||||||
<p style={{ marginBottom: 'var(--space-4)' }}>This is a <strong>{modalVariant}</strong> modal with beveled card shape, backdrop blur, and scale-in animation.</p>
|
<p style={{ marginBottom: 'var(--space-4)' }}>This is a <strong>{modalVariant}</strong> modal with beveled card shape, backdrop blur, and scale-in animation.</p>
|
||||||
<p style={{ color: 'var(--color-text-muted)', fontSize: '0.85rem' }}>Click the backdrop or press Escape to dismiss.</p>
|
<p style={{ color: 'var(--color-text-muted)', fontSize: '0.85rem' }}>Click the backdrop or press Escape to dismiss.</p>
|
||||||
</DTModal>
|
</DTModal>
|
||||||
@@ -74,7 +74,7 @@ export function BevelsPage() {
|
|||||||
</button>
|
</button>
|
||||||
</Row>
|
</Row>
|
||||||
<CodeLabel text="<DTDrawer position='right' heading='...' visible onDismiss>content</DTDrawer>" />
|
<CodeLabel text="<DTDrawer position='right' heading='...' visible onDismiss>content</DTDrawer>" />
|
||||||
<DTDrawer visible={drawerSide !== null} onDismiss={() => setDrawerSide(null)} position={drawerSide ?? 'right'} heading={`${(drawerSide ?? 'right').toUpperCase()} DRAWER`} headingVariant={drawerSide === 'left' ? 'other' : 'emphasis'}>
|
<DTDrawer visible={drawerSide !== null} onDismiss={() => setDrawerSide(null)} position={drawerSide ?? 'right'} heading={`${drawerSide ?? 'right'} Drawer`} headingVariant={drawerSide === 'left' ? 'other' : 'emphasis'}>
|
||||||
<p style={{ marginBottom: 'var(--space-4)' }}>Sliding panel from the <strong>{drawerSide}</strong> edge with beveled corners and backdrop blur.</p>
|
<p style={{ marginBottom: 'var(--space-4)' }}>Sliding panel from the <strong>{drawerSide}</strong> edge with beveled corners and backdrop blur.</p>
|
||||||
<p style={{ color: 'var(--color-text-muted)', fontSize: '0.85rem' }}>Click the backdrop, press Escape, or click ✕ to dismiss.</p>
|
<p style={{ color: 'var(--color-text-muted)', fontSize: '0.85rem' }}>Click the backdrop, press Escape, or click ✕ to dismiss.</p>
|
||||||
</DTDrawer>
|
</DTDrawer>
|
||||||
@@ -91,7 +91,7 @@ export function BevelsPage() {
|
|||||||
|
|
||||||
<Section title="Accent Top" description="Used on accordion headers and menu items.">
|
<Section title="Accent Top" description="Used on accordion headers and menu items.">
|
||||||
<div className="dt-accent-top" style={{ padding: 'var(--space-4)', background: 'var(--color-surface)', border: '1px solid var(--color-border)' }}>
|
<div className="dt-accent-top" style={{ padding: 'var(--space-4)', background: 'var(--color-surface)', border: '1px solid var(--color-border)' }}>
|
||||||
<span style={{ fontWeight: 600, textTransform: 'uppercase' }}>Thick Top Border Accent</span>
|
<span style={{ fontWeight: 600 }}>Thick Top Border Accent</span>
|
||||||
</div>
|
</div>
|
||||||
<CodeLabel text=".dt-accent-top" />
|
<CodeLabel text=".dt-accent-top" />
|
||||||
</Section>
|
</Section>
|
||||||
@@ -99,7 +99,7 @@ export function BevelsPage() {
|
|||||||
<Section title="Card Color Modes" description="Per-card mode coloring — see Advanced Cards page for full demos.">
|
<Section title="Card Color Modes" description="Per-card mode coloring — see Advanced Cards page for full demos.">
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-dt-3">
|
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-dt-3">
|
||||||
{modes.map(mode => (
|
{modes.map(mode => (
|
||||||
<DTCard key={mode} variant={mode} title={mode.toUpperCase()}>
|
<DTCard key={mode} variant={mode} title={mode}>
|
||||||
<div className="card-body" style={{ fontSize: '0.75rem' }}>mode-{mode}</div>
|
<div className="card-body" style={{ fontSize: '0.75rem' }}>mode-{mode}</div>
|
||||||
</DTCard>
|
</DTCard>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { createElement, useState, type CSSProperties } from 'react';
|
import { createElement, type CSSProperties } from 'react';
|
||||||
import {
|
import {
|
||||||
DTCard,
|
DTCard,
|
||||||
DTStaggerContainer,
|
|
||||||
DTFeatureLegend,
|
DTFeatureLegend,
|
||||||
} from '@dangerousthings/react';
|
} from '@dangerousthings/react';
|
||||||
import type { DTFeatureItem } from '@dangerousthings/react';
|
import type { DTFeatureItem } from '@dangerousthings/react';
|
||||||
@@ -33,35 +32,35 @@ const modes: DTVariant[] = ['normal', 'emphasis', 'warning', 'success', 'other']
|
|||||||
const ico = (C: any) => createElement(C, { style: { fontSize: '2rem' } });
|
const ico = (C: any) => createElement(C, { style: { fontSize: '2rem' } });
|
||||||
|
|
||||||
// Full chip feature legend (9 features — from storefront UseCaseLegend)
|
// Full chip feature legend (9 features — from storefront UseCaseLegend)
|
||||||
|
// NExT v2: NTAGI2C (HF) + T5577 (LF) with power-harvesting LED
|
||||||
const chipFeatures: DTFeatureItem[] = [
|
const chipFeatures: DTFeatureItem[] = [
|
||||||
{ key: 'smartphone', name: 'Smartphone', icon: ico(MdOutlinePhonelinkRing), state: 'supported' },
|
{ 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' },
|
{ key: 'access_control', name: 'Access Control', icon: ico(MdOutlineVpnKey), state: 'supported', detail: 'Legacy' },
|
||||||
{ key: 'digital_security', name: 'Digital Security', icon: ico(FaUserShield), state: 'supported' },
|
{ key: 'digital_security', name: 'Digital Security', icon: ico(FaUserShield), state: 'unsupported', detail: 'Not Supported' },
|
||||||
{ key: 'cryptography', name: 'Cryptography', icon: ico(LuBinary), state: 'unsupported' },
|
{ key: 'cryptography', name: 'Cryptography', icon: ico(LuBinary), state: 'unsupported', detail: 'Not Supported' },
|
||||||
{ key: 'data_sharing', name: 'Data Sharing', icon: ico(MdOutlineMobileScreenShare), state: 'supported' },
|
{ key: 'data_sharing', name: 'Data Sharing', icon: ico(MdOutlineMobileScreenShare), state: 'supported', detail: '1 kB' },
|
||||||
{ key: 'payment', name: 'Payment', icon: ico(MdOutlineCreditCard), state: 'disabled' },
|
{ key: 'payment', name: 'Payment', icon: ico(MdOutlineCreditCard), state: 'unsupported', detail: 'Not Supported' },
|
||||||
{ key: 'magic', name: 'UID Magic', icon: ico(MdOutlineCopyAll), state: 'supported' },
|
{ key: 'magic', name: 'Magic', icon: ico(MdOutlineCopyAll), state: 'supported', detail: 'Yes' },
|
||||||
{ key: 'illumination', name: 'Illumination', icon: ico(MdOutlineLightbulb), state: 'unsupported' },
|
{ key: 'Illumination', name: 'Illumination', icon: ico(MdOutlineLightbulb), state: 'supported', detail: 'HF: green, blue, white' },
|
||||||
{ key: 'temperature', name: 'Temperature', icon: ico(MdOutlineThermostat), state: 'unsupported' },
|
{ key: 'temperature', name: 'Sensors', icon: ico(MdOutlineThermostat), state: 'unsupported', detail: 'None' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Full biomagnet feature legend (4 features — from storefront MagnetUseCaseLegend)
|
// Full biomagnet feature legend (4 features — from storefront MagnetUseCaseLegend)
|
||||||
|
// m0422a: axial neodymium disc, bioresin coated
|
||||||
const magnetFeatures: DTFeatureItem[] = [
|
const magnetFeatures: DTFeatureItem[] = [
|
||||||
{ key: 'sensing', name: 'Sensing', icon: ico(MdOutlineSensors), state: 'supported' },
|
{ key: 'sensing', name: 'Sensing', icon: ico(MdOutlineSensors), state: 'supported', detail: 'RP 22,860 G/g' },
|
||||||
{ key: 'lifting', name: 'Lifting', icon: ico(MdOutlineFitbit), state: 'supported' },
|
{ key: 'lifting', name: 'Lifting', icon: ico(MdOutlineFitbit), state: 'supported', detail: '~20 g' },
|
||||||
{ key: 'haptics', name: 'Haptics', icon: ico(MdOutlineVibration), state: 'supported' },
|
{ key: 'haptics', name: 'Haptics', icon: ico(MdOutlineVibration), state: 'supported', detail: 'Push/Pull' },
|
||||||
{ key: 'polarity', name: 'Polarity Detection', icon: ico(MdOutlineExplore), state: 'unsupported' },
|
{ key: 'polarity', name: 'Polarity Detection', icon: ico(MdOutlineExplore), state: 'supported', detail: 'Monopole' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Full aesthetic feature legend (2 features — from storefront AestheticUseCaseLegend)
|
// Full aesthetic feature legend (2 features — from storefront AestheticUseCaseLegend)
|
||||||
const aestheticFeatures: DTFeatureItem[] = [
|
const aestheticFeatures: DTFeatureItem[] = [
|
||||||
{ key: 'illumination', name: 'Illumination', icon: ico(MdLightbulbOutline), state: 'supported' },
|
{ key: 'illumination', name: 'Illumination', icon: ico(MdLightbulbOutline), state: 'supported', detail: 'HF: red, green, blue, white' },
|
||||||
{ key: 'prominence', name: 'Prominence', icon: ico(MdOutlineVisibility), state: 'supported' },
|
{ key: 'prominence', name: 'Prominence', icon: ico(MdOutlineVisibility), state: 'supported', detail: 'Low' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function CardsAdvancedPage() {
|
export function CardsAdvancedPage() {
|
||||||
const [staggerKey, setStaggerKey] = useState(0);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className="page-title">Advanced Cards</h1>
|
<h1 className="page-title">Advanced Cards</h1>
|
||||||
@@ -70,7 +69,7 @@ export function CardsAdvancedPage() {
|
|||||||
<Section title="Card Color Modes" description="Per-card color via variant prop. Sets --dt-card-color, glow color, and accent.">
|
<Section title="Card Color Modes" description="Per-card color via variant prop. Sets --dt-card-color, glow color, and accent.">
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-dt-3">
|
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-dt-3">
|
||||||
{modes.map(mode => (
|
{modes.map(mode => (
|
||||||
<DTCard key={mode} variant={mode} title={mode.toUpperCase()}>
|
<DTCard key={mode} variant={mode} title={mode}>
|
||||||
<div className="card-body" style={{ fontSize: '0.8rem' }}>mode-{mode}</div>
|
<div className="card-body" style={{ fontSize: '0.8rem' }}>mode-{mode}</div>
|
||||||
</DTCard>
|
</DTCard>
|
||||||
))}
|
))}
|
||||||
@@ -79,6 +78,23 @@ export function CardsAdvancedPage() {
|
|||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
|
|
||||||
|
<Section title="Card Grow" description="Cards stretch to equal height in a row via grow prop. Compare the varying content heights.">
|
||||||
|
<div style={{ display: 'flex', gap: 'var(--space-3)' }}>
|
||||||
|
<DTCard variant="normal" title="SHORT" grow>
|
||||||
|
<div className="card-body" style={{ fontSize: '0.8rem' }}>One line</div>
|
||||||
|
</DTCard>
|
||||||
|
<DTCard variant="emphasis" title="TALL" grow>
|
||||||
|
<div className="card-body" style={{ fontSize: '0.8rem' }}>
|
||||||
|
Multiple lines of content to demonstrate that the shorter card stretches to match this card's height.
|
||||||
|
</div>
|
||||||
|
</DTCard>
|
||||||
|
<DTCard variant="other" title="MEDIUM" grow>
|
||||||
|
<div className="card-body" style={{ fontSize: '0.8rem' }}>Two lines of content here.</div>
|
||||||
|
</DTCard>
|
||||||
|
</div>
|
||||||
|
<CodeLabel text="<DTCard grow /> — flex-grow: 1 + height: 100% to fill container" />
|
||||||
|
</Section>
|
||||||
|
|
||||||
<Section title="Card Progress Bar" description="Vertical left-edge bar driven by progress prop (0-100).">
|
<Section title="Card Progress Bar" description="Vertical left-edge bar driven by progress prop (0-100).">
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-dt-3">
|
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-dt-3">
|
||||||
{[0, 25, 50, 75, 100].map((val, i) => (
|
{[0, 25, 50, 75, 100].map((val, i) => (
|
||||||
@@ -97,41 +113,47 @@ export function CardsAdvancedPage() {
|
|||||||
{ label: 'BUNDLE', mode: 'other' as DTVariant, img: 'https://images.unsplash.com/photo-1518770660439-4636190af475?w=400&h=400&fit=crop' },
|
{ label: 'BUNDLE', mode: 'other' as DTVariant, img: 'https://images.unsplash.com/photo-1518770660439-4636190af475?w=400&h=400&fit=crop' },
|
||||||
{ label: 'NFC', mode: 'normal' as DTVariant, img: 'https://images.unsplash.com/photo-1550751827-4bd374c3f58b?w=400&h=400&fit=crop' },
|
{ label: 'NFC', mode: 'normal' as DTVariant, img: 'https://images.unsplash.com/photo-1550751827-4bd374c3f58b?w=400&h=400&fit=crop' },
|
||||||
].map(badge => (
|
].map(badge => (
|
||||||
<DTCard key={badge.label} variant={badge.mode} title={badge.label + ' PRODUCT'}>
|
<DTCard key={badge.label} variant={badge.mode} title={badge.label + ' PRODUCT'}
|
||||||
<div className="card-body-flush dt-badge-parent" style={{ aspectRatio: '1' }}>
|
badge={<span>{badge.label}</span>}>
|
||||||
|
<div className="card-body-flush" style={{ aspectRatio: '1' }}>
|
||||||
<img src={badge.img} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
|
<img src={badge.img} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
|
||||||
<span className="dt-card-badge">{badge.label}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</DTCard>
|
</DTCard>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<CodeLabel text=".dt-badge-parent > .dt-card-badge — badge positioned on card image container" />
|
<CodeLabel text="<DTCard badge={<span>LAB</span>}> — badge rendered and positioned by card" />
|
||||||
</Section>
|
</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 }}>
|
<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
|
<button
|
||||||
key={item}
|
key={item.name}
|
||||||
className={`dt-menu-item${i === 0 ? ' active' : ''}${i === 4 ? ' mode-warning' : ''}`}
|
className={`dt-menu-item${item.cls}`}
|
||||||
style={i > 1 && i < 4 ? { '--dt-menu-level': '1' } as CSSProperties : undefined}
|
style={item.pad ? { paddingLeft: item.pad } : undefined}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{item}
|
{item.name}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<Section title="Feature Legends" description="Product feature grids from the storefront. Icons indicate feature capabilities; color indicates state.">
|
<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="NFC CHIP FEATURES" variant="normal" columns={5} />
|
<DTFeatureLegend features={chipFeatures} title="NExT v2 Features" variant="normal" columns={5} />
|
||||||
<div style={{ height: 'var(--space-6)' }} />
|
<div style={{ height: 'var(--space-6)' }} />
|
||||||
<DTFeatureLegend features={magnetFeatures} title="BIOMAGNET FEATURES" variant="emphasis" columns={4} />
|
<DTFeatureLegend features={magnetFeatures} title="m0422a Features" variant="emphasis" columns={4} />
|
||||||
<div style={{ height: 'var(--space-6)' }} />
|
<div style={{ height: 'var(--space-6)' }} />
|
||||||
<DTFeatureLegend features={aestheticFeatures} title="AESTHETIC FEATURES" variant="other" columns={2} />
|
<DTFeatureLegend features={aestheticFeatures} title="xLED HF Features" variant="other" columns={2} />
|
||||||
<div style={{ height: 'var(--space-4)' }} />
|
<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={magnetFeatures} variant='emphasis' columns={4} />" />
|
||||||
<CodeLabel text="<DTFeatureLegend features={aestheticFeatures} variant='other' columns={2} />" />
|
<CodeLabel text="<DTFeatureLegend features={aestheticFeatures} variant='other' columns={2} />" />
|
||||||
<div style={{ marginTop: 'var(--space-3)', display: 'flex', gap: 'var(--space-4)', fontSize: '0.75rem' }}>
|
<div style={{ marginTop: 'var(--space-3)', display: 'flex', gap: 'var(--space-4)', fontSize: '0.75rem' }}>
|
||||||
@@ -141,27 +163,6 @@ export function CardsAdvancedPage() {
|
|||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="Stagger + Modes" description="Mode-colored cards in a stagger container.">
|
|
||||||
<DTStaggerContainer
|
|
||||||
key={staggerKey}
|
|
||||||
className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-dt-3"
|
|
||||||
>
|
|
||||||
{Array.from({ length: 10 }, (_, i) => (
|
|
||||||
<DTCard key={i} variant={modes[i % modes.length]} title={modes[i % modes.length].toUpperCase()} style={{ textAlign: 'center' }}>
|
|
||||||
{null}
|
|
||||||
</DTCard>
|
|
||||||
))}
|
|
||||||
</DTStaggerContainer>
|
|
||||||
<button
|
|
||||||
className="btn-secondary"
|
|
||||||
style={{ marginTop: 'var(--space-4)' }}
|
|
||||||
onClick={() => setStaggerKey(k => k + 1)}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
REPLAY
|
|
||||||
</button>
|
|
||||||
<CodeLabel text="<DTStaggerContainer> with <DTCard variant='...' />" />
|
|
||||||
</Section>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,20 +102,26 @@ export function FormsPage() {
|
|||||||
<CodeLabel text=".dt-filter-header | .dt-filter-header.active" />
|
<CodeLabel text=".dt-filter-header | .dt-filter-header.active" />
|
||||||
</Section>
|
</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 }}>
|
<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
|
<button
|
||||||
key={name}
|
key={item.name}
|
||||||
className={`dt-menu-item${i === 0 ? ' active' : ''}${i === 4 ? ' mode-warning' : ''}`}
|
className={`dt-menu-item${item.cls}`}
|
||||||
style={(i === 2 || i === 3) ? { '--dt-menu-level': '1' } as React.CSSProperties : undefined}
|
style={item.pad ? { paddingLeft: item.pad } : undefined}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{name}
|
{item.name}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<CodeLabel text=".dt-menu-item | .active | --dt-menu-level: 1" />
|
<CodeLabel text=".dt-menu-item | .active | paddingLeft for nesting" />
|
||||||
</Section>
|
</Section>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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)', 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
import { DTCard, DTStaggerContainer } from '@dangerousthings/react';
|
import { DTCard } from '@dangerousthings/react';
|
||||||
import type { DTVariant } from '@dangerousthings/tokens';
|
import type { DTVariant } from '@dangerousthings/tokens';
|
||||||
import { Section, CodeLabel } from '../components/Section';
|
import { Section, CodeLabel } from '../components/Section';
|
||||||
|
|
||||||
const categories: { hash: string; title: string; desc: string; mode: DTVariant; count: number }[] = [
|
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: '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: '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: 'animations', title: 'Animations', desc: 'Entrance animations, 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 },
|
{ hash: 'cards-advanced', title: 'Advanced Cards', desc: 'Card color modes, progress bars, badge overlays, interactive bevel buttons, feature legend', mode: 'warning', count: 6 },
|
||||||
{ hash: 'tokens', title: 'Tokens', desc: 'Color palette, typography, spacing, and shape values for the active brand', mode: 'normal', count: 3 },
|
{ hash: 'tokens', title: 'Tokens', desc: 'Color palette, typography, spacing, and shape values for the active brand', mode: 'normal', count: 3 },
|
||||||
];
|
];
|
||||||
@@ -32,7 +31,6 @@ export function HomePage() {
|
|||||||
fontWeight: 900,
|
fontWeight: 900,
|
||||||
letterSpacing: '0.15em',
|
letterSpacing: '0.15em',
|
||||||
marginBottom: '0.25rem',
|
marginBottom: '0.25rem',
|
||||||
textTransform: 'uppercase',
|
|
||||||
}}>
|
}}>
|
||||||
DANGEROUS THINGS
|
DANGEROUS THINGS
|
||||||
</h1>
|
</h1>
|
||||||
@@ -41,7 +39,6 @@ export function HomePage() {
|
|||||||
fontSize: '1.25rem',
|
fontSize: '1.25rem',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
letterSpacing: '0.2em',
|
letterSpacing: '0.2em',
|
||||||
textTransform: 'uppercase',
|
|
||||||
marginTop: 0,
|
marginTop: 0,
|
||||||
}}>
|
}}>
|
||||||
DESIGN SYSTEM
|
DESIGN SYSTEM
|
||||||
@@ -72,16 +69,15 @@ export function HomePage() {
|
|||||||
color: 'var(--color-text-muted)',
|
color: 'var(--color-text-muted)',
|
||||||
fontSize: '0.7rem',
|
fontSize: '0.7rem',
|
||||||
letterSpacing: '0.15em',
|
letterSpacing: '0.15em',
|
||||||
textTransform: 'uppercase',
|
|
||||||
marginBottom: 'var(--space-4)',
|
marginBottom: 'var(--space-4)',
|
||||||
}}>
|
}}>
|
||||||
COMPONENT CATALOG
|
COMPONENT CATALOG
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<DTStaggerContainer className="grid grid-cols-1 md:grid-cols-2 gap-dt-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-dt-4">
|
||||||
{categories.map(cat => (
|
{categories.map(cat => (
|
||||||
<a key={cat.hash} href={`#/${cat.hash}`} style={{ textDecoration: 'none', color: 'inherit' }}>
|
<a key={cat.hash} href={`#/${cat.hash}`} style={{ textDecoration: 'none', color: 'inherit' }}>
|
||||||
<DTCard variant={cat.mode} title={cat.title.toUpperCase()}>
|
<DTCard variant={cat.mode} title={cat.title}>
|
||||||
<div className="card-body">{cat.desc}</div>
|
<div className="card-body">{cat.desc}</div>
|
||||||
{cat.count > 0 && (
|
{cat.count > 0 && (
|
||||||
<div style={{ color: 'var(--color-text-muted)', fontSize: '0.7rem', marginTop: 'var(--space-2)' }}>
|
<div style={{ color: 'var(--color-text-muted)', fontSize: '0.7rem', marginTop: 'var(--space-2)' }}>
|
||||||
@@ -91,7 +87,7 @@ export function HomePage() {
|
|||||||
</DTCard>
|
</DTCard>
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
</DTStaggerContainer>
|
</div>
|
||||||
|
|
||||||
<Section title="Quick Start" description="Import the React components and CSS to get started.">
|
<Section title="Quick Start" description="Import the React components and CSS to get started.">
|
||||||
<div className="terminal dt-accent-top">
|
<div className="terminal dt-accent-top">
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ body {
|
|||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.1em;
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
text-transform: uppercase;
|
|
||||||
padding-bottom: var(--space-4);
|
padding-bottom: var(--space-4);
|
||||||
border-bottom: 2px solid var(--color-primary);
|
border-bottom: 2px solid var(--color-primary);
|
||||||
}
|
}
|
||||||
@@ -65,7 +64,6 @@ body {
|
|||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-transform: uppercase;
|
|
||||||
transition: all var(--transition-fast);
|
transition: all var(--transition-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +91,6 @@ body {
|
|||||||
font-size: 0.625rem;
|
font-size: 0.625rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-transform: uppercase;
|
|
||||||
transition: all var(--transition-fast);
|
transition: all var(--transition-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +114,6 @@ body {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
letter-spacing: 0.02em;
|
letter-spacing: 0.02em;
|
||||||
text-transform: uppercase;
|
|
||||||
border-left: 3px solid transparent;
|
border-left: 3px solid transparent;
|
||||||
transition: all var(--transition-fast);
|
transition: all var(--transition-fast);
|
||||||
}
|
}
|
||||||
@@ -151,7 +147,6 @@ body {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 1.125rem;
|
font-size: 1.125rem;
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
margin-bottom: var(--space-2);
|
margin-bottom: var(--space-2);
|
||||||
padding-bottom: var(--space-2);
|
padding-bottom: var(--space-2);
|
||||||
@@ -175,7 +170,6 @@ body {
|
|||||||
.demo-label {
|
.demo-label {
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
font-size: 0.6875rem;
|
font-size: 0.6875rem;
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
margin-bottom: var(--space-2);
|
margin-bottom: var(--space-2);
|
||||||
margin-top: var(--space-4);
|
margin-top: var(--space-4);
|
||||||
@@ -200,7 +194,6 @@ body {
|
|||||||
padding: var(--space-3) var(--space-6);
|
padding: var(--space-3) var(--space-6);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all var(--transition-fast);
|
transition: all var(--transition-fast);
|
||||||
@@ -244,7 +237,6 @@ body {
|
|||||||
.card-title {
|
.card-title {
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +252,6 @@ body {
|
|||||||
padding: var(--space-2) var(--space-3);
|
padding: var(--space-2) var(--space-3);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +291,6 @@ input:focus {
|
|||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
font-size: 1.75rem;
|
font-size: 1.75rem;
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.1em;
|
||||||
margin-bottom: var(--space-2);
|
margin-bottom: var(--space-2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
|
||||||
import { Text } from 'react-native-paper';
|
import { Text } from 'react-native-paper';
|
||||||
import {
|
import {
|
||||||
DTCard,
|
DTCard,
|
||||||
DTLabel,
|
DTLabel,
|
||||||
DTStaggerContainer,
|
|
||||||
useDTTheme,
|
useDTTheme,
|
||||||
useScaleIn,
|
useScaleIn,
|
||||||
usePulse,
|
usePulse,
|
||||||
} from '@dangerousthings/react-native';
|
} from '@dangerousthings/react-native';
|
||||||
import type { DTVariant } from '@dangerousthings/react-native';
|
|
||||||
import { ScreenContainer } from '../components/ScreenContainer';
|
import { ScreenContainer } from '../components/ScreenContainer';
|
||||||
import { DemoSection } from '../components/DemoSection';
|
import { DemoSection } from '../components/DemoSection';
|
||||||
import { CodeLabel } from '../components/CodeLabel';
|
import { CodeLabel } from '../components/CodeLabel';
|
||||||
@@ -20,44 +17,8 @@ export function AnimationsScreen() {
|
|||||||
const scaleAnim = useScaleIn({ duration: 600 });
|
const scaleAnim = useScaleIn({ duration: 600 });
|
||||||
const pulseAnim = usePulse(true);
|
const pulseAnim = usePulse(true);
|
||||||
|
|
||||||
const modes: DTVariant[] = ['normal', 'emphasis', 'warning', 'success', 'other'];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScreenContainer>
|
<ScreenContainer>
|
||||||
{/* Stagger Container */}
|
|
||||||
<DemoSection
|
|
||||||
title="DTStaggerContainer"
|
|
||||||
variant="normal"
|
|
||||||
description="Staggered scale-in entrance animation for child elements."
|
|
||||||
>
|
|
||||||
<DTStaggerContainer duration={330} interval={75}>
|
|
||||||
{modes.map((mode) => (
|
|
||||||
<DTCard key={mode} mode={mode} title={mode.toUpperCase()} style={{ marginBottom: 12 }}>
|
|
||||||
<Text variant="bodySmall" style={{ color: theme.colors.onSurface }}>
|
|
||||||
Staggered entrance with scale animation
|
|
||||||
</Text>
|
|
||||||
</DTCard>
|
|
||||||
))}
|
|
||||||
</DTStaggerContainer>
|
|
||||||
<CodeLabel text="<DTStaggerContainer duration={330} interval={75}>" />
|
|
||||||
</DemoSection>
|
|
||||||
|
|
||||||
{/* Stagger with Labels */}
|
|
||||||
<DemoSection
|
|
||||||
title="Staggered Labels"
|
|
||||||
variant="emphasis"
|
|
||||||
description="Labels with staggered entrance animation."
|
|
||||||
>
|
|
||||||
<DTStaggerContainer duration={400} interval={100}>
|
|
||||||
{modes.map((mode) => (
|
|
||||||
<View key={mode} style={{ marginBottom: 8 }}>
|
|
||||||
<DTLabel primaryText={mode.toUpperCase()} mode={mode} />
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
</DTStaggerContainer>
|
|
||||||
<CodeLabel text="DTStaggerContainer > DTLabel — customizable timing" />
|
|
||||||
</DemoSection>
|
|
||||||
|
|
||||||
{/* Scale-In Hook */}
|
{/* Scale-In Hook */}
|
||||||
<DemoSection
|
<DemoSection
|
||||||
title="useScaleIn"
|
title="useScaleIn"
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
DTCard,
|
DTCard,
|
||||||
DTChip,
|
DTChip,
|
||||||
DTBadgeOverlay,
|
DTBadgeOverlay,
|
||||||
DTStaggerContainer,
|
|
||||||
useDTTheme,
|
useDTTheme,
|
||||||
} from '@dangerousthings/react-native';
|
} from '@dangerousthings/react-native';
|
||||||
import type { DTVariant } from '@dangerousthings/react-native';
|
import type { DTVariant } from '@dangerousthings/react-native';
|
||||||
@@ -83,29 +82,6 @@ export function CardsAdvancedScreen() {
|
|||||||
<CodeLabel text="<DTBadgeOverlay position='bottom-right'><DTChip variant='warning'>LAB</DTChip>" />
|
<CodeLabel text="<DTBadgeOverlay position='bottom-right'><DTChip variant='warning'>LAB</DTChip>" />
|
||||||
</DemoSection>
|
</DemoSection>
|
||||||
|
|
||||||
{/* Stagger + Progress */}
|
|
||||||
<DemoSection
|
|
||||||
title="Staggered Cards with Progress"
|
|
||||||
variant="success"
|
|
||||||
description="Stagger container with progress bars across all modes."
|
|
||||||
>
|
|
||||||
<DTStaggerContainer>
|
|
||||||
{modes.map((mode) => (
|
|
||||||
<DTCard
|
|
||||||
key={mode}
|
|
||||||
mode={mode}
|
|
||||||
title={mode.toUpperCase()}
|
|
||||||
progress={Math.random()}
|
|
||||||
style={{ marginBottom: 12 }}
|
|
||||||
>
|
|
||||||
<Text variant="bodySmall" style={{ color: theme.colors.onSurface }}>
|
|
||||||
Staggered + progress
|
|
||||||
</Text>
|
|
||||||
</DTCard>
|
|
||||||
))}
|
|
||||||
</DTStaggerContainer>
|
|
||||||
<CodeLabel text="DTStaggerContainer > DTCard progress" />
|
|
||||||
</DemoSection>
|
|
||||||
</ScreenContainer>
|
</ScreenContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,14 +58,14 @@ const categories: {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'ADVANCED CARDS',
|
title: 'ADVANCED CARDS',
|
||||||
subtitle: 'Selected state, progress bar, badge overlays, stagger',
|
subtitle: 'Selected state, progress bar, badge overlays',
|
||||||
mode: 'emphasis',
|
mode: 'emphasis',
|
||||||
route: 'CardsAdvanced',
|
route: 'CardsAdvanced',
|
||||||
count: 4,
|
count: 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'ANIMATIONS',
|
title: 'ANIMATIONS',
|
||||||
subtitle: 'DTStaggerContainer, useScaleIn, usePulse',
|
subtitle: 'useScaleIn, usePulse',
|
||||||
mode: 'success',
|
mode: 'success',
|
||||||
route: 'Animations',
|
route: 'Animations',
|
||||||
count: 3,
|
count: 3,
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ Because the preset uses `var()` references, switching brands at runtime requires
|
|||||||
|
|
||||||
## Monorepo
|
## 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
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
# @dangerousthings/web
|
# @dangerousthings/web
|
||||||
|
|
||||||
|
## 0.4.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Video letterboxing for DTMediaFrame, card badge styling with bevel-aware positioning, restored DTStaggerContainer.
|
||||||
|
|
||||||
|
## 0.4.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- Video letterboxing for DTMediaFrame, card badge styling with bevel-aware positioning, restored DTStaggerContainer in react-native, updated DTGallery/DTModal/DTMobileFilterOverlay components.
|
||||||
|
|
||||||
## 0.3.0
|
## 0.3.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# @dangerousthings/web
|
# @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
|
## Install
|
||||||
|
|
||||||
@@ -24,7 +24,6 @@ Or import individual pieces:
|
|||||||
|
|
||||||
/* Individual components */
|
/* Individual components */
|
||||||
@import "@dangerousthings/web/components/bevels.css";
|
@import "@dangerousthings/web/components/bevels.css";
|
||||||
@import "@dangerousthings/web/components/glows.css";
|
|
||||||
@import "@dangerousthings/web/components/forms-dt.css";
|
@import "@dangerousthings/web/components/forms-dt.css";
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -53,7 +52,6 @@ import { themes, brands } from "@dangerousthings/web/theme-registry";
|
|||||||
| File | Description |
|
| 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 |
|
| `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 |
|
| `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 |
|
| `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"]` |
|
| `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)
|
**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`
|
**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`
|
**Scrollbar** — `.dt-scrollbar`, `.dt-scrollbar-mode`
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@dangerousthings/web",
|
"name": "@dangerousthings/web",
|
||||||
"version": "0.3.0",
|
"version": "0.4.1",
|
||||||
"description": "Web CSS theme for the Dangerous Things design system — bevels, glows, form styles",
|
"description": "Web CSS theme for the Dangerous Things design system — bevels, glows, form styles",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": {
|
"author": {
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
"main": "dist/index.css",
|
"main": "dist/index.css",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/index.css",
|
".": "./dist/index.css",
|
||||||
|
"./dist/index.css": "./dist/index.css",
|
||||||
"./tokens/*": "./dist/tokens/*",
|
"./tokens/*": "./dist/tokens/*",
|
||||||
"./components/*": "./dist/components/*",
|
"./components/*": "./dist/components/*",
|
||||||
"./fonts/*": "./dist/fonts/*",
|
"./fonts/*": "./dist/fonts/*",
|
||||||
|
|||||||
@@ -82,49 +82,16 @@
|
|||||||
|
|
||||||
/* ============================================================================
|
/* ============================================================================
|
||||||
Stagger Container
|
Stagger Container
|
||||||
Source: storefront framer-motion staggered card entrance
|
|
||||||
Usage: <div class="dt-stagger-container">...</div>
|
|
||||||
Customize: --dt-stagger-duration, --dt-stagger-interval
|
|
||||||
============================================================================ */
|
============================================================================ */
|
||||||
|
|
||||||
.dt-stagger-container {
|
.dt-stagger-container {
|
||||||
--dt-stagger-duration: 0.33s;
|
--dt-stagger-duration: 0.33s;
|
||||||
--dt-stagger-interval: 75ms;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dt-stagger-container > * {
|
.dt-stagger-container > * {
|
||||||
animation: dt-scale-in var(--dt-stagger-duration) ease-in-out both;
|
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)); }
|
|
||||||
.dt-stagger-container > :nth-child(3) { animation-delay: calc(2 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(4) { animation-delay: calc(3 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(5) { animation-delay: calc(4 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(6) { animation-delay: calc(5 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(7) { animation-delay: calc(6 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(8) { animation-delay: calc(7 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(9) { animation-delay: calc(8 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(10) { animation-delay: calc(9 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(11) { animation-delay: calc(10 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(12) { animation-delay: calc(11 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(13) { animation-delay: calc(12 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(14) { animation-delay: calc(13 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(15) { animation-delay: calc(14 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(16) { animation-delay: calc(15 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(17) { animation-delay: calc(16 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(18) { animation-delay: calc(17 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(19) { animation-delay: calc(18 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(20) { animation-delay: calc(19 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(21) { animation-delay: calc(20 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(22) { animation-delay: calc(21 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(23) { animation-delay: calc(22 * var(--dt-stagger-interval)); }
|
|
||||||
.dt-stagger-container > :nth-child(24) { animation-delay: calc(23 * var(--dt-stagger-interval)); }
|
|
||||||
|
|
||||||
.dt-stagger-container > :nth-child(n+25) {
|
|
||||||
animation-delay: calc(24 * var(--dt-stagger-interval));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================================
|
/* ============================================================================
|
||||||
Transition Utilities
|
Transition Utilities
|
||||||
============================================================================ */
|
============================================================================ */
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
[data-brand="dt"] .card {
|
[data-brand="dt"] .card {
|
||||||
--dt-card-progress: 0;
|
--dt-card-progress: 0;
|
||||||
--_card-pad: var(--space-6);
|
--_card-pad: var(--space-6);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
isolation: isolate;
|
isolation: isolate;
|
||||||
background: var(--dt-card-color, var(--color-primary));
|
background: var(--dt-card-color, var(--color-primary));
|
||||||
@@ -44,17 +46,21 @@
|
|||||||
z-index: -1;
|
z-index: -1;
|
||||||
/* Inner clip-path: left edge at bevel-sm to create progress bar zone.
|
/* Inner clip-path: left edge at bevel-sm to create progress bar zone.
|
||||||
The progress bar occupies x=0 to x=bevel-sm with its own borders.
|
The progress bar occupies x=0 to x=bevel-sm with its own borders.
|
||||||
Inner surface starts at bevel-sm, so the left frame is wider than 3px. */
|
Inner surface starts at bevel-sm, so the left frame is wider than 3px.
|
||||||
|
Diagonal vertices use bevel-md + 3px (not bevel-md alone) so the
|
||||||
|
perpendicular border width on the 45-degree diagonal visually matches
|
||||||
|
the 3px straight edges despite sub-pixel antialiasing. */
|
||||||
clip-path: polygon(var(--bevel-sm) 3px,
|
clip-path: polygon(var(--bevel-sm) 3px,
|
||||||
calc(100% - 3px) 3px,
|
calc(100% - 3px) 3px,
|
||||||
calc(100% - 3px) calc(100% - var(--bevel-md)),
|
calc(100% - 3px) calc(100% - var(--bevel-md) - 3px),
|
||||||
calc(100% - var(--bevel-md)) calc(100% - 3px),
|
calc(100% - var(--bevel-md) - 3px) calc(100% - 3px),
|
||||||
var(--bevel-sm) calc(100% - 3px));
|
var(--bevel-sm) calc(100% - 3px));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Card Progress Bar — structural fill area on the left edge
|
/* Card Progress Bar — structural fill area on the left edge
|
||||||
Sits between the 3px frame borders, follows bottom-left bevel diagonal.
|
Sits between the 3px frame borders, follows bottom-left bevel diagonal.
|
||||||
Gradient fills from bottom (accent) to top (surface) based on --dt-card-progress.
|
Gradient fills top-to-bottom based on --dt-card-progress so the card
|
||||||
|
visually "opens" as the bar descends.
|
||||||
Filled portion: fully opaque accent color. Unfilled portion: semi-transparent surface.
|
Filled portion: fully opaque accent color. Unfilled portion: semi-transparent surface.
|
||||||
At 0%: all semi-transparent surface. At 100%: all opaque accent.
|
At 0%: all semi-transparent surface. At 100%: all opaque accent.
|
||||||
Override --dt-progress-empty-opacity to control unfilled portion transparency. */
|
Override --dt-progress-empty-opacity to control unfilled portion transparency. */
|
||||||
@@ -70,7 +76,7 @@
|
|||||||
calc(var(--bevel-sm) - 3px) calc(100% - 6px),
|
calc(var(--bevel-sm) - 3px) calc(100% - 6px),
|
||||||
3px calc(100% - var(--bevel-sm)));
|
3px calc(100% - var(--bevel-sm)));
|
||||||
|
|
||||||
background: linear-gradient(to top,
|
background: linear-gradient(to bottom,
|
||||||
rgba(var(--dt-card-color-rgb, var(--color-primary-rgb)), 1) calc(var(--dt-card-progress, 0) * 1%),
|
rgba(var(--dt-card-color-rgb, var(--color-primary-rgb)), 1) calc(var(--dt-card-progress, 0) * 1%),
|
||||||
rgba(var(--color-surface-rgb),
|
rgba(var(--color-surface-rgb),
|
||||||
var(--dt-progress-empty-opacity, 0.6)) calc(var(--dt-card-progress, 0) * 1%));
|
var(--dt-progress-empty-opacity, 0.6)) calc(var(--dt-card-progress, 0) * 1%));
|
||||||
@@ -79,12 +85,32 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Card children — ensure body content renders above the progress bar (::after z-index:1) */
|
/* Card children — ensure body content renders above the progress bar (::after z-index:1) */
|
||||||
[data-brand="dt"] .dt-bevel-card > *:not(.dt-card-badge),
|
[data-brand="dt"] .dt-bevel-card > *:not(.dt-badge-bottom-right):not(.dt-card-badge),
|
||||||
[data-brand="dt"] .card > *:not(.dt-card-badge) {
|
[data-brand="dt"] .card > *:not(.dt-badge-bottom-right):not(.dt-card-badge) {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Card Grow — stretch to fill available space in flex/grid containers.
|
||||||
|
By default the body (content area) grows. Use .dt-card-grow-title to
|
||||||
|
grow the header instead. */
|
||||||
|
.dt-card-grow {
|
||||||
|
flex: 1 1 0%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dt-card-grow > .card-title {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dt-card-grow:not(.dt-card-grow-title) > :last-child {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dt-card-grow.dt-card-grow-title > .card-title {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
/* Card Header — storefront .card-header pattern
|
/* Card Header — storefront .card-header pattern
|
||||||
Title starts at bevel-sm (past the progress bar zone) and extends to the right edge.
|
Title starts at bevel-sm (past the progress bar zone) and extends to the right edge.
|
||||||
Below it, the ::before dark surface shows through for the body area. */
|
Below it, the ::before dark surface shows through for the body area. */
|
||||||
@@ -102,7 +128,6 @@
|
|||||||
background: var(--dt-card-color, var(--color-primary));
|
background: var(--dt-card-color, var(--color-primary));
|
||||||
color: var(--color-bg);
|
color: var(--color-bg);
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -125,17 +150,16 @@
|
|||||||
background: var(--dt-card-color, var(--color-primary));
|
background: var(--dt-card-color, var(--color-primary));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bottom-right bevel on card body content (img/video). Clip-path matches
|
/* Bottom-right bevel on card body content. Clip-path matches
|
||||||
the card's inner surface bevel so the 3px structural border is visible
|
the card's inner surface bevel so the 3px structural border is visible
|
||||||
at the diagonal corner. Badge is not clipped (it's a sibling, not img). */
|
at the diagonal corner. Badge overlays are excluded. */
|
||||||
[data-brand="dt"] .card-body-flush > img,
|
[data-brand="dt"] .card-body-flush > *:not(.dt-badge-bottom-right):not(.dt-card-badge) {
|
||||||
[data-brand="dt"] .card-body-flush > video {
|
|
||||||
display: block;
|
display: block;
|
||||||
clip-path: polygon(
|
clip-path: polygon(
|
||||||
0% 0%,
|
0% 0%,
|
||||||
100% 0%,
|
100% 0%,
|
||||||
100% calc(100% - var(--bevel-md) + 3px),
|
100% calc(100% - var(--bevel-md)),
|
||||||
calc(100% - var(--bevel-md) + 3px) 100%,
|
calc(100% - var(--bevel-md)) 100%,
|
||||||
0% 100%);
|
0% 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,6 +296,37 @@
|
|||||||
color: var(--color-bg);
|
color: var(--color-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mode-variant badges — bridge .mode-* vars to badge color vars */
|
||||||
|
[data-brand="dt"] .badge.mode-normal {
|
||||||
|
--badge-border-color: var(--mode-normal);
|
||||||
|
--badge-fill: var(--mode-normal);
|
||||||
|
color: var(--color-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-brand="dt"] .badge.mode-emphasis {
|
||||||
|
--badge-border-color: var(--mode-emphasis);
|
||||||
|
--badge-fill: var(--mode-emphasis);
|
||||||
|
color: var(--color-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-brand="dt"] .badge.mode-warning {
|
||||||
|
--badge-border-color: var(--mode-warning);
|
||||||
|
--badge-fill: var(--mode-warning);
|
||||||
|
color: var(--color-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-brand="dt"] .badge.mode-success {
|
||||||
|
--badge-border-color: var(--mode-success);
|
||||||
|
--badge-fill: var(--mode-success);
|
||||||
|
color: var(--color-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-brand="dt"] .badge.mode-other {
|
||||||
|
--badge-border-color: var(--mode-other);
|
||||||
|
--badge-fill: var(--mode-other);
|
||||||
|
color: var(--color-bg);
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================================================
|
/* ============================================================================
|
||||||
Opt-in Bevel Utility Classes
|
Opt-in Bevel Utility Classes
|
||||||
Use these for custom elements that need bevel shapes
|
Use these for custom elements that need bevel shapes
|
||||||
@@ -326,7 +381,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dt-bevel-media > img,
|
.dt-bevel-media > img,
|
||||||
.dt-bevel-media > video {
|
.dt-bevel-media > video,
|
||||||
|
.dt-bevel-media > model-viewer,
|
||||||
|
.dt-bevel-media > iframe {
|
||||||
display: block;
|
display: block;
|
||||||
clip-path: polygon(
|
clip-path: polygon(
|
||||||
calc(var(--bevel-md) + 1px) 3px,
|
calc(var(--bevel-md) + 1px) 3px,
|
||||||
@@ -337,6 +394,14 @@
|
|||||||
3px calc(var(--bevel-md) + 1px));
|
3px calc(var(--bevel-md) + 1px));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Video letterboxing — respect natural aspect ratio with black bars */
|
||||||
|
.dt-bevel-media > video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
/* Media frame placeholder — shows when no image/video is present.
|
/* Media frame placeholder — shows when no image/video is present.
|
||||||
Surface-colored background is already provided by ::before.
|
Surface-colored background is already provided by ::before.
|
||||||
This adds centered "MISSING MEDIA" text via ::after. */
|
This adds centered "MISSING MEDIA" text via ::after. */
|
||||||
@@ -352,7 +417,6 @@
|
|||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.1em;
|
||||||
text-transform: uppercase;
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,7 +433,6 @@
|
|||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.1em;
|
||||||
text-transform: uppercase;
|
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,14 +513,13 @@
|
|||||||
/* ============================================================================
|
/* ============================================================================
|
||||||
Card Color Modes — per-instance color overrides
|
Card Color Modes — per-instance color overrides
|
||||||
Sets --dt-card-color, --dt-card-color-selected, --dt-card-color-rgb,
|
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
|
Source: dt-shopify-storefront .mode-* classes
|
||||||
============================================================================ */
|
============================================================================ */
|
||||||
[data-brand="dt"] .mode-normal {
|
[data-brand="dt"] .mode-normal {
|
||||||
--dt-card-color: var(--mode-normal);
|
--dt-card-color: var(--mode-normal);
|
||||||
--dt-card-color-rgb: var(--mode-normal-rgb);
|
--dt-card-color-rgb: var(--mode-normal-rgb);
|
||||||
--dt-card-color-selected: var(--mode-normal-selected);
|
--dt-card-color-selected: var(--mode-normal-selected);
|
||||||
--dt-glow-color: var(--mode-normal);
|
|
||||||
--accent-mode: var(--mode-emphasis);
|
--accent-mode: var(--mode-emphasis);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,7 +527,6 @@
|
|||||||
--dt-card-color: var(--mode-emphasis);
|
--dt-card-color: var(--mode-emphasis);
|
||||||
--dt-card-color-rgb: var(--mode-emphasis-rgb);
|
--dt-card-color-rgb: var(--mode-emphasis-rgb);
|
||||||
--dt-card-color-selected: var(--mode-emphasis-selected);
|
--dt-card-color-selected: var(--mode-emphasis-selected);
|
||||||
--dt-glow-color: var(--mode-emphasis);
|
|
||||||
--accent-mode: var(--mode-normal);
|
--accent-mode: var(--mode-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,7 +534,6 @@
|
|||||||
--dt-card-color: var(--mode-warning);
|
--dt-card-color: var(--mode-warning);
|
||||||
--dt-card-color-rgb: var(--mode-warning-rgb);
|
--dt-card-color-rgb: var(--mode-warning-rgb);
|
||||||
--dt-card-color-selected: var(--mode-warning-selected);
|
--dt-card-color-selected: var(--mode-warning-selected);
|
||||||
--dt-glow-color: var(--mode-warning);
|
|
||||||
--accent-mode: var(--mode-emphasis);
|
--accent-mode: var(--mode-emphasis);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,7 +541,6 @@
|
|||||||
--dt-card-color: var(--mode-success);
|
--dt-card-color: var(--mode-success);
|
||||||
--dt-card-color-rgb: var(--mode-success-rgb);
|
--dt-card-color-rgb: var(--mode-success-rgb);
|
||||||
--dt-card-color-selected: var(--mode-success-selected);
|
--dt-card-color-selected: var(--mode-success-selected);
|
||||||
--dt-glow-color: var(--mode-success);
|
|
||||||
--accent-mode: var(--mode-emphasis);
|
--accent-mode: var(--mode-emphasis);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -489,7 +548,6 @@
|
|||||||
--dt-card-color: var(--mode-other);
|
--dt-card-color: var(--mode-other);
|
||||||
--dt-card-color-rgb: var(--mode-other-rgb);
|
--dt-card-color-rgb: var(--mode-other-rgb);
|
||||||
--dt-card-color-selected: var(--mode-other-selected);
|
--dt-card-color-selected: var(--mode-other-selected);
|
||||||
--dt-glow-color: var(--mode-other);
|
|
||||||
--accent-mode: var(--mode-normal);
|
--accent-mode: var(--mode-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,15 +555,17 @@
|
|||||||
/* ============================================================================
|
/* ============================================================================
|
||||||
Card Badge — bottom-right product type chip (LAB, BUNDLE, etc.)
|
Card Badge — bottom-right product type chip (LAB, BUNDLE, etc.)
|
||||||
Source: dt-shopify-storefront CyberProductCard LabChip / BundleChip
|
Source: dt-shopify-storefront CyberProductCard LabChip / BundleChip
|
||||||
Plain flat rectangle. Inherits card color via --dt-card-color.
|
Positioned at card level so the card's clip-path bevels the badge corner.
|
||||||
|
Consumer provides badge content/styling; card handles placement.
|
||||||
============================================================================ */
|
============================================================================ */
|
||||||
/* Badge parent — provides position context for absolutely-positioned badges.
|
|
||||||
Use on the image container inside a card, or on any element that wraps content + badge. */
|
/* Legacy: standalone badge parent for non-card contexts */
|
||||||
.dt-badge-parent {
|
.dt-badge-parent {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dt-card-badge {
|
/* Legacy: standalone badge positioning for non-card contexts */
|
||||||
|
.dt-badge-bottom-right {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 8px;
|
bottom: 8px;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
@@ -513,19 +573,32 @@
|
|||||||
padding: 2px calc(8px + 0.2rem) 2px 8px;
|
padding: 2px calc(8px + 0.2rem) 2px 8px;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.1em;
|
||||||
background-color: var(--dt-card-color, var(--color-primary));
|
background-color: var(--dt-card-color, var(--color-primary));
|
||||||
color: var(--color-bg);
|
color: var(--color-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Inside card body-flush, offset badge position to compensate for the
|
/* Card-level badge slot — positioned so the card's bevel clip-path
|
||||||
3px margin from the card edge. This makes the badge position relative
|
clips the badge's bottom-right corner at 45 degrees.
|
||||||
to the card's bevel corner match the media frame's placement exactly
|
Values tuned so the badge is mostly visible with just the corner clipped. */
|
||||||
(8px from card edge in both cases). */
|
.card > .dt-card-badge,
|
||||||
.card-body-flush > .dt-card-badge {
|
.dt-bevel-card > .dt-card-badge {
|
||||||
bottom: 5px;
|
position: absolute;
|
||||||
right: 5px;
|
bottom: calc(var(--bevel-md) * 0.25);
|
||||||
|
right: calc(var(--bevel-md) * -0.15);
|
||||||
|
z-index: 4;
|
||||||
|
background: var(--dt-card-color, var(--color-primary));
|
||||||
|
color: var(--color-bg);
|
||||||
|
padding: 0.35em 2em 0.35em 1em;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-brand="classic"] .card > .dt-card-badge,
|
||||||
|
[data-brand="classic"] .dt-bevel-card > .dt-card-badge {
|
||||||
|
border-radius: 0.375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================================
|
/* ============================================================================
|
||||||
@@ -547,7 +620,6 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
text-transform: uppercase;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color var(--transition-fast),
|
transition: background-color var(--transition-fast),
|
||||||
color var(--transition-fast),
|
color var(--transition-fast),
|
||||||
|
|||||||
@@ -2,55 +2,184 @@
|
|||||||
/* Source: dt-shopify-storefront UseCaseLegend component */
|
/* 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 {
|
.dt-feature-legend {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- Header bar ---- */
|
||||||
.dt-feature-legend-header {
|
.dt-feature-legend-header {
|
||||||
padding: 8px 16px;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
background: var(--dt-card-color, var(--color-primary));
|
background: var(--dt-card-color, var(--color-primary));
|
||||||
color: var(--color-bg);
|
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-weight: 700;
|
||||||
|
font-size: 1.75rem;
|
||||||
|
line-height: 1.1;
|
||||||
|
color: var(--color-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dt-feature-legend-detail {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
text-transform: uppercase;
|
margin-top: 2px;
|
||||||
letter-spacing: 0.05em;
|
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 {
|
.dt-feature-legend-grid {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
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 {
|
.dt-feature-legend-item {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
justify-content: center;
|
||||||
padding: 8px 4px;
|
padding: 8px 4px;
|
||||||
flex: 0 0 20%; /* 5 columns */
|
background: var(--color-bg);
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- Icon ---- */
|
||||||
.dt-feature-legend-icon {
|
.dt-feature-legend-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
font-size: 42px;
|
font-size: 42px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
color: var(--dt-feature-color, var(--color-primary));
|
color: var(--dt-feature-color, var(--color-primary));
|
||||||
|
transition: opacity 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dt-feature-legend-label {
|
.dt-feature-legend-item:hover .dt-feature-legend-icon {
|
||||||
font-size: 0.75rem;
|
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;
|
font-weight: 600;
|
||||||
color: var(--dt-feature-color, var(--color-text-primary));
|
white-space: nowrap;
|
||||||
text-align: center;
|
padding: 4px 2px;
|
||||||
line-height: 1.2;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Feature state color classes */
|
/* ---- Feature state color classes ---- */
|
||||||
.dt-feature-supported { --dt-feature-color: var(--mode-normal, var(--color-primary)); }
|
.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-disabled { --dt-feature-color: var(--mode-emphasis, var(--color-secondary)); }
|
||||||
.dt-feature-unsupported { --dt-feature-color: var(--mode-warning, var(--color-error)); }
|
.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)); }
|
||||||
|
|||||||
@@ -143,49 +143,38 @@
|
|||||||
/* ============================================================================
|
/* ============================================================================
|
||||||
Radio Button — Hexagonal indicator
|
Radio Button — Hexagonal indicator
|
||||||
Matches RN DTRadioGroup: outlined hexagon with inner filled dot on check.
|
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"] {
|
[data-brand="dt"] .dt-radio-hex {
|
||||||
appearance: none;
|
position: relative;
|
||||||
-webkit-appearance: none;
|
display: inline-block;
|
||||||
display: grid;
|
|
||||||
place-content: center;
|
|
||||||
width: 22px;
|
width: 22px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin: 0;
|
|
||||||
border: none;
|
|
||||||
background: var(--color-primary);
|
background: var(--color-primary);
|
||||||
cursor: pointer;
|
|
||||||
clip-path: polygon(
|
clip-path: polygon(
|
||||||
50% 0%, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%
|
50% 0%, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%
|
||||||
);
|
);
|
||||||
transition: background var(--transition-fast);
|
transition: background var(--transition-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Inner surface — creates the outlined hexagon look (accent border + transparent center) */
|
/* Inner surface — outlined hexagon look (accent border + dark center) */
|
||||||
[data-brand="dt"] input[type="radio"]::before {
|
[data-brand="dt"] .dt-radio-hex-inner {
|
||||||
content: '';
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 2px;
|
inset: 2px;
|
||||||
background: var(--color-bg);
|
background: var(--color-bg);
|
||||||
clip-path: polygon(
|
clip-path: polygon(
|
||||||
50% 0%, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%
|
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) */
|
/* Checked: inner dot fills with accent color (smaller inset) */
|
||||||
[data-brand="dt"] input[type="radio"]:checked::before {
|
[data-brand="dt"] .dt-radio-hex.checked .dt-radio-hex-inner {
|
||||||
inset: 5px;
|
inset: 5px;
|
||||||
background: var(--color-primary);
|
background: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-brand="dt"] input[type="radio"]:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Radio group option container */
|
/* Radio group option container */
|
||||||
.dt-radio-option {
|
.dt-radio-option {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -262,7 +251,6 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-transform: uppercase;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
@@ -362,7 +350,6 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
text-transform: uppercase;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all var(--transition-fast);
|
transition: all var(--transition-fast);
|
||||||
}
|
}
|
||||||
@@ -403,11 +390,6 @@
|
|||||||
animation: dt-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
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 */
|
/* Classic brand: rounded, thinner borders */
|
||||||
[data-brand="classic"] .dt-menu-item {
|
[data-brand="classic"] .dt-menu-item {
|
||||||
border-top-width: 2px;
|
border-top-width: 2px;
|
||||||
@@ -436,7 +418,6 @@
|
|||||||
border-top: 5px solid var(--color-primary);
|
border-top: 5px solid var(--color-primary);
|
||||||
border-bottom: 1px solid rgba(var(--color-primary-rgb), 0.2);
|
border-bottom: 1px solid rgba(var(--color-primary-rgb), 0.2);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
|
|
||||||
/* Component styles — DT visual identity */
|
/* Component styles — DT visual identity */
|
||||||
@import './components/bevels.css';
|
@import './components/bevels.css';
|
||||||
@import './components/glows.css';
|
|
||||||
@import './components/forms-dt.css';
|
@import './components/forms-dt.css';
|
||||||
@import './components/animations.css';
|
@import './components/animations.css';
|
||||||
@import './components/scrollbar.css';
|
@import './components/scrollbar.css';
|
||||||
|
|||||||
Reference in New Issue
Block a user