Compare commits

...

6 Commits

Author SHA1 Message Date
michael
0cfc945960 Fixed type in label 2026-05-05 14:16:32 -07:00
michael
77c6a5a3bf feat(DTGallery): add optional activeIndex prop for external control
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 12:42:10 -07:00
michael
27824ae189 chore(hex-background): bump 0.2.1, changelog for animation fix 2026-04-13 16:28:59 -07:00
michael
1f67ebf48a feat(hex-background): pass animationDuration through HexGridBackground 2026-04-13 16:26:17 -07:00
michael
5112e5c057 fix(hex-background): replace lerp with duration-based easing
Hexagons now interpolate via smoothstep over a fixed duration instead of
frame-rate-dependent lerp. Prevents height convergence to mean over time.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 16:23:28 -07:00
michael
1be0070abf chore: version packages (web 0.4.1, react 2.0.1, react-native 0.4.1)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 11:26:57 -07:00
12 changed files with 90 additions and 24 deletions

View File

@@ -1,5 +1,13 @@
# @dangerousthings/hex-background # @dangerousthings/hex-background
## 0.2.1
### Fixed
- Animation no longer degrades to noise over time — replaced frame-rate-dependent lerp with duration-based smoothstep easing
### Added
- `animationDuration` prop (default 1200ms) controls how long each hexagon takes to reach its target height
## 0.2.0 ## 0.2.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@dangerousthings/hex-background", "name": "@dangerousthings/hex-background",
"version": "0.2.0", "version": "0.2.1",
"description": "3D hexagon grid background for the Dangerous Things design system", "description": "3D hexagon grid background for the Dangerous Things design system",
"license": "MIT", "license": "MIT",
"author": { "author": {

View File

@@ -13,7 +13,6 @@ import {
Shape, Shape,
ExtrudeGeometry, ExtrudeGeometry,
MeshStandardMaterial, MeshStandardMaterial,
MathUtils,
} from 'three'; } from 'three';
export interface HexGridProps { export interface HexGridProps {
@@ -29,6 +28,13 @@ export interface HexGridProps {
maxHeight?: number; maxHeight?: number;
/** Interval (ms) between height target changes @default 1500 */ /** Interval (ms) between height target changes @default 1500 */
animationInterval?: number; animationInterval?: number;
/** Duration (ms) for each hex to reach its target @default 1200 */
animationDuration?: number;
}
/** Hermite smoothstep: 3t² - 2t³ */
function smoothstep(t: number): number {
return t * t * (3 - 2 * t);
} }
function computeHexPositions( function computeHexPositions(
@@ -62,6 +68,7 @@ export function HexGrid({
margin = 0.05, margin = 0.05,
maxHeight = 3, maxHeight = 3,
animationInterval = 1500, animationInterval = 1500,
animationDuration = 1200,
}: HexGridProps) { }: HexGridProps) {
const meshRef = useRef<InstancedMesh>(null); const meshRef = useRef<InstancedMesh>(null);
@@ -70,11 +77,11 @@ export function HexGrid({
[rows, cols, hexRadius, extrudeHeight, margin], [rows, cols, hexRadius, extrudeHeight, margin],
); );
const targetHeightsRef = useRef( const animStateRef = useRef(
positions.map(() => 1 + Math.random() * maxHeight), positions.map(() => {
); const h = 1 + Math.random() * maxHeight;
const currentHeightsRef = useRef( return {startHeight: h, targetHeight: h, startTime: 0};
positions.map(() => 1 + Math.random() * maxHeight), }),
); );
// Reuse dummy object for matrix updates — 15-20% FPS improvement // Reuse dummy object for matrix updates — 15-20% FPS improvement
@@ -101,25 +108,41 @@ export function HexGrid({
// Regenerate target heights at interval // Regenerate target heights at interval
useEffect(() => { useEffect(() => {
const interval = setInterval(() => { const interval = setInterval(() => {
targetHeightsRef.current = positions.map( const now = performance.now();
() => 0.5 + Math.random() * maxHeight, animStateRef.current = animStateRef.current.map((state) => {
// Compute where the hex currently is (same easing as useFrame)
const elapsed = Math.min(
(now - state.startTime) / animationDuration,
1,
); );
const t = smoothstep(elapsed);
const currentHeight =
state.startHeight + (state.targetHeight - state.startHeight) * t;
return {
startHeight: currentHeight,
targetHeight: 0.5 + Math.random() * maxHeight,
startTime: now,
};
});
}, animationInterval); }, animationInterval);
return () => clearInterval(interval); return () => clearInterval(interval);
}, [positions, maxHeight, animationInterval]); }, [positions.length, maxHeight, animationInterval, animationDuration]);
// Animate per frame: lerp current heights toward targets // Animate per frame: time-based smoothstep easing
useFrame((_state, delta) => { useFrame(() => {
if (!meshRef.current) return; if (!meshRef.current) return;
const dummy = dummyRef.current; const dummy = dummyRef.current;
const now = performance.now();
positions.forEach(({x, z}, i) => { positions.forEach(({x, z}, i) => {
currentHeightsRef.current[i] = MathUtils.lerp( const state = animStateRef.current[i];
currentHeightsRef.current[i], const elapsed = Math.min(
targetHeightsRef.current[i], (now - state.startTime) / animationDuration,
delta * 0.5, 1,
); );
const height = currentHeightsRef.current[i]; const t = smoothstep(elapsed);
const height =
state.startHeight + (state.targetHeight - state.startHeight) * t;
dummy.position.set(x, height / 2, z); dummy.position.set(x, height / 2, z);
dummy.scale.set(1, height, 1); dummy.scale.set(1, height, 1);

View File

@@ -22,6 +22,8 @@ export interface HexGridBackgroundProps {
maxHeight?: number; maxHeight?: number;
/** Height target refresh interval in ms @default 1500 */ /** Height target refresh interval in ms @default 1500 */
animationInterval?: number; animationInterval?: number;
/** Duration (ms) for each hex to ease to target @default 1200 */
animationDuration?: number;
/** Camera orbital speed @default 0.02 */ /** Camera orbital speed @default 0.02 */
cameraSpeed?: number; cameraSpeed?: number;
/** Camera orbital radius @default 8 */ /** Camera orbital radius @default 8 */
@@ -62,6 +64,7 @@ export function HexGridBackground({
margin = 0.05, margin = 0.05,
maxHeight = 3, maxHeight = 3,
animationInterval = 1500, animationInterval = 1500,
animationDuration = 1200,
cameraSpeed = 0.02, cameraSpeed = 0.02,
cameraRadius = 8, cameraRadius = 8,
fov = 40, fov = 40,
@@ -134,6 +137,7 @@ export function HexGridBackground({
margin={margin} margin={margin}
maxHeight={maxHeight} maxHeight={maxHeight}
animationInterval={animationInterval} animationInterval={animationInterval}
animationDuration={animationDuration}
/> />
</Suspense> </Suspense>
</Canvas> </Canvas>

View File

@@ -1,5 +1,11 @@
# @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 ## 0.4.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@dangerousthings/react-native", "name": "@dangerousthings/react-native",
"version": "0.4.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": {

View File

@@ -1,5 +1,13 @@
# @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 ## 2.0.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@dangerousthings/react", "name": "@dangerousthings/react",
"version": "2.0.0", "version": "2.1.0",
"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": {

View File

@@ -41,6 +41,9 @@ export type DTGalleryItem =
interface DTGalleryProps { interface DTGalleryProps {
items: DTGalleryItem[]; items: DTGalleryItem[];
/** Externally-controlled active index. When this value changes, the gallery
* jumps to the corresponding item. Thumbnail clicks still work normally. */
activeIndex?: number;
/** Color variant @default 'normal' */ /** Color variant @default 'normal' */
variant?: DTVariant; variant?: DTVariant;
/** Aspect ratio (width / height) for the display area @default 1 */ /** Aspect ratio (width / height) for the display area @default 1 */
@@ -85,12 +88,20 @@ const fillStyle: CSSProperties = {
export function DTGallery({ export function DTGallery({
items, items,
activeIndex: activeIndexProp,
variant = 'normal', variant = 'normal',
aspectRatio = 1, aspectRatio = 1,
className, className,
style, style,
}: DTGalleryProps) { }: DTGalleryProps) {
const [activeIndex, setActiveIndex] = useState(0); const [activeIndex, setActiveIndex] = useState(activeIndexProp ?? 0);
// Sync external activeIndex prop to internal state when it changes
useEffect(() => {
if (activeIndexProp != null && activeIndexProp >= 0 && activeIndexProp < items.length) {
setActiveIndex(activeIndexProp);
}
}, [activeIndexProp, items.length]);
const hasModel = items.some((i) => i.type === 'model3d'); const hasModel = items.some((i) => i.type === 'model3d');
useModelViewerScript(hasModel); useModelViewerScript(hasModel);

View File

@@ -25,7 +25,7 @@ export function DTLabel({
}: DTLabelProps) { }: DTLabelProps) {
return ( return (
<span <span
className={cx('badge', 'badge-mode', getVariantClass(variant), className)} className={cx('badge', 'p-3', 'badge-mode', getVariantClass(variant), className)}
style={style}> style={style}>
{children} {children}
</span> </span>

View File

@@ -1,5 +1,11 @@
# @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 ## 0.4.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@dangerousthings/web", "name": "@dangerousthings/web",
"version": "0.4.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": {