From 708183a591b44243930411ae5c6a5b454decba0c Mon Sep 17 00:00:00 2001 From: Kris Baumgartner Date: Fri, 31 Jul 2026 12:56:15 -0400 Subject: [PATCH 1/5] =?UTF-8?q?trails:=20v9.7.0=20release=20demo=20?= =?UTF-8?q?=E2=80=94=20extruded=209.7.0=20swarm=20with=20pink=20hero?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Temporary demo advertising @react-three/fiber 9.7.0 (dep bumped to the real release). The pmndrs logos become a physics swarm of extruded "9.7.0" blocks (shared centered TextGeometry, helvetiker bold) pulled toward a point behind an immovable pink hero "9.7.0" they bounce off. Text3D+Center were dropped after drei's
mis-measured: Box3. setFromObject(precise) reads the raw position attribute of the fat-line (its Line2 instancing template quad, 2x3 units), inflating the box and offsetting the word by roughly a quarter width. Co-Authored-By: Claude Fable 5 --- demos/trails/package.json | 2 +- demos/trails/src/App.jsx | 62 ++++++++++----- demos/trails/src/index.jsx | 4 +- pnpm-lock.yaml | 157 +++++++++++++++++++++++++++++++------ pnpm-workspace.yaml | 1 + 5 files changed, 182 insertions(+), 44 deletions(-) diff --git a/demos/trails/package.json b/demos/trails/package.json index 0889eb0b..36188152 100644 --- a/demos/trails/package.json +++ b/demos/trails/package.json @@ -5,7 +5,7 @@ "@pmndrs/branding": "^0.0.8", "@react-three/cannon": "^6.6.0", "@react-three/drei": "10.7.7", - "@react-three/fiber": "9.6.1", + "@react-three/fiber": "9.7.0", "lamina": "1.2.2", "react": "19.2.8", "react-dom": "19.2.8", diff --git a/demos/trails/src/App.jsx b/demos/trails/src/App.jsx index bf197b94..a3aedb02 100644 --- a/demos/trails/src/App.jsx +++ b/demos/trails/src/App.jsx @@ -4,46 +4,70 @@ import { Canvas, useThree, useFrame } from '@react-three/fiber' import { useGLTF, Edges, Trail } from '@react-three/drei' import { Physics, useCompoundBody } from '@react-three/cannon' import { LayerMaterial, Depth, Fresnel } from 'lamina' +import { FontLoader, TextGeometry } from 'three-stdlib' -import pmndrsModel from './pmndrs.glb?url' +import boldFont from 'three/examples/fonts/helvetiker_bold.typeface.json' import cursorModel from './cursor.glb?url' +// One centered, extruded "9.7.0" shared by every block in the scene +const font = new FontLoader().parse(boldFont) +const text = new TextGeometry('9.7.0', { font, size: 0.7, height: 0.35, curveSegments: 16, letterSpacing: 0.02 }) +text.center() + const vec = new THREE.Vector3() +// The swarm gathers slightly behind the hero so it frames it instead of covering it +const attractor = new THREE.Vector3(0, 0, -1) const white = new THREE.MeshBasicMaterial({ color: '#fefefe', toneMapped: false }) -const black = new THREE.MeshBasicMaterial({ color: 'black', toneMapped: false }) +const pink = new THREE.MeshBasicMaterial({ color: '#ff0080', toneMapped: false }) +// Accent blocks cycle through the cursor-gradient palette +const accents = ['#ff0080', '#f7b955', '#5786f5'].map((color) => new THREE.MeshBasicMaterial({ color, toneMapped: false })) -export const App = ({ amount = 12 }) => ( +export const App = ({ amount = 11 }) => ( + {Array.from({ length: amount }, (_, i) => ( - + ))} ) -function Pmndrs(props) { - const { nodes } = useGLTF(pmndrsModel) - const [ref, api] = useCompoundBody(() => ({ - ...props, - shapes: [ - { type: 'Box', args: [0.65, 0.65, 0.5], position: [0.18, 0.18, 0] }, - { type: 'Box', args: [0.3, 0.3, 0.5], position: [-0.35, 0, 0] }, - { type: 'Box', args: [0.3, 0.3, 0.5], position: [0, -0.35, 0] } - ] - })) - // Pull every logo back towards the center of the scene - useFrame(() => api.applyForce(vec.setFromMatrixPosition(ref.current.matrix).normalize().multiplyScalar(-40).toArray(), [0, 0, 0])) +function Block({ scale = 1, material = white, edge = 'black', ...props }) { return ( - - - + + + ) } +// One box approximating the extruded "9.7.0" word, baked to the visual scale +const wordBox = (scale) => [{ type: 'Box', args: [2.2 * scale, 0.55 * scale, 0.35 * scale], position: [0, 0, 0] }] + +// Immovable billboard version number the swarm and cursor bounce off +function Hero({ scale = 1.2 }) { + const [ref] = useCompoundBody(() => ({ mass: 0, position: [0, 0, 1.5], shapes: wordBox(scale) })) + return +} + +function Version({ scale = 1, material = white, ...props }) { + const [ref, api] = useCompoundBody(() => ({ mass: 4 * scale, ...props, shapes: wordBox(scale) })) + // Pull every block back towards the gather point behind the hero + useFrame(() => api.applyForce(vec.setFromMatrixPosition(ref.current.matrix).sub(attractor).normalize().multiplyScalar(-40 * scale).toArray(), [0, 0, 0])) + return +} + function Cursor({ speed = 10, gradient = 0.7, ...props }) { const { nodes } = useGLTF(cursorModel) const viewport = useThree((state) => state.viewport) diff --git a/demos/trails/src/index.jsx b/demos/trails/src/index.jsx index 4dda878c..e3121f25 100644 --- a/demos/trails/src/index.jsx +++ b/demos/trails/src/index.jsx @@ -6,7 +6,7 @@ import { App } from './App' createRoot(document.getElementById('root')).render( <> -
trails —
+
@react-three/fiber v9.7.0 —
dev collective -
18/03/2022
+
31/07/2026
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0ff3af0d..6d0f7e96 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6578,16 +6578,16 @@ importers: version: 0.0.8(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@react-three/cannon': specifier: ^6.6.0 - version: 6.6.0(@react-three/fiber@9.6.1(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0))(react@19.2.8)(three@0.165.0)(typescript@7.0.2) + version: 6.6.0(@react-three/fiber@9.7.0(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0))(react@19.2.8)(three@0.165.0)(typescript@7.0.2) '@react-three/drei': specifier: 10.7.7 - version: 10.7.7(@react-three/fiber@9.6.1(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0))(@types/react@19.2.17)(@types/three@0.165.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0) + version: 10.7.7(@react-three/fiber@9.7.0(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0))(@types/react@19.2.17)(@types/three@0.165.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0) '@react-three/fiber': - specifier: 9.6.1 - version: 9.6.1(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0) + specifier: 9.7.0 + version: 9.7.0(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0) lamina: specifier: 1.2.2 - version: 1.2.2(@react-three/fiber@9.6.1(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0))(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0) + version: 1.2.2(@react-three/fiber@9.7.0(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0))(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0) react: specifier: 19.2.8 version: 19.2.8 @@ -9224,6 +9224,31 @@ packages: react-native: optional: true + '@react-three/fiber@9.7.0': + resolution: {integrity: sha512-EWm9FwcaOZQu/ExFW5rggoCMM1NJet5YbxVxKaOE+KSncrjU0Wx7017qSyGFvupviK89nMYGCWU3BIK4dI1clw==} + 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 + '@react-three/flex@1.0.1': resolution: {integrity: sha512-m1zcuE0BWyUP6ASX09SGYZWHkyMS9GVcP4WGXjdSdxWi228D0p8hti9nHlnv+fpZvQO8v+Qz4EXmrf302mEzXQ==} peerDependencies: @@ -14466,13 +14491,13 @@ snapshots: '@radix-ui/number@1.0.0': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@radix-ui/number@1.1.3': {} '@radix-ui/primitive@1.0.0': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@radix-ui/primitive@1.1.3': {} @@ -14610,7 +14635,7 @@ snapshots: '@radix-ui/react-compose-refs@1.0.0(react@19.2.8)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 react: 19.2.8 '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.17)(react@19.2.8)': @@ -14640,7 +14665,7 @@ snapshots: '@radix-ui/react-context@1.0.0(react@19.2.8)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 react: 19.2.8 '@radix-ui/react-context@1.1.2(@types/react@19.2.17)(react@19.2.8)': @@ -14680,7 +14705,7 @@ snapshots: '@radix-ui/react-direction@1.0.0(react@19.2.8)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 react: 19.2.8 '@radix-ui/react-direction@1.1.4(@types/react@19.2.17)(react@19.2.8)': @@ -15003,7 +15028,7 @@ snapshots: '@radix-ui/react-portal@1.0.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@radix-ui/react-primitive': 1.0.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) @@ -15040,7 +15065,7 @@ snapshots: '@radix-ui/react-presence@1.0.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@radix-ui/react-compose-refs': 1.0.0(react@19.2.8) '@radix-ui/react-use-layout-effect': 1.0.0(react@19.2.8) react: 19.2.8 @@ -15067,7 +15092,7 @@ snapshots: '@radix-ui/react-primitive@1.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@radix-ui/react-slot': 1.0.1(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) @@ -15362,7 +15387,7 @@ snapshots: '@radix-ui/react-tooltip@1.0.5(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@radix-ui/primitive': 1.0.0 '@radix-ui/react-compose-refs': 1.0.0(react@19.2.8) '@radix-ui/react-context': 1.0.0(react@19.2.8) @@ -15423,7 +15448,7 @@ snapshots: '@radix-ui/react-use-callback-ref@1.0.0(react@19.2.8)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 react: 19.2.8 '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.17)(react@19.2.8)': @@ -15503,7 +15528,7 @@ snapshots: '@radix-ui/react-use-layout-effect@1.0.0(react@19.2.8)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 react: 19.2.8 '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.17)(react@19.2.8)': @@ -15599,7 +15624,7 @@ snapshots: '@rc-component/async-validator@5.1.0': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@rc-component/color-picker@2.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: @@ -15612,14 +15637,14 @@ snapshots: '@rc-component/context@1.4.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 rc-util: 5.44.4(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) '@rc-component/mini-decimal@1.1.3': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@rc-component/mutate-observer@1.1.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: @@ -15631,7 +15656,7 @@ snapshots: '@rc-component/portal@1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 classnames: 2.5.1 rc-util: 5.44.4(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react: 19.2.8 @@ -15755,6 +15780,17 @@ snapshots: transitivePeerDependencies: - typescript + '@react-three/cannon@6.6.0(@react-three/fiber@9.7.0(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0))(react@19.2.8)(three@0.165.0)(typescript@7.0.2)': + dependencies: + '@pmndrs/cannon-worker-api': 2.4.0(three@0.165.0) + '@react-three/fiber': 9.7.0(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0) + cannon-es: 0.20.0 + cannon-es-debugger: 1.0.0(cannon-es@0.20.0)(three@0.165.0)(typescript@7.0.2) + react: 19.2.8 + three: 0.165.0 + transitivePeerDependencies: + - typescript + '@react-three/csg@4.0.0(three@0.165.0)': dependencies: three-bvh-csg: 0.0.16(three-mesh-bvh@0.6.8(three@0.165.0))(three@0.165.0) @@ -15795,6 +15831,39 @@ snapshots: - '@types/three' - immer + '@react-three/drei@10.7.7(@react-three/fiber@9.7.0(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0))(@types/react@19.2.17)(@types/three@0.165.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0)': + dependencies: + '@babel/runtime': 7.29.2 + '@mediapipe/tasks-vision': 0.10.17 + '@monogrid/gainmap-js': 3.4.0(three@0.165.0) + '@react-three/fiber': 9.7.0(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0) + '@use-gesture/react': 10.3.1(react@19.2.8) + camera-controls: 3.1.2(three@0.165.0) + cross-env: 7.0.3 + detect-gpu: 5.0.70 + glsl-noise: 0.0.0 + hls.js: 1.6.15 + maath: 0.10.8(@types/three@0.165.0)(three@0.165.0) + meshline: 3.3.1(three@0.165.0) + react: 19.2.8 + stats-gl: 2.4.2(@types/three@0.165.0)(three@0.165.0) + stats.js: 0.17.0 + suspend-react: 0.1.3(react@19.2.8) + three: 0.165.0 + three-mesh-bvh: 0.8.3(three@0.165.0) + three-stdlib: 2.36.1(three@0.165.0) + troika-three-text: 0.52.4(three@0.165.0) + tunnel-rat: 0.1.2(@types/react@19.2.17)(react@19.2.8) + use-sync-external-store: 1.6.0(react@19.2.8) + utility-types: 3.11.0 + zustand: 5.0.14(@types/react@19.2.17)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)) + optionalDependencies: + react-dom: 19.2.8(react@19.2.8) + transitivePeerDependencies: + - '@types/react' + - '@types/three' + - immer + '@react-three/drei@9.122.0(@react-three/fiber@9.6.1(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0))(@types/react@19.2.17)(@types/three@0.165.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0)(use-sync-external-store@1.6.0(react@19.2.8))': dependencies: '@babel/runtime': 7.29.2 @@ -15850,6 +15919,26 @@ snapshots: - '@types/react' - immer + '@react-three/fiber@9.7.0(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0)': + dependencies: + '@babel/runtime': 7.29.7 + '@types/webxr': 0.5.24 + base64-js: 1.5.1 + buffer: 6.0.3 + its-fine: 2.0.0(@types/react@19.2.17)(react@19.2.8) + react: 19.2.8 + react-use-measure: 2.1.7(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + scheduler: 0.27.0 + suspend-react: 0.1.3(react@19.2.8) + three: 0.165.0 + use-sync-external-store: 1.6.0(react@19.2.8) + zustand: 5.0.14(@types/react@19.2.17)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)) + optionalDependencies: + react-dom: 19.2.8(react@19.2.8) + transitivePeerDependencies: + - '@types/react' + - immer + '@react-three/flex@1.0.1(@react-three/fiber@9.6.1(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0))(react@19.2.8)(three@0.165.0)': dependencies: '@react-pdf/yoga': 2.0.4 @@ -18215,6 +18304,23 @@ snapshots: - '@types/react' - '@types/react-dom' + lamina@1.2.2(@react-three/fiber@9.7.0(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0))(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0): + dependencies: + glsl-token-descope: 1.0.2 + glsl-token-functions: 1.0.1 + glsl-token-string: 1.0.1 + glsl-tokenizer: 2.1.5 + leva: 0.10.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + three: 0.165.0 + three-custom-shader-material: 6.4.0(@react-three/fiber@9.7.0(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0))(react@19.2.8)(three@0.165.0) + optionalDependencies: + '@react-three/fiber': 9.7.0(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + language-subtag-registry@0.3.23: {} language-tags@1.0.9: @@ -19129,7 +19235,7 @@ snapshots: rc-overflow@1.5.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 classnames: 2.5.1 rc-resize-observer: 1.4.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) rc-util: 5.44.4(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -19306,7 +19412,7 @@ snapshots: rc-virtual-list@3.19.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 classnames: 2.5.1 rc-resize-observer: 1.4.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) rc-util: 5.44.4(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -20035,6 +20141,13 @@ snapshots: '@react-three/fiber': 9.6.1(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0) react: 19.2.8 + three-custom-shader-material@6.4.0(@react-three/fiber@9.7.0(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0))(react@19.2.8)(three@0.165.0): + dependencies: + three: 0.165.0 + optionalDependencies: + '@react-three/fiber': 9.7.0(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0) + react: 19.2.8 + three-mesh-bvh@0.6.8(three@0.165.0): dependencies: three: 0.165.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ed92b422..858a0c49 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -23,3 +23,4 @@ patchedDependencies: minimumReleaseAgeExclude: - material-theme-builder@3.0.0 + - '@react-three/fiber@9.7.0' From 5bea7a1a2dee46ec8021370008a9c248031c56ae Mon Sep 17 00:00:00 2001 From: Kris Baumgartner Date: Fri, 31 Jul 2026 13:36:21 -0400 Subject: [PATCH 2/5] trails: cursor dives into the clump, click detonates the swarm The cursor's z velocity now targets the swarm's gather depth (z=-1) instead of z=0, so it plows through the middle of the clump like the original demo. Clicking applies a distance-falloff impulse to every block radiating from the cursor, slightly off-center for tumble; the center attractor then reels the swarm back in for the next blast. Co-Authored-By: Claude Fable 5 --- demos/trails/src/App.jsx | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/demos/trails/src/App.jsx b/demos/trails/src/App.jsx index a3aedb02..81e1264b 100644 --- a/demos/trails/src/App.jsx +++ b/demos/trails/src/App.jsx @@ -1,4 +1,4 @@ -import { useRef } from 'react' +import { useEffect, useRef } from 'react' import * as THREE from 'three' import { Canvas, useThree, useFrame } from '@react-three/fiber' import { useGLTF, Edges, Trail } from '@react-three/drei' @@ -15,8 +15,11 @@ const text = new TextGeometry('9.7.0', { font, size: 0.7, height: 0.35, curveSeg text.center() const vec = new THREE.Vector3() +const dir = new THREE.Vector3() // The swarm gathers slightly behind the hero so it frames it instead of covering it const attractor = new THREE.Vector3(0, 0, -1) +// Live registry of swarm bodies so the cursor can blast them apart on click +const blocks = new Set() const white = new THREE.MeshBasicMaterial({ color: '#fefefe', toneMapped: false }) const pink = new THREE.MeshBasicMaterial({ color: '#ff0080', toneMapped: false }) // Accent blocks cycle through the cursor-gradient palette @@ -63,6 +66,11 @@ function Hero({ scale = 1.2 }) { function Version({ scale = 1, material = white, ...props }) { const [ref, api] = useCompoundBody(() => ({ mass: 4 * scale, ...props, shapes: wordBox(scale) })) + useEffect(() => { + const entry = { ref, api, scale } + blocks.add(entry) + return () => blocks.delete(entry) + }, [ref, api, scale]) // Pull every block back towards the gather point behind the hero useFrame(() => api.applyForce(vec.setFromMatrixPosition(ref.current.matrix).sub(attractor).normalize().multiplyScalar(-40 * scale).toArray(), [0, 0, 0])) return @@ -82,8 +90,24 @@ function Cursor({ speed = 10, gradient = 0.7, ...props }) { })) useFrame((state) => { vec.setFromMatrixPosition(ref.current.matrix) - api.velocity.set(((state.mouse.x * viewport.width) / 2 - vec.x) * speed, ((state.mouse.y * viewport.height) / 2 - vec.y) * speed, -vec.z) + // Chase the pointer, diving to the swarm's gather depth so it plows through the clump + api.velocity.set(((state.mouse.x * viewport.width) / 2 - vec.x) * speed, ((state.mouse.y * viewport.height) / 2 - vec.y) * speed, (attractor.z - vec.z) * 2) }) + useEffect(() => { + // Click detonates: every block gets a distance-falloff impulse away from the cursor, + // applied slightly off-center so they tumble. The attractor then reels them back in. + const explode = () => { + vec.setFromMatrixPosition(ref.current.matrix) + for (const block of blocks) { + dir.setFromMatrixPosition(block.ref.current.matrix).sub(vec) + const falloff = Math.max(dir.length(), 1) + dir.normalize().multiplyScalar((42 * block.scale) / falloff) + block.api.applyImpulse(dir.toArray(), [(Math.random() - 0.5) * 0.4, (Math.random() - 0.5) * 0.4, (Math.random() - 0.5) * 0.4]) + } + } + window.addEventListener('pointerdown', explode) + return () => window.removeEventListener('pointerdown', explode) + }, []) return ( <> w * 2} /> From 8ea62632cf728bf31326b60b011bea1ddd25f87f Mon Sep 17 00:00:00 2001 From: Kris Baumgartner Date: Fri, 31 Jul 2026 13:43:28 -0400 Subject: [PATCH 3/5] trails: clicks burst pmndrs-logo shrapnel from the cursor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Detonations now spawn 16 tumbling pmndrs logos (the original demo's glb, recentered) that fly out from the cursor with a camera-ward bias, decelerate, and shrink away over 1.5s. Pure useFrame VFX — no physics bodies — so rapid clicks stay cheap. Palette mixes white with the accent colors, black fat-line outlines to match the scene. Co-Authored-By: Claude Fable 5 --- demos/trails/src/App.jsx | 69 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/demos/trails/src/App.jsx b/demos/trails/src/App.jsx index 81e1264b..c3b19851 100644 --- a/demos/trails/src/App.jsx +++ b/demos/trails/src/App.jsx @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import * as THREE from 'three' import { Canvas, useThree, useFrame } from '@react-three/fiber' import { useGLTF, Edges, Trail } from '@react-three/drei' @@ -7,6 +7,7 @@ import { LayerMaterial, Depth, Fresnel } from 'lamina' import { FontLoader, TextGeometry } from 'three-stdlib' import boldFont from 'three/examples/fonts/helvetiker_bold.typeface.json' +import pmndrsModel from './pmndrs.glb?url' import cursorModel from './cursor.glb?url' // One centered, extruded "9.7.0" shared by every block in the scene @@ -20,6 +21,8 @@ const dir = new THREE.Vector3() const attractor = new THREE.Vector3(0, 0, -1) // Live registry of swarm bodies so the cursor can blast them apart on click const blocks = new Set() +// Set by ; the cursor calls it on click to spawn logo shrapnel at its position +let burst = () => {} const white = new THREE.MeshBasicMaterial({ color: '#fefefe', toneMapped: false }) const pink = new THREE.MeshBasicMaterial({ color: '#ff0080', toneMapped: false }) // Accent blocks cycle through the cursor-gradient palette @@ -42,9 +45,72 @@ export const App = ({ amount = 11 }) => ( ))} + ) +function Bursts() { + const { nodes } = useGLTF(pmndrsModel) + // The glb logo pivot is off-center; recenter a clone so shrapnel spins around itself + const logo = useMemo(() => { + const geometry = nodes.logo.geometry.clone() + geometry.center() + return geometry + }, [nodes]) + const [bursts, setBursts] = useState([]) + const id = useRef(0) + useEffect(() => { + burst = (origin) => setBursts((all) => [...all, { id: ++id.current, origin }]) + return () => (burst = () => {}) + }, []) + return bursts.map(({ id, origin }) => ( + setBursts((all) => all.filter((b) => b.id !== id))} /> + )) +} + +// Purely visual logo shrapnel: flies outward, tumbles, shrinks away — no physics cost +function Burst({ geometry, origin, onDone, count = 16, life = 1.5 }) { + const group = useRef() + const age = useRef(0) + const particles = useMemo( + () => + Array.from({ length: count }, (_, i) => ({ + // random sphere, biased towards the camera so debris whooshes past the viewer + dir: new THREE.Vector3().randomDirection().add(vec.set(0, 0, 0.5)).normalize(), + speed: 5 + Math.random() * 7, + rotation: [Math.random() * Math.PI * 2, Math.random() * Math.PI * 2, Math.random() * Math.PI * 2], + spin: [(Math.random() - 0.5) * 12, (Math.random() - 0.5) * 12, (Math.random() - 0.5) * 12], + size: 0.4 + Math.random() * 0.8, + material: Math.random() < 0.45 ? accents[i % accents.length] : white + })), + [count] + ) + useFrame((_, delta) => { + age.current += delta + const k = age.current / life + if (k >= 1) return onDone() + // pop in fast, decelerate, shrink to nothing + const envelope = Math.min(k * 10, 1) * (1 - k) + group.current.children.forEach((child, i) => { + const p = particles[i] + child.position.addScaledVector(p.dir, p.speed * (1 - 0.7 * k) * delta) + child.rotation.x += p.spin[0] * delta + child.rotation.y += p.spin[1] * delta + child.rotation.z += p.spin[2] * delta + child.scale.set(0.188, 0.188, 0.97).multiplyScalar(p.size * envelope) + }) + }) + return ( + + {particles.map((p, i) => ( + + + + ))} + + ) +} + function Block({ scale = 1, material = white, edge = 'black', ...props }) { return ( @@ -98,6 +164,7 @@ function Cursor({ speed = 10, gradient = 0.7, ...props }) { // applied slightly off-center so they tumble. The attractor then reels them back in. const explode = () => { vec.setFromMatrixPosition(ref.current.matrix) + burst(vec.toArray()) for (const block of blocks) { dir.setFromMatrixPosition(block.ref.current.matrix).sub(vec) const falloff = Math.max(dir.length(), 1) From e35eebcc31d7d2d9104ab9fa449b5d22f8e949f4 Mon Sep 17 00:00:00 2001 From: Kris Baumgartner Date: Fri, 31 Jul 2026 13:49:21 -0400 Subject: [PATCH 4/5] demos: restore trails, move v9.7.0 release demo to trails-r3f-9.7.0 demos/trails goes back to its pristine upstream state (including its fiber 9.6.1 pin). The release demo now lives as its own workspace package @demo/trails-r3f-9.7.0 with updated pmndrs.json metadata, page title and README links. Thumbnail is inherited from trails for now. Co-Authored-By: Claude Fable 5 --- demos/trails-r3f-9.7.0/.prettierrc | 10 ++ demos/trails-r3f-9.7.0/README.md | 9 + demos/trails-r3f-9.7.0/index.html | 13 ++ demos/trails-r3f-9.7.0/package.json | 40 +++++ demos/trails-r3f-9.7.0/pmndrs.json | 11 ++ demos/trails-r3f-9.7.0/public/vite.svg | 1 + demos/trails-r3f-9.7.0/src/App.jsx | 196 ++++++++++++++++++++++ demos/trails-r3f-9.7.0/src/cursor.glb | Bin 0 -> 2604 bytes demos/trails-r3f-9.7.0/src/index.jsx | 45 +++++ demos/trails-r3f-9.7.0/src/pmndrs.glb | Bin 0 -> 4740 bytes demos/trails-r3f-9.7.0/src/styles.css | 48 ++++++ demos/trails-r3f-9.7.0/src/vite-env.d.ts | 1 + demos/trails-r3f-9.7.0/thumbnail.webp | Bin 0 -> 29092 bytes demos/trails-r3f-9.7.0/tsconfig.json | 20 +++ demos/trails-r3f-9.7.0/tsconfig.node.json | 9 + demos/trails-r3f-9.7.0/vite.config.ts | 6 + demos/trails/package.json | 2 +- demos/trails/src/App.jsx | 153 +++-------------- demos/trails/src/index.jsx | 4 +- pnpm-lock.yaml | 94 ++++++----- 20 files changed, 486 insertions(+), 176 deletions(-) create mode 100755 demos/trails-r3f-9.7.0/.prettierrc create mode 100644 demos/trails-r3f-9.7.0/README.md create mode 100644 demos/trails-r3f-9.7.0/index.html create mode 100644 demos/trails-r3f-9.7.0/package.json create mode 100644 demos/trails-r3f-9.7.0/pmndrs.json create mode 100644 demos/trails-r3f-9.7.0/public/vite.svg create mode 100644 demos/trails-r3f-9.7.0/src/App.jsx create mode 100755 demos/trails-r3f-9.7.0/src/cursor.glb create mode 100644 demos/trails-r3f-9.7.0/src/index.jsx create mode 100755 demos/trails-r3f-9.7.0/src/pmndrs.glb create mode 100644 demos/trails-r3f-9.7.0/src/styles.css create mode 100644 demos/trails-r3f-9.7.0/src/vite-env.d.ts create mode 100644 demos/trails-r3f-9.7.0/thumbnail.webp create mode 100644 demos/trails-r3f-9.7.0/tsconfig.json create mode 100644 demos/trails-r3f-9.7.0/tsconfig.node.json create mode 100644 demos/trails-r3f-9.7.0/vite.config.ts diff --git a/demos/trails-r3f-9.7.0/.prettierrc b/demos/trails-r3f-9.7.0/.prettierrc new file mode 100755 index 00000000..6043ce2c --- /dev/null +++ b/demos/trails-r3f-9.7.0/.prettierrc @@ -0,0 +1,10 @@ +{ + "printWidth": 160, + "tabWidth": 2, + "useTabs": false, + "semi": false, + "singleQuote": true, + "trailingComma": "none", + "bracketSpacing": true, + "jsxBracketSameLine": true +} \ No newline at end of file diff --git a/demos/trails-r3f-9.7.0/README.md b/demos/trails-r3f-9.7.0/README.md new file mode 100644 index 00000000..704030e0 --- /dev/null +++ b/demos/trails-r3f-9.7.0/README.md @@ -0,0 +1,9 @@ +[![Static](https://img.shields.io/badge/demo-%23646CFF.svg?logo=html5&logoColor=white)](https://pmndrs.github.io/examples/trails-r3f-9.7.0) +[![CodeSandbox](https://img.shields.io/badge/codesandbox-040404?logo=codesandbox&logoColor=DBDBDB)](https://codesandbox.io/s/github/pmndrs/examples/tree/main/demos/trails-r3f-9.7.0) +[![Stackblitz](https://img.shields.io/badge/stackblitz-fff?logo=Stackblitz&logoColor=1389FD)](https://stackblitz.com/github/pmndrs/examples/tree/main/demos/trails-r3f-9.7.0) + +```sh +$ npx degit pmndrs/examples/demos/trails-r3f-9.7.0 +``` + +![](thumbnail.webp) diff --git a/demos/trails-r3f-9.7.0/index.html b/demos/trails-r3f-9.7.0/index.html new file mode 100644 index 00000000..31ae1683 --- /dev/null +++ b/demos/trails-r3f-9.7.0/index.html @@ -0,0 +1,13 @@ + + + + + + + React Three Fiber v9.7.0 + + +
+ + + diff --git a/demos/trails-r3f-9.7.0/package.json b/demos/trails-r3f-9.7.0/package.json new file mode 100644 index 00000000..77c5e545 --- /dev/null +++ b/demos/trails-r3f-9.7.0/package.json @@ -0,0 +1,40 @@ +{ + "name": "@demo/trails-r3f-9.7.0", + "version": "1.0.0", + "dependencies": { + "@pmndrs/branding": "^0.0.8", + "@react-three/cannon": "^6.6.0", + "@react-three/drei": "10.7.7", + "@react-three/fiber": "9.7.0", + "lamina": "1.2.2", + "react": "19.2.8", + "react-dom": "19.2.8", + "three": "0.165.0" + }, + "devDependencies": { + "@types/react": "19.2.17", + "@types/react-dom": "19.2.3", + "@types/three": "^0.165.0", + "@vitejs/plugin-react": "^6.0.4", + "typescript": "^7.0.2", + "vite": "^8.1.5" + }, + "scripts": { + "dev": "vite --host", + "dev3": "e2e-dev $npm_package_name", + "build": "tsc && vite build", + "build2": "tsc && e2e-build $npm_package_name", + "preview": "vite preview" + }, + "browserslist": [ + "> 1%", + "not dead", + "not ie 11", + "not op_mini all" + ], + "type": "module", + "repository": { + "type": "git", + "url": "https://github.com/pmndrs/examples/tree/main/demos/trails-r3f-9.7.0" + } +} diff --git a/demos/trails-r3f-9.7.0/pmndrs.json b/demos/trails-r3f-9.7.0/pmndrs.json new file mode 100644 index 00000000..c874fe02 --- /dev/null +++ b/demos/trails-r3f-9.7.0/pmndrs.json @@ -0,0 +1,11 @@ +{ + "$schema": "../../schemas/pmndrs.schema.json", + "title": "Trails with React Three Fiber 9.7.0", + "description": "A v9.7.0 release riff on the trails demo: a swarm of physics-driven extruded version numbers orbits an immovable 9.7.0, and clicking detonates them in a burst of pmndrs logos.", + "tags": ["trail", "meshline", "physics", "gradient", "text3d", "release"], + "authors": ["Faraz Shaikh", "Kris Baumgartner"], + "publishedAt": "2026-07-31", + "source": "https://github.com/pmndrs/examples/tree/main/demos/trails", + "libraries": ["@react-three/cannon", "@react-three/drei", "@react-three/fiber", "lamina"], + "assets": [] +} diff --git a/demos/trails-r3f-9.7.0/public/vite.svg b/demos/trails-r3f-9.7.0/public/vite.svg new file mode 100644 index 00000000..ee9fadaf --- /dev/null +++ b/demos/trails-r3f-9.7.0/public/vite.svg @@ -0,0 +1 @@ + diff --git a/demos/trails-r3f-9.7.0/src/App.jsx b/demos/trails-r3f-9.7.0/src/App.jsx new file mode 100644 index 00000000..c3b19851 --- /dev/null +++ b/demos/trails-r3f-9.7.0/src/App.jsx @@ -0,0 +1,196 @@ +import { useEffect, useMemo, useRef, useState } from 'react' +import * as THREE from 'three' +import { Canvas, useThree, useFrame } from '@react-three/fiber' +import { useGLTF, Edges, Trail } from '@react-three/drei' +import { Physics, useCompoundBody } from '@react-three/cannon' +import { LayerMaterial, Depth, Fresnel } from 'lamina' +import { FontLoader, TextGeometry } from 'three-stdlib' + +import boldFont from 'three/examples/fonts/helvetiker_bold.typeface.json' +import pmndrsModel from './pmndrs.glb?url' +import cursorModel from './cursor.glb?url' + +// One centered, extruded "9.7.0" shared by every block in the scene +const font = new FontLoader().parse(boldFont) +const text = new TextGeometry('9.7.0', { font, size: 0.7, height: 0.35, curveSegments: 16, letterSpacing: 0.02 }) +text.center() + +const vec = new THREE.Vector3() +const dir = new THREE.Vector3() +// The swarm gathers slightly behind the hero so it frames it instead of covering it +const attractor = new THREE.Vector3(0, 0, -1) +// Live registry of swarm bodies so the cursor can blast them apart on click +const blocks = new Set() +// Set by ; the cursor calls it on click to spawn logo shrapnel at its position +let burst = () => {} +const white = new THREE.MeshBasicMaterial({ color: '#fefefe', toneMapped: false }) +const pink = new THREE.MeshBasicMaterial({ color: '#ff0080', toneMapped: false }) +// Accent blocks cycle through the cursor-gradient palette +const accents = ['#ff0080', '#f7b955', '#5786f5'].map((color) => new THREE.MeshBasicMaterial({ color, toneMapped: false })) + +export const App = ({ amount = 11 }) => ( + + + + {Array.from({ length: amount }, (_, i) => ( + + ))} + + + + +) + +function Bursts() { + const { nodes } = useGLTF(pmndrsModel) + // The glb logo pivot is off-center; recenter a clone so shrapnel spins around itself + const logo = useMemo(() => { + const geometry = nodes.logo.geometry.clone() + geometry.center() + return geometry + }, [nodes]) + const [bursts, setBursts] = useState([]) + const id = useRef(0) + useEffect(() => { + burst = (origin) => setBursts((all) => [...all, { id: ++id.current, origin }]) + return () => (burst = () => {}) + }, []) + return bursts.map(({ id, origin }) => ( + setBursts((all) => all.filter((b) => b.id !== id))} /> + )) +} + +// Purely visual logo shrapnel: flies outward, tumbles, shrinks away — no physics cost +function Burst({ geometry, origin, onDone, count = 16, life = 1.5 }) { + const group = useRef() + const age = useRef(0) + const particles = useMemo( + () => + Array.from({ length: count }, (_, i) => ({ + // random sphere, biased towards the camera so debris whooshes past the viewer + dir: new THREE.Vector3().randomDirection().add(vec.set(0, 0, 0.5)).normalize(), + speed: 5 + Math.random() * 7, + rotation: [Math.random() * Math.PI * 2, Math.random() * Math.PI * 2, Math.random() * Math.PI * 2], + spin: [(Math.random() - 0.5) * 12, (Math.random() - 0.5) * 12, (Math.random() - 0.5) * 12], + size: 0.4 + Math.random() * 0.8, + material: Math.random() < 0.45 ? accents[i % accents.length] : white + })), + [count] + ) + useFrame((_, delta) => { + age.current += delta + const k = age.current / life + if (k >= 1) return onDone() + // pop in fast, decelerate, shrink to nothing + const envelope = Math.min(k * 10, 1) * (1 - k) + group.current.children.forEach((child, i) => { + const p = particles[i] + child.position.addScaledVector(p.dir, p.speed * (1 - 0.7 * k) * delta) + child.rotation.x += p.spin[0] * delta + child.rotation.y += p.spin[1] * delta + child.rotation.z += p.spin[2] * delta + child.scale.set(0.188, 0.188, 0.97).multiplyScalar(p.size * envelope) + }) + }) + return ( + + {particles.map((p, i) => ( + + + + ))} + + ) +} + +function Block({ scale = 1, material = white, edge = 'black', ...props }) { + return ( + + + + + + ) +} + +// One box approximating the extruded "9.7.0" word, baked to the visual scale +const wordBox = (scale) => [{ type: 'Box', args: [2.2 * scale, 0.55 * scale, 0.35 * scale], position: [0, 0, 0] }] + +// Immovable billboard version number the swarm and cursor bounce off +function Hero({ scale = 1.2 }) { + const [ref] = useCompoundBody(() => ({ mass: 0, position: [0, 0, 1.5], shapes: wordBox(scale) })) + return +} + +function Version({ scale = 1, material = white, ...props }) { + const [ref, api] = useCompoundBody(() => ({ mass: 4 * scale, ...props, shapes: wordBox(scale) })) + useEffect(() => { + const entry = { ref, api, scale } + blocks.add(entry) + return () => blocks.delete(entry) + }, [ref, api, scale]) + // Pull every block back towards the gather point behind the hero + useFrame(() => api.applyForce(vec.setFromMatrixPosition(ref.current.matrix).sub(attractor).normalize().multiplyScalar(-40 * scale).toArray(), [0, 0, 0])) + return +} + +function Cursor({ speed = 10, gradient = 0.7, ...props }) { + const { nodes } = useGLTF(cursorModel) + const viewport = useThree((state) => state.viewport) + // follows this object3d, parented to the cursor body so the ribbon trails its tip + const trail = useRef() + const [ref, api] = useCompoundBody(() => ({ + ...props, + shapes: [ + { type: 'Cylinder', args: [0.6, 0.6, 0.5, 3], position: [0, 0.2, 0], rotation: [Math.PI / 2, Math.PI, 0] }, + { type: 'Box', args: [0.25, 1, 0.3], position: [0, -0.45, 0] } + ] + })) + useFrame((state) => { + vec.setFromMatrixPosition(ref.current.matrix) + // Chase the pointer, diving to the swarm's gather depth so it plows through the clump + api.velocity.set(((state.mouse.x * viewport.width) / 2 - vec.x) * speed, ((state.mouse.y * viewport.height) / 2 - vec.y) * speed, (attractor.z - vec.z) * 2) + }) + useEffect(() => { + // Click detonates: every block gets a distance-falloff impulse away from the cursor, + // applied slightly off-center so they tumble. The attractor then reels them back in. + const explode = () => { + vec.setFromMatrixPosition(ref.current.matrix) + burst(vec.toArray()) + for (const block of blocks) { + dir.setFromMatrixPosition(block.ref.current.matrix).sub(vec) + const falloff = Math.max(dir.length(), 1) + dir.normalize().multiplyScalar((42 * block.scale) / falloff) + block.api.applyImpulse(dir.toArray(), [(Math.random() - 0.5) * 0.4, (Math.random() - 0.5) * 0.4, (Math.random() - 0.5) * 0.4]) + } + } + window.addEventListener('pointerdown', explode) + return () => window.removeEventListener('pointerdown', explode) + }, []) + return ( + <> + w * 2} /> + + + + + + + + + + + + + + + ) +} diff --git a/demos/trails-r3f-9.7.0/src/cursor.glb b/demos/trails-r3f-9.7.0/src/cursor.glb new file mode 100755 index 0000000000000000000000000000000000000000..c87ded4be733ea1f7cab50686d3950413cced050 GIT binary patch literal 2604 zcma)8&2Jk;6n~@%l+yB1DB+`&Wq<*aI0@q^&_U(@6I<7CZ(M7t|;ZBo#(#qFWX;00V8I8$o zk9)rDI*8RW3fq^N-#~v>P$JD|g{?kwtS;JEO`OThahseDlo5Uv=_%3*)m4Ru4MB$#mE`ieNhpX zDta(#4i%eGlq}`$*B>vHeV!m&sD2%8L(_FMmTP^VeRFc|X3`Xx6a9ziHOLMD=V$Q&;##k<@ZW#1f z!&;&`Qpu}^Y2tg5B{qJF= z7-J^rCGw5zGxaEzz)a8&0S1RL{}df_9bsnbe+?^tP=5$NNsn?Cm?^r*C-R-Dr+h^Z zF@NGbrDMIwG3xWU9`}Uo({%WEK3l?%FtJ|3`tTEDK(!|N2s4g9fr)(3cZ5g2lRRlm z$REcPIN_Vv<1>it2`0*)`0*11F(+dkA3vh}&>oKvJK3J*0Z3yu@7=@S2XUXoMDYaw zWX$w@Qh3Ud#z3rL4MIN)CVh|Td=h(-^Y;C#*sG)BWcgz$v?ucM!GFcyetlFLmj`HX zwOXZdNjOb^Qk*28{WB2uw4_{!2kR~x=$FW&Xn$G`heO0271NU9(SBSK?(x9`!5`yk zPq=AvH@GRz$ECD*Ecny%8eE1~alQ=aaDE1#;JhX*=TKXO58xx5U%?#Cx8QZ0i^4L8 z+Q;x7T*37UybV|3eUNaK;8S=9-i1rJUV_))6}SKwalHs{z?<+AEa18T=iy7Z4)eI; Pzk~1vd +
@react-three/fiber v9.7.0 —
+
+ +
+ pmnd.rs +
+ dev collective +
31/07/2026
+
+
+ + + + +) diff --git a/demos/trails-r3f-9.7.0/src/pmndrs.glb b/demos/trails-r3f-9.7.0/src/pmndrs.glb new file mode 100755 index 0000000000000000000000000000000000000000..ce5c278d9aa0e9cfc777b4e5dfc74ec214eaa1a5 GIT binary patch literal 4740 zcmb7G%X1WU5N?7f0-{DiMUiC?1>G#OGduf;y1PjT5C~aFBJzrC9+PA>*$unPBNbE5 zK|NZQCNn9cEL$a_t)KDf4}bE>>)FA zqenW3=)+E;8yks^j}(TMwDDTau4~;(+KgSctK;=bRqNJH%vLMqN-Z+OEs@?iyF6uA zBLjyDk*o1oI%a55?W$d^l`3WC8Zlj4j%u|@?$^3?57o5p3rkvge4caKh^j@ka%IX! zK_6QlTZ@*odAl~NT0Q<;Wu~Hmf`aO3NTC$d^*E2PP%X`u>ZPk5VZ2_imL{&$5iV(` z3L^uf1BD?aJ5(4x*)ynh$D`V4|JlAmVfgSRUF$Y@>{5BEG>H~-Sq(Kl$x*FRb?2YB zGCgfq&y?(IN@ub%zfdXL<@)I20`FzTjbv1ttXwJc1;+I>&pUpd*E3?NWZa5#CY6Y% zlDc638CIHQ5*aha zglLzJwM|2h8)hO8*OksLj z{jl}5{U3mh{x%KiWTIK!urmBBk_%pUfN2J%F%wo2KWuj$Zx!GCW{UO3mLtD!C+Zy- zqWZ0_m9Gwdo!{}tLiT=m?&c@I2q%cYxUJ_u9le`_PCG1Tfzt{@2l}EDhS4keFg&f_ z$caunyt1>Rbiiqcv6pbdu$B4>sFg~8*%eEw(s7Q-Cyy?Y

8S!@clQ=Ld=|;%X0%|UC8;SyFwox2PQ;^ z>%lt&)6Mz6dq^*4Q0JimUGC%N)_R*cAv%~LOo#JLyEHhS2Feahh-NOx0Z-218nm1s zCq!o@$Wby6ct74b>R`7|LA8fh!wikRQ1#ApfZfNz3{4!#!-wg38Ynw3>{eia+{bBq zKTO4W>+UB+r|tcSj(29*3}JhDxyB3u&bnRN-jB@Ce1{R%gg!i;BI2wY=e;~U^2F~d z*H91o2-l>~@4=HkzXu<2v;Xz-zuD_Y?_|Gy|D#6V&7R$ghdlW3`#$T)N&fcm4=N9j z{M0{NvcIv;j~u%?m}fOR%X!qX@88wl9O`aNq^W#9_ZR1(FR`~LW@QamhcQ2x+fj45 zaS^dPhbZsyiTx_)5J&j+`yx1AUHbeUe8fQyJ?Zm%@DK+*_zr6D{HlHq!9!m0IW8jh z>Uob(?7>GI;n(kr;COZE^Ly|S2R-zp&+oxQ9Q5EjsKN8A`Z)v-dBx|rh}f&=JwCAq zA8~|Vzb}I0)uqqx!ABhQ(33vD2M=+x-%d}`R@%<*X4*}U(4+jffg-evBVeE>XcKj? z?Vv66EbX99ww?4mJw{K{GyJ!Y9-_xN0tVVkFY-})`Q1o6={efNb`NRv0zF0B*lweT z=_PuFy4ZHns}!XeW!PpYMQM7SEVdRI6ep7ousuL8(`%F@ovlv$shbYbLAD1eL2vL% z{cQWGmk!erI?eVp73dU=&`Gu@={OD03Cgq0Qo#6MsLzE9i<+&Jv2z8^fryL y9i#Jfk=~&a+Y-&tEWJlITbm|moF?fk+p~0rF3>a;*%s+7dY3NIIkx9$iv9yq)A>sP literal 0 HcmV?d00001 diff --git a/demos/trails-r3f-9.7.0/src/styles.css b/demos/trails-r3f-9.7.0/src/styles.css new file mode 100644 index 00000000..274411a7 --- /dev/null +++ b/demos/trails-r3f-9.7.0/src/styles.css @@ -0,0 +1,48 @@ +@import url('https://rsms.me/inter/inter.css'); + +* { + box-sizing: border-box; +} + +html, +body, +#root { + width: 100%; + height: 100%; + margin: 0; + padding: 0; +} + +body { + font-family: inter; + font-size: 13px; + color: #fefefe; + background: black; + cursor: none; +} + +@keyframes fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +canvas { + opacity: 0; + touch-action: none; + animation: fade-in 2s ease 0.3s forwards; + animation-delay: 0s; +} + +svg { + fill: #fefefe; +} + +a { + pointer-events: all; + color: white; + text-decoration: none; +} diff --git a/demos/trails-r3f-9.7.0/src/vite-env.d.ts b/demos/trails-r3f-9.7.0/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/demos/trails-r3f-9.7.0/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/demos/trails-r3f-9.7.0/thumbnail.webp b/demos/trails-r3f-9.7.0/thumbnail.webp new file mode 100644 index 0000000000000000000000000000000000000000..09bf1e699ea2c8a2e68a6193e08e90faa0696209 GIT binary patch literal 29092 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

nz}Thkmbu#6vNL`~7u@ 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 a{@?8T6MW2v>=fSN00000000000000Q$I8wC literal 0 HcmV?d00001 diff --git a/demos/trails-r3f-9.7.0/tsconfig.json b/demos/trails-r3f-9.7.0/tsconfig.json new file mode 100644 index 00000000..2bb228a4 --- /dev/null +++ b/demos/trails-r3f-9.7.0/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/demos/trails-r3f-9.7.0/tsconfig.node.json b/demos/trails-r3f-9.7.0/tsconfig.node.json new file mode 100644 index 00000000..16dfedc6 --- /dev/null +++ b/demos/trails-r3f-9.7.0/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/demos/trails-r3f-9.7.0/vite.config.ts b/demos/trails-r3f-9.7.0/vite.config.ts new file mode 100644 index 00000000..eca6e2f3 --- /dev/null +++ b/demos/trails-r3f-9.7.0/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()] +}) diff --git a/demos/trails/package.json b/demos/trails/package.json index 36188152..0889eb0b 100644 --- a/demos/trails/package.json +++ b/demos/trails/package.json @@ -5,7 +5,7 @@ "@pmndrs/branding": "^0.0.8", "@react-three/cannon": "^6.6.0", "@react-three/drei": "10.7.7", - "@react-three/fiber": "9.7.0", + "@react-three/fiber": "9.6.1", "lamina": "1.2.2", "react": "19.2.8", "react-dom": "19.2.8", diff --git a/demos/trails/src/App.jsx b/demos/trails/src/App.jsx index c3b19851..bf197b94 100644 --- a/demos/trails/src/App.jsx +++ b/demos/trails/src/App.jsx @@ -1,147 +1,49 @@ -import { useEffect, useMemo, useRef, useState } from 'react' +import { useRef } from 'react' import * as THREE from 'three' import { Canvas, useThree, useFrame } from '@react-three/fiber' import { useGLTF, Edges, Trail } from '@react-three/drei' import { Physics, useCompoundBody } from '@react-three/cannon' import { LayerMaterial, Depth, Fresnel } from 'lamina' -import { FontLoader, TextGeometry } from 'three-stdlib' -import boldFont from 'three/examples/fonts/helvetiker_bold.typeface.json' import pmndrsModel from './pmndrs.glb?url' import cursorModel from './cursor.glb?url' -// One centered, extruded "9.7.0" shared by every block in the scene -const font = new FontLoader().parse(boldFont) -const text = new TextGeometry('9.7.0', { font, size: 0.7, height: 0.35, curveSegments: 16, letterSpacing: 0.02 }) -text.center() - const vec = new THREE.Vector3() -const dir = new THREE.Vector3() -// The swarm gathers slightly behind the hero so it frames it instead of covering it -const attractor = new THREE.Vector3(0, 0, -1) -// Live registry of swarm bodies so the cursor can blast them apart on click -const blocks = new Set() -// Set by ; the cursor calls it on click to spawn logo shrapnel at its position -let burst = () => {} const white = new THREE.MeshBasicMaterial({ color: '#fefefe', toneMapped: false }) -const pink = new THREE.MeshBasicMaterial({ color: '#ff0080', toneMapped: false }) -// Accent blocks cycle through the cursor-gradient palette -const accents = ['#ff0080', '#f7b955', '#5786f5'].map((color) => new THREE.MeshBasicMaterial({ color, toneMapped: false })) +const black = new THREE.MeshBasicMaterial({ color: 'black', toneMapped: false }) -export const App = ({ amount = 11 }) => ( +export const App = ({ amount = 12 }) => ( - {Array.from({ length: amount }, (_, i) => ( - + ))} - ) -function Bursts() { +function Pmndrs(props) { const { nodes } = useGLTF(pmndrsModel) - // The glb logo pivot is off-center; recenter a clone so shrapnel spins around itself - const logo = useMemo(() => { - const geometry = nodes.logo.geometry.clone() - geometry.center() - return geometry - }, [nodes]) - const [bursts, setBursts] = useState([]) - const id = useRef(0) - useEffect(() => { - burst = (origin) => setBursts((all) => [...all, { id: ++id.current, origin }]) - return () => (burst = () => {}) - }, []) - return bursts.map(({ id, origin }) => ( - setBursts((all) => all.filter((b) => b.id !== id))} /> - )) -} - -// Purely visual logo shrapnel: flies outward, tumbles, shrinks away — no physics cost -function Burst({ geometry, origin, onDone, count = 16, life = 1.5 }) { - const group = useRef() - const age = useRef(0) - const particles = useMemo( - () => - Array.from({ length: count }, (_, i) => ({ - // random sphere, biased towards the camera so debris whooshes past the viewer - dir: new THREE.Vector3().randomDirection().add(vec.set(0, 0, 0.5)).normalize(), - speed: 5 + Math.random() * 7, - rotation: [Math.random() * Math.PI * 2, Math.random() * Math.PI * 2, Math.random() * Math.PI * 2], - spin: [(Math.random() - 0.5) * 12, (Math.random() - 0.5) * 12, (Math.random() - 0.5) * 12], - size: 0.4 + Math.random() * 0.8, - material: Math.random() < 0.45 ? accents[i % accents.length] : white - })), - [count] - ) - useFrame((_, delta) => { - age.current += delta - const k = age.current / life - if (k >= 1) return onDone() - // pop in fast, decelerate, shrink to nothing - const envelope = Math.min(k * 10, 1) * (1 - k) - group.current.children.forEach((child, i) => { - const p = particles[i] - child.position.addScaledVector(p.dir, p.speed * (1 - 0.7 * k) * delta) - child.rotation.x += p.spin[0] * delta - child.rotation.y += p.spin[1] * delta - child.rotation.z += p.spin[2] * delta - child.scale.set(0.188, 0.188, 0.97).multiplyScalar(p.size * envelope) - }) - }) - return ( - - {particles.map((p, i) => ( - - - - ))} - - ) -} - -function Block({ scale = 1, material = white, edge = 'black', ...props }) { + const [ref, api] = useCompoundBody(() => ({ + ...props, + shapes: [ + { type: 'Box', args: [0.65, 0.65, 0.5], position: [0.18, 0.18, 0] }, + { type: 'Box', args: [0.3, 0.3, 0.5], position: [-0.35, 0, 0] }, + { type: 'Box', args: [0.3, 0.3, 0.5], position: [0, -0.35, 0] } + ] + })) + // Pull every logo back towards the center of the scene + useFrame(() => api.applyForce(vec.setFromMatrixPosition(ref.current.matrix).normalize().multiplyScalar(-40).toArray(), [0, 0, 0])) return ( - - - + + + ) } -// One box approximating the extruded "9.7.0" word, baked to the visual scale -const wordBox = (scale) => [{ type: 'Box', args: [2.2 * scale, 0.55 * scale, 0.35 * scale], position: [0, 0, 0] }] - -// Immovable billboard version number the swarm and cursor bounce off -function Hero({ scale = 1.2 }) { - const [ref] = useCompoundBody(() => ({ mass: 0, position: [0, 0, 1.5], shapes: wordBox(scale) })) - return -} - -function Version({ scale = 1, material = white, ...props }) { - const [ref, api] = useCompoundBody(() => ({ mass: 4 * scale, ...props, shapes: wordBox(scale) })) - useEffect(() => { - const entry = { ref, api, scale } - blocks.add(entry) - return () => blocks.delete(entry) - }, [ref, api, scale]) - // Pull every block back towards the gather point behind the hero - useFrame(() => api.applyForce(vec.setFromMatrixPosition(ref.current.matrix).sub(attractor).normalize().multiplyScalar(-40 * scale).toArray(), [0, 0, 0])) - return -} - function Cursor({ speed = 10, gradient = 0.7, ...props }) { const { nodes } = useGLTF(cursorModel) const viewport = useThree((state) => state.viewport) @@ -156,25 +58,8 @@ function Cursor({ speed = 10, gradient = 0.7, ...props }) { })) useFrame((state) => { vec.setFromMatrixPosition(ref.current.matrix) - // Chase the pointer, diving to the swarm's gather depth so it plows through the clump - api.velocity.set(((state.mouse.x * viewport.width) / 2 - vec.x) * speed, ((state.mouse.y * viewport.height) / 2 - vec.y) * speed, (attractor.z - vec.z) * 2) + api.velocity.set(((state.mouse.x * viewport.width) / 2 - vec.x) * speed, ((state.mouse.y * viewport.height) / 2 - vec.y) * speed, -vec.z) }) - useEffect(() => { - // Click detonates: every block gets a distance-falloff impulse away from the cursor, - // applied slightly off-center so they tumble. The attractor then reels them back in. - const explode = () => { - vec.setFromMatrixPosition(ref.current.matrix) - burst(vec.toArray()) - for (const block of blocks) { - dir.setFromMatrixPosition(block.ref.current.matrix).sub(vec) - const falloff = Math.max(dir.length(), 1) - dir.normalize().multiplyScalar((42 * block.scale) / falloff) - block.api.applyImpulse(dir.toArray(), [(Math.random() - 0.5) * 0.4, (Math.random() - 0.5) * 0.4, (Math.random() - 0.5) * 0.4]) - } - } - window.addEventListener('pointerdown', explode) - return () => window.removeEventListener('pointerdown', explode) - }, []) return ( <> w * 2} /> diff --git a/demos/trails/src/index.jsx b/demos/trails/src/index.jsx index e3121f25..4dda878c 100644 --- a/demos/trails/src/index.jsx +++ b/demos/trails/src/index.jsx @@ -6,7 +6,7 @@ import { App } from './App' createRoot(document.getElementById('root')).render( <> -

@react-three/fiber v9.7.0 —
+
trails —
dev collective -
31/07/2026
+
18/03/2022
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6d0f7e96..e4e27aca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6572,6 +6572,52 @@ importers: version: 8.1.5(@types/node@20.19.39)(jiti@2.7.0)(yaml@2.9.0) demos/trails: + dependencies: + '@pmndrs/branding': + specifier: ^0.0.8 + version: 0.0.8(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@react-three/cannon': + specifier: ^6.6.0 + version: 6.6.0(@react-three/fiber@9.6.1(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0))(react@19.2.8)(three@0.165.0)(typescript@7.0.2) + '@react-three/drei': + specifier: 10.7.7 + version: 10.7.7(@react-three/fiber@9.6.1(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0))(@types/react@19.2.17)(@types/three@0.165.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0) + '@react-three/fiber': + specifier: 9.6.1 + version: 9.6.1(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0) + lamina: + specifier: 1.2.2 + version: 1.2.2(@react-three/fiber@9.6.1(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0))(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0) + react: + specifier: 19.2.8 + version: 19.2.8 + react-dom: + specifier: 19.2.8 + version: 19.2.8(react@19.2.8) + three: + specifier: 0.165.0 + version: 0.165.0 + devDependencies: + '@types/react': + specifier: 19.2.17 + version: 19.2.17 + '@types/react-dom': + specifier: 19.2.3 + version: 19.2.3(@types/react@19.2.17) + '@types/three': + specifier: ^0.165.0 + version: 0.165.0 + '@vitejs/plugin-react': + specifier: ^6.0.4 + version: 6.0.4(vite@8.1.5(@types/node@20.19.39)(jiti@2.7.0)(yaml@2.9.0)) + typescript: + specifier: ^7.0.2 + version: 7.0.2 + vite: + specifier: ^8.1.5 + version: 8.1.5(@types/node@20.19.39)(jiti@2.7.0)(yaml@2.9.0) + + demos/trails-r3f-9.7.0: dependencies: '@pmndrs/branding': specifier: ^0.0.8 @@ -7505,18 +7551,12 @@ packages: '@emnapi/core@1.11.1': resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} - '@emnapi/core@1.9.2': - resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} - '@emnapi/runtime@1.11.1': resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} '@emnapi/runtime@1.9.2': resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} - '@emnapi/wasi-threads@1.2.1': - resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} - '@emnapi/wasi-threads@1.2.2': resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} @@ -13256,10 +13296,6 @@ packages: resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} engines: {node: '>=18'} - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} - engines: {node: '>=12.0.0'} - tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} @@ -14035,12 +14071,6 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/core@1.9.2': - dependencies: - '@emnapi/wasi-threads': 1.2.1 - tslib: 2.8.1 - optional: true - '@emnapi/runtime@1.11.1': dependencies: tslib: 2.8.1 @@ -14051,11 +14081,6 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.2.1': - dependencies: - tslib: 2.8.1 - optional: true - '@emnapi/wasi-threads@1.2.2': dependencies: tslib: 2.8.1 @@ -14416,8 +14441,8 @@ snapshots: '@napi-rs/wasm-runtime@0.2.12': dependencies: - '@emnapi/core': 1.9.2 - '@emnapi/runtime': 1.9.2 + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 '@tybys/wasm-util': 0.10.1 optional: true @@ -15800,7 +15825,7 @@ snapshots: '@react-three/drei@10.7.7(@react-three/fiber@9.6.1(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0))(@types/react@19.2.17)(@types/three@0.165.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@mediapipe/tasks-vision': 0.10.17 '@monogrid/gainmap-js': 3.4.0(three@0.165.0) '@react-three/fiber': 9.6.1(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0) @@ -15833,7 +15858,7 @@ snapshots: '@react-three/drei@10.7.7(@react-three/fiber@9.7.0(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0))(@types/react@19.2.17)(@types/three@0.165.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0)': dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.29.7 '@mediapipe/tasks-vision': 0.10.17 '@monogrid/gainmap-js': 3.4.0(three@0.165.0) '@react-three/fiber': 9.7.0(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.165.0) @@ -16325,7 +16350,7 @@ snapshots: debug: 4.4.3(supports-color@7.2.0) minimatch: 10.2.6 semver: 7.7.4 - tinyglobby: 0.2.15 + tinyglobby: 0.2.17 ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -16513,7 +16538,7 @@ snapshots: '@vue/shared': 3.5.32 estree-walker: 2.0.2 magic-string: 0.30.21 - postcss: 8.5.8 + postcss: 8.5.25 source-map-js: 1.2.1 '@vue/compiler-ssr@3.5.32': @@ -17361,7 +17386,7 @@ snapshots: get-tsconfig: 4.13.7 is-bun-module: 2.0.0 stable-hash: 0.0.5 - tinyglobby: 0.2.15 + tinyglobby: 0.2.17 unrs-resolver: 1.11.1 optionalDependencies: eslint-plugin-import: 2.32.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0) @@ -17652,10 +17677,6 @@ snapshots: dependencies: reusify: 1.1.0 - fdir@6.5.0(picomatch@4.0.4): - optionalDependencies: - picomatch: 4.0.4 - fdir@6.5.0(picomatch@4.0.5): optionalDependencies: picomatch: 4.0.5 @@ -20187,15 +20208,10 @@ snapshots: tinyexec@1.2.4: {} - tinyglobby@0.2.15: - dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 to-readable-stream@1.0.0: {} From 9ae37ac8cde656b87567257b76006fe076e9cbea Mon Sep 17 00:00:00 2001 From: Kris Baumgartner Date: Fri, 31 Jul 2026 13:58:34 -0400 Subject: [PATCH 5/5] Add trails++ to live list --- apps/website/package.json | 1 + pnpm-lock.yaml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/apps/website/package.json b/apps/website/package.json index 06230d50..b9e69095 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -153,6 +153,7 @@ "@demo/threejs-journey-portal": "workspace:*", "@demo/thunder-clouds": "workspace:*", "@demo/trails": "workspace:*", + "@demo/trails-r3f-9.7.0": "workspace:*", "@demo/transformcontrols-and-makedefault": "workspace:*", "@demo/transparent-aesop-bottles": "workspace:*", "@demo/trigger-meshes": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e4e27aca..f95eb194 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -467,6 +467,9 @@ importers: '@demo/trails': specifier: workspace:* version: link:../../demos/trails + '@demo/trails-r3f-9.7.0': + specifier: workspace:* + version: link:../../demos/trails-r3f-9.7.0 '@demo/transformcontrols-and-makedefault': specifier: workspace:* version: link:../../demos/transformcontrols-and-makedefault