diff --git a/app/Canvas.tsx b/app/Canvas.tsx index 3942ad87d7..e20317c96d 100644 --- a/app/Canvas.tsx +++ b/app/Canvas.tsx @@ -1,18 +1,19 @@ 'use client' import { useCanvasApi } from './canvas-state' -import CausticOverlay from '@/components/scenes/caustic/CausticOverlay' -import CausticScene from '@/components/scenes/caustic/CausticScene' +import FlowShieldControls from '@/components/scenes/flow-shield/FlowShieldControls' +import FlowShieldOverlay from '@/components/scenes/flow-shield/FlowShieldOverlay' +import FlowShieldScene from '@/components/scenes/flow-shield/FlowShieldScene' import { useSpring } from '@react-spring/web' import { PerformanceMonitor } from '@react-three/drei' import { Canvas, invalidate, useThree } from '@react-three/fiber' import { usePathname } from 'next/navigation' -import { memo, useEffect, useRef, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import * as THREE from 'three' THREE.Texture.DEFAULT_ANISOTROPY = 8 -const Scene = memo(CausticScene) +const Scene = FlowShieldScene export default function PmndrsCanvas() { const parentRef = useRef(null!) @@ -111,7 +112,8 @@ export default function PmndrsCanvas() { return ( <> - + + { if (!node) return diff --git a/app/tag-data.json b/app/tag-data.json index 43e1fd9149..278228c935 100644 --- a/app/tag-data.json +++ b/app/tag-data.json @@ -1,9 +1,13 @@ { + "react-three-fiber": 1, + "glsl": 1, + "shaders": 1, + "vfx": 1, "react": 2, "global-state": 2, - "valtio": 1, "zustand": 1, - "pmndrs": 1, + "valtio": 1, "xr": 1, - "react-three": 1 + "react-three": 1, + "pmndrs": 1 } diff --git a/components/scenes/flow-shield/FlowShieldControls.tsx b/components/scenes/flow-shield/FlowShieldControls.tsx new file mode 100644 index 0000000000..23eddfe525 --- /dev/null +++ b/components/scenes/flow-shield/FlowShieldControls.tsx @@ -0,0 +1,21 @@ +'use client' + +import { Leva } from 'leva' + +export default function FlowShieldControls({ show = true }: { show?: boolean }) { + return ( +
+ +
+ ) +} diff --git a/components/scenes/flow-shield/FlowShieldOverlay.tsx b/components/scenes/flow-shield/FlowShieldOverlay.tsx new file mode 100644 index 0000000000..bf84c35acf --- /dev/null +++ b/components/scenes/flow-shield/FlowShieldOverlay.tsx @@ -0,0 +1,63 @@ +'use client' + +import Logo from '@/data/logo.svg' +import Link from 'next/link' + +export default function FlowShieldOverlay({ show = true }: { show?: boolean }) { + return ( +
+
+

+ New Article +

+

+ Creating an Interactive Sci-Fi Shield +

+

+ Interactive sci-fi shielding with hit detection, procedural hexes, flowing noise, and + custom shader work in React Three Fiber. +

+

+ by Christian Oritz +

+
+ + Read article + +
+
+ +
+
+ +
+ pmndrs +
+ dev collective +
+
+
+
+ ) +} diff --git a/components/scenes/flow-shield/FlowShieldScene.tsx b/components/scenes/flow-shield/FlowShieldScene.tsx new file mode 100644 index 0000000000..90dd53d697 --- /dev/null +++ b/components/scenes/flow-shield/FlowShieldScene.tsx @@ -0,0 +1,39 @@ +'use client' + +import SceneContent from './src/components/playground/SceneContent' +import { useCanvasApi } from 'app/canvas-state' +import { memo, useEffect } from 'react' +import { useThree } from '@react-three/fiber' +import * as THREE from 'three' + +const MODE = 'Background' as const +const PRESET = 'default' as const + +export default memo(function FlowShieldScene({ + perfSucks: _perfSucks = false, +}: { + perfSucks?: boolean +}) { + const setIsLoaded = useCanvasApi((state) => state.setIsLoaded) + + useEffect(() => { + setIsLoaded(true) + }, [setIsLoaded]) + + return ( + <> + + + + ) +}) + +function SceneBackground() { + const scene = useThree((state) => state.scene) + + useEffect(() => { + scene.background = new THREE.Color('#050816') + }, [scene]) + + return null +} diff --git a/components/scenes/flow-shield/src/components/ForceShield/consts/index.ts b/components/scenes/flow-shield/src/components/ForceShield/consts/index.ts new file mode 100644 index 0000000000..3bc27017da --- /dev/null +++ b/components/scenes/flow-shield/src/components/ForceShield/consts/index.ts @@ -0,0 +1,54 @@ +import type { Preset } from '../../../types' + +export const MAX_HITS = 6 + +export const SHIELD_PRESETS: Record> = { + default: { + color: '#26aeff', + opacity: 0.76, + showHex: true, + hexScale: 3.0, + hexOpacity: 0.13, + edgeWidth: 0.06, + fresnelPower: 1.8, + fresnelStrength: 1.75, + flashSpeed: 0.6, + flashIntensity: 0.11, + noiseScale: 1.3, + noiseEdgeColor: '#26aeff', + noiseEdgeWidth: 0.02, + noiseEdgeIntensity: 10.0, + noiseEdgeSmoothness: 0.5, + flowScale: 2.4, + flowSpeed: 1.13, + flowIntensity: 4, + hitRingSpeed: 1.75, + hitRingWidth: 0.12, + hitMaxRadius: 0.85, + fadeStart: -1, + }, + droideka: { + hexScale: 3, + hexOpacity: 0.27, + showHex: false, + edgeWidth: 0.2, + fresnelPower: 1.8, + fadeStart: 1, + fresnelStrength: 1.75, + flashSpeed: 0.6, + color: '#5992f7', + noiseEdgeColor: '#7faaf5', + noiseEdgeWidth: 0.1, + noiseEdgeIntensity: 0.6, + noiseEdgeSmoothness: 0.5, + noiseScale: 1, + opacity: 0.29, + flashIntensity: 0.11, + flowScale: 6.2, + flowSpeed: 1.08, + flowIntensity: 4, + hitRingSpeed: 0.8, + hitRingWidth: 0.12, + hitMaxRadius: 2.1, + }, +} diff --git a/components/scenes/flow-shield/src/components/ForceShield/index.tsx b/components/scenes/flow-shield/src/components/ForceShield/index.tsx new file mode 100644 index 0000000000..5cbc436ba0 --- /dev/null +++ b/components/scenes/flow-shield/src/components/ForceShield/index.tsx @@ -0,0 +1,192 @@ +'use client' + +import { useRef, useMemo, useEffect, useCallback } from 'react' +import { useFrame } from '@react-three/fiber' +import type { ThreeEvent } from '@react-three/fiber' +import * as THREE from 'three' +import type { Preset } from '../../types' +import { MAX_HITS } from './consts' +import { useShieldControls } from './useShieldControls' +import { createShieldMaterial } from './shaderMaterial' + +interface ShieldProps { + isActive?: boolean + posYOverride?: number + preset?: Preset +} + +function Shield({ isActive = false, posYOverride, preset: _preset }: ShieldProps) { + const materialRef = useRef(null!) + const groupRef = useRef(null!) + const revealRef = useRef(1) + const timeRef = useRef(0) + const hitIdxRef = useRef(0) + const lifeRef = useRef(1.0) + const hitDamageRef = useRef(10) + + const [controls] = useShieldControls(lifeRef) + const { + debugAlwaysOn, + manualReveal, + revealProgress, + posX, + posY, + posZ, + scale, + color, + hexScale, + hexOpacity, + showHex, + edgeWidth, + fresnelPower, + fresnelStrength, + opacity, + fadeStart, + revealSpeed, + flashSpeed, + flashIntensity, + noiseScale, + noiseEdgeColor, + noiseEdgeWidth, + noiseEdgeIntensity, + noiseEdgeSmoothness, + flowScale, + flowSpeed, + flowIntensity, + hitRingSpeed, + hitRingWidth, + hitDuration, + hitIntensity, + hitImpactRadius, + hitMaxRadius, + hitDamage, + } = controls + + const visible = isActive || debugAlwaysOn + + // Keep hitDamageRef in sync so the click handler always sees latest value + hitDamageRef.current = hitDamage + + // ── Shader material ─────────────────────────────────────────────────────── + const shieldMaterial = useMemo(() => createShieldMaterial(), []) + + if (shieldMaterial && materialRef.current !== shieldMaterial) { + materialRef.current = shieldMaterial + } + + // ── Sync Leva → uniforms ────────────────────────────────────────────────── + useEffect(() => { + if (!materialRef.current) return + const u = materialRef.current.uniforms + u.uColor.value.set(color) + u.uHexScale.value = hexScale + u.uHexOpacity.value = hexOpacity + u.uShowHex.value = showHex ? 1.0 : 0.0 + u.uEdgeWidth.value = edgeWidth + u.uFresnelPower.value = fresnelPower + u.uFresnelStrength.value = fresnelStrength + u.uOpacity.value = opacity + u.uFadeStart.value = fadeStart + u.uFlashSpeed.value = flashSpeed + u.uFlashIntensity.value = flashIntensity + u.uNoiseScale.value = noiseScale + u.uNoiseEdgeColor.value.set(noiseEdgeColor) + u.uNoiseEdgeWidth.value = noiseEdgeWidth + u.uNoiseEdgeIntensity.value = noiseEdgeIntensity + u.uNoiseEdgeSmoothness.value = noiseEdgeSmoothness + u.uFlowScale.value = flowScale + u.uFlowSpeed.value = flowSpeed + u.uFlowIntensity.value = flowIntensity + u.uHitRingSpeed.value = hitRingSpeed + u.uHitRingWidth.value = hitRingWidth + u.uHitMaxRadius.value = hitMaxRadius + u.uHitDuration.value = hitDuration + u.uHitIntensity.value = hitIntensity + u.uHitImpactRadius.value = hitImpactRadius + }, [ + color, + hexScale, + hexOpacity, + showHex, + edgeWidth, + fresnelPower, + fresnelStrength, + opacity, + fadeStart, + flashSpeed, + flashIntensity, + noiseScale, + noiseEdgeColor, + noiseEdgeWidth, + noiseEdgeIntensity, + noiseEdgeSmoothness, + flowScale, + flowSpeed, + flowIntensity, + hitRingSpeed, + hitRingWidth, + hitMaxRadius, + hitDuration, + hitIntensity, + hitImpactRadius, + ]) + + // ── Click → spawn hit in ring buffer ───────────────────────────────────── + const handleClick = useCallback((e: ThreeEvent) => { + e.stopPropagation() + if (!materialRef.current) return + + // e.point is world-space; worldToLocal gives object space, + // matching vObjPos (position attribute) in the vertex shader. + const localPoint = e.object.worldToLocal(e.point.clone()) + + const idx = hitIdxRef.current % MAX_HITS + hitIdxRef.current++ + + const u = materialRef.current.uniforms + u.uHitPos.value[idx].copy(localPoint) + u.uHitTime.value[idx] = timeRef.current + + lifeRef.current = Math.max(0, lifeRef.current - hitDamageRef.current / 100) + }, []) + + // ── Per-frame ───────────────────────────────────────────────────────────── + useFrame((state, delta) => { + if (!materialRef.current) return + + timeRef.current = state.clock.elapsedTime + materialRef.current.uniforms.uTime.value = timeRef.current + materialRef.current.uniforms.uLife.value = lifeRef.current + + if (manualReveal) { + revealRef.current = revealProgress + } else { + const target = visible ? 0 : 1 + revealRef.current = THREE.MathUtils.lerp( + revealRef.current, + target, + 1 - Math.exp(-revealSpeed * delta) + ) + if (visible && revealRef.current < 0.005) revealRef.current = 0 + if (!visible && revealRef.current > 0.995) revealRef.current = 1 + } + + materialRef.current.uniforms.uReveal.value = revealRef.current + materialRef.current.visible = revealRef.current < 1 + }) + + return ( + + + + + + + ) +} + +export default Shield diff --git a/components/scenes/flow-shield/src/components/ForceShield/shaderMaterial.ts b/components/scenes/flow-shield/src/components/ForceShield/shaderMaterial.ts new file mode 100644 index 0000000000..85d0aef26c --- /dev/null +++ b/components/scenes/flow-shield/src/components/ForceShield/shaderMaterial.ts @@ -0,0 +1,287 @@ +import * as THREE from 'three' +import { MAX_HITS } from './consts' + +// ── Vertex shader ───────────────────────────────────────────────────────────── +export const vertexShader = /* glsl */ ` + varying vec3 vNormal; + varying vec3 vViewDir; + varying vec3 vObjPos; + + void main() { + vObjPos = position; + vNormal = normalize(normalMatrix * normal); + vec4 viewPosition = modelViewMatrix * vec4(position, 1.0); + vViewDir = normalize(-viewPosition.xyz); + gl_Position = projectionMatrix * viewPosition; + } +` + +// ── Fragment shader ─────────────────────────────────────────────────────────── +export const fragmentShader = /* glsl */ ` + #define MAX_HITS 6 + + uniform float uTime; + uniform vec3 uColor; + uniform float uLife; + uniform float uHexScale; + uniform float uEdgeWidth; + uniform float uFresnelPower; + uniform float uFresnelStrength; + uniform float uOpacity; + uniform float uReveal; + uniform float uFlashSpeed; + uniform float uFlashIntensity; + uniform float uNoiseScale; + uniform vec3 uNoiseEdgeColor; + uniform float uNoiseEdgeWidth; + uniform float uNoiseEdgeIntensity; + uniform float uNoiseEdgeSmoothness; + uniform float uHexOpacity; + uniform float uShowHex; + uniform float uFlowScale; + uniform float uFlowSpeed; + uniform float uFlowIntensity; + uniform vec3 uHitPos[MAX_HITS]; + uniform float uHitTime[MAX_HITS]; + uniform float uHitRingSpeed; + uniform float uHitRingWidth; + uniform float uHitMaxRadius; + uniform float uHitDuration; + uniform float uHitIntensity; + uniform float uHitImpactRadius; + uniform float uFadeStart; + + varying vec3 vNormal; + varying vec3 vViewDir; + varying vec3 vObjPos; + + // ── Simplex 3D noise ──────────────────────────────────────────────────────── + vec3 mod289v3(vec3 x){ return x - floor(x*(1./289.))*289.; } + vec4 mod289v4(vec4 x){ return x - floor(x*(1./289.))*289.; } + vec4 permute(vec4 x){ return mod289v4(((x*34.)+1.)*x); } + vec4 taylorInvSqrt(vec4 r){ return 1.79284291400159 - 0.85373472095314*r; } + + float snoise(vec3 v){ + const vec2 C = vec2(1./6., 1./3.); + const vec4 D = vec4(0., 0.5, 1., 2.); + vec3 i = floor(v + dot(v, C.yyy)); + vec3 x0 = v - i + dot(i, C.xxx); + vec3 g = step(x0.yzx, x0.xyz); + vec3 l = 1. - g; + vec3 i1 = min(g.xyz, l.zxy); + vec3 i2 = max(g.xyz, l.zxy); + vec3 x1 = x0 - i1 + C.xxx; + vec3 x2 = x0 - i2 + C.yyy; + vec3 x3 = x0 - D.yyy; + i = mod289v3(i); + vec4 p = permute(permute(permute( + i.z+vec4(0.,i1.z,i2.z,1.)) + +i.y+vec4(0.,i1.y,i2.y,1.)) + +i.x+vec4(0.,i1.x,i2.x,1.)); + float n_ = 0.142857142857; + vec3 ns = n_*D.wyz - D.xzx; + vec4 j = p - 49.*floor(p*ns.z*ns.z); + vec4 x_ = floor(j*ns.z); + vec4 y_ = floor(j - 7.*x_); + vec4 x = x_*ns.x + ns.yyyy; + vec4 y = y_*ns.x + ns.yyyy; + vec4 h = 1. - abs(x) - abs(y); + vec4 b0 = vec4(x.xy, y.xy); + vec4 b1 = vec4(x.zw, y.zw); + vec4 s0 = floor(b0)*2.+1.; + vec4 s1 = floor(b1)*2.+1.; + vec4 sh = -step(h, vec4(0.)); + vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy; + vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww; + vec3 p0 = vec3(a0.xy, h.x); + vec3 p1 = vec3(a0.zw, h.y); + vec3 p2 = vec3(a1.xy, h.z); + vec3 p3 = vec3(a1.zw, h.w); + vec4 norm = taylorInvSqrt(vec4(dot(p0,p0),dot(p1,p1),dot(p2,p2),dot(p3,p3))); + p0*=norm.x; p1*=norm.y; p2*=norm.z; p3*=norm.w; + vec4 m = max(0.6-vec4(dot(x0,x0),dot(x1,x1),dot(x2,x2),dot(x3,x3)),0.); + m = m*m; + return 42.*dot(m*m, vec4(dot(p0,x0),dot(p1,x1),dot(p2,x2),dot(p3,x3))); + } + + // ── Life color: uColor (full) → red (empty) ──────────────────────────────── + vec3 lifeColor(float life){ + return mix(vec3(1.0, 0.08, 0.04), uColor, life); + } + + // ── Hex grid ──────────────────────────────────────────────────────────────── + float hexPattern(vec2 p){ + p *= uHexScale; + const vec2 s = vec2(1., 1.7320508); + vec4 hC = floor(vec4(p, p-vec2(0.5,1.))/s.xyxy) + 0.5; + vec4 h = vec4(p-hC.xy*s, p-(hC.zw+0.5)*s); + vec2 cell = (dot(h.xy,h.xy) < dot(h.zw,h.zw)) ? h.xy : h.zw; + cell = abs(cell); + float d = max(dot(cell, s*0.5), cell.x); + return smoothstep(0.5-uEdgeWidth, 0.5, d); + } + + vec2 hexCellId(vec2 p){ + p *= uHexScale; + const vec2 s = vec2(1., 1.7320508); + vec4 hC = floor(vec4(p, p-vec2(0.5,1.))/s.xyxy) + 0.5; + vec4 h = vec4(p-hC.xy*s, p-(hC.zw+0.5)*s); + return (dot(h.xy,h.xy) < dot(h.zw,h.zw)) ? hC.xy : hC.zw+0.5; + } + + float cellFlash(vec2 cellId){ + float rnd = fract(sin(dot(cellId, vec2(127.1,311.7)))*43758.5453); + float phase = rnd * 6.2831; + float speed = 0.5 + rnd * 1.5; + return smoothstep(0.6, 1.0, sin(uTime*uFlashSpeed*speed+phase)) * uFlashIntensity; + } + + void main(){ + // ── Reveal / dissolve ───────────────────────────────────────────────────── + float noise = snoise(vObjPos * uNoiseScale) * 0.5 + 0.5; + float revealMask = smoothstep(uReveal - uNoiseEdgeWidth, uReveal, noise); + if (revealMask < 0.001) discard; + + float innerFade = mix(0.98, 0.15, uNoiseEdgeSmoothness); + float edgeLow = smoothstep(uReveal-uNoiseEdgeWidth, uReveal-uNoiseEdgeWidth*innerFade, noise); + float edgeHigh = smoothstep(uReveal-uNoiseEdgeWidth*0.15, uReveal, noise); + float revealEdge = edgeLow * (1.0 - edgeHigh); + + // ── Fresnel ─────────────────────────────────────────────────────────────── + float fresnel = pow(1.0 - dot(vNormal, vViewDir), uFresnelPower) * uFresnelStrength; + + // ── Flow noise ──────────────────────────────────────────────────────────── + float t = uTime * uFlowSpeed; + float fn1 = snoise(vObjPos*uFlowScale + vec3(t, t*0.6, t*0.4)); + float fn2 = snoise(vObjPos*uFlowScale*2.1 + vec3(-t*0.5, t*0.9, t*0.3)); + float flowNoise = (fn1*0.6 + fn2*0.4)*0.5 + 0.5; + + // ── Hex: cube-face select + seam fade ──────────────────────────────────── + // One projection per fragment (no overlap). Near the 45° seam + // between cube faces, hexFade drives hex opacity to 0 so neither + // a ghost grid NOR a hard discontinuity is visible. + // dominance = 1.0 → face center → full hex + // dominance ≈ 0.71 → 45° seam → hex fades to 0 + vec3 absN = abs(normalize(vObjPos)); + float dominance = max(absN.x, max(absN.y, absN.z)); + float hexFade = smoothstep(0.65, 0.85, dominance); + + vec2 faceUV; + if (absN.x >= absN.y && absN.x >= absN.z) { + faceUV = vObjPos.yz; // ±X face + } else if (absN.y >= absN.z) { + faceUV = vObjPos.xz; // ±Y face + } else { + faceUV = vObjPos.xy; // ±Z face + } + + float hex = hexPattern(faceUV) * hexFade; + vec2 cId = hexCellId(faceUV); + float flash = cellFlash(cId) * hexFade; + + // ── Hit ring buffer ─────────────────────────────────────────────────────── + // Each slot: expanding noisy ring + initial hex highlight zone + vec3 normPos = normalize(vObjPos); + float ringContrib = 0.0; + float hexHitBoost = 0.0; + + for (int i = 0; i < MAX_HITS; i++) { + float ht = uHitTime[i]; + float elapsed = uTime - ht; + + // isActive: slot valid AND within lifetime + float isActive = step(0.0, ht) + * step(0.0, elapsed) + * step(elapsed, uHitDuration); + + // Geodesic distance on the sphere surface (radians) + float dist = acos(clamp(dot(normPos, normalize(uHitPos[i])), -1.0, 1.0)); + + // Expanding ring — capped at uHitMaxRadius, fades as it reaches it + float ringR = min(elapsed * uHitRingSpeed, uHitMaxRadius); + float noiseD = snoise(normPos*5.0 + vec3(elapsed*2.0)) * 0.05; + float ring = smoothstep(uHitRingWidth, 0.0, abs(dist + noiseD - ringR)); + float fade = 1.0 - smoothstep(uHitDuration*0.5, uHitDuration, elapsed); + float radialFade = 1.0 - smoothstep(uHitMaxRadius*0.75, uHitMaxRadius, ringR); + ringContrib += ring * fade * radialFade * isActive; + + // Hex highlight: cells within impact radius flash on impact + float zone = smoothstep(uHitImpactRadius, 0.0, dist); + float zoneFade = 1.0 - smoothstep(0.0, uHitDuration*0.35, elapsed); + hexHitBoost += zone * zoneFade * isActive; + } + + ringContrib = min(ringContrib, 2.0); + hexHitBoost = min(hexHitBoost, 1.0); + + // ── Combine ─────────────────────────────────────────────────────────────── + vec3 lColor = lifeColor(uLife); + + float effectiveHexOpacity = (uHexOpacity + hexHitBoost * uHitIntensity) * uShowHex; + float intensity = hex * effectiveHexOpacity * (0.3 + fresnel*0.7) + fresnel*0.4 + flash * uShowHex; + + vec3 shieldColor = lColor * intensity * 2.0; + shieldColor += lColor * (flowNoise * fresnel * uFlowIntensity); + shieldColor += lColor * ringContrib * uHitIntensity; + + vec3 edgeColor = mix(uNoiseEdgeColor, lColor, 1.0 - uLife); + vec3 edgeGlow = edgeColor * revealEdge * uNoiseEdgeIntensity; + + float alpha = clamp(intensity*uOpacity*revealMask + revealEdge*uNoiseEdgeIntensity, 0.0, 1.0); + + // ── Bottom fade gradient ────────────────────────────────────────────────── + // vObjPos.y / 1.8 normalizes Y to [-1, 1] for the sphere (radius 1.8). + // smoothstep(-1, uFadeStart, normY): 0 at the bottom pole, 1 at uFadeStart. + float normY = vObjPos.y / 1.8; + alpha *= smoothstep(-1.0, uFadeStart, normY); + + gl_FragColor = vec4(shieldColor + edgeGlow, alpha); + } +` + +// ── Material factory ────────────────────────────────────────────────────────── +export function createShieldMaterial(): THREE.ShaderMaterial { + const hitPositions = Array.from({ length: MAX_HITS }, () => new THREE.Vector3(0, 1.8, 0)) + const hitTimes = new Array(MAX_HITS).fill(-999) + + return new THREE.ShaderMaterial({ + uniforms: { + uTime: { value: 0 }, + uColor: { value: new THREE.Color('#26aeff') }, + uLife: { value: 1.0 }, + uHexScale: { value: 3.0 }, + uEdgeWidth: { value: 0.06 }, + uFresnelPower: { value: 1.8 }, + uFresnelStrength: { value: 1.75 }, + uOpacity: { value: 0.76 }, + uReveal: { value: 1 }, + uFlashSpeed: { value: 0.6 }, + uFlashIntensity: { value: 0.11 }, + uNoiseScale: { value: 1.3 }, + uNoiseEdgeColor: { value: new THREE.Color('#26aeff') }, + uNoiseEdgeWidth: { value: 0.02 }, + uNoiseEdgeIntensity: { value: 10.0 }, + uNoiseEdgeSmoothness: { value: 0.5 }, + uHexOpacity: { value: 0.13 }, + uShowHex: { value: 1.0 }, + uFlowScale: { value: 2.4 }, + uFlowSpeed: { value: 1.13 }, + uFlowIntensity: { value: 4 }, + uHitPos: { value: hitPositions }, + uHitTime: { value: hitTimes }, + uHitRingSpeed: { value: 1.75 }, + uHitRingWidth: { value: 0.12 }, + uHitMaxRadius: { value: 0.85 }, + uHitDuration: { value: 1.8 }, + uHitIntensity: { value: 4.1 }, + uHitImpactRadius: { value: 0.3 }, + uFadeStart: { value: 0.0 }, + }, + vertexShader, + fragmentShader, + transparent: true, + depthWrite: false, + side: THREE.FrontSide, + blending: THREE.AdditiveBlending, + }) +} diff --git a/components/scenes/flow-shield/src/components/ForceShield/useShieldControls.ts b/components/scenes/flow-shield/src/components/ForceShield/useShieldControls.ts new file mode 100644 index 0000000000..db035a2b91 --- /dev/null +++ b/components/scenes/flow-shield/src/components/ForceShield/useShieldControls.ts @@ -0,0 +1,89 @@ +'use client' + +import type { MutableRefObject } from 'react' +import { button, useControls } from 'leva' + +const SHIELD_DEFAULTS = { + debugAlwaysOn: true, + manualReveal: false, + revealProgress: 0.0, + posX: 0, + posY: 2, + posZ: 0.2, + scale: 1, + color: '#26aeff', + hexScale: 3.0, + hexOpacity: 0.13, + showHex: true, + edgeWidth: 0.06, + fresnelPower: 1.8, + fresnelStrength: 1.75, + opacity: 0.76, + fadeStart: 0.0, + revealSpeed: 3.5, + flashSpeed: 0.6, + flashIntensity: 0.11, + noiseScale: 1.3, + noiseEdgeColor: '#26aeff', + noiseEdgeWidth: 0.02, + noiseEdgeIntensity: 10.0, + noiseEdgeSmoothness: 0.5, + flowScale: 2.4, + flowSpeed: 1.13, + flowIntensity: 4, + hitRingSpeed: 1.75, + hitRingWidth: 0.12, + hitMaxRadius: 0.85, + hitDuration: 1.8, + hitIntensity: 4.1, + hitImpactRadius: 0.3, + hitDamage: 5, +} as const + +export function useShieldControls(lifeRef: MutableRefObject) { + const [controls, setControls] = useControls(() => ({ + color: { value: SHIELD_DEFAULTS.color, label: 'Color' }, + opacity: { value: SHIELD_DEFAULTS.opacity, min: 0.2, max: 1, step: 0.01, label: 'Opacity' }, + showHex: { value: SHIELD_DEFAULTS.showHex, label: 'Hex Grid' }, + hexScale: { + value: SHIELD_DEFAULTS.hexScale, + min: 1, + max: 8, + step: 0.1, + label: 'Hex Size', + }, + hexOpacity: { + value: SHIELD_DEFAULTS.hexOpacity, + min: 0, + max: 0.5, + step: 0.01, + label: 'Hex Strength', + }, + flowSpeed: { + value: SHIELD_DEFAULTS.flowSpeed, + min: 0, + max: 2, + step: 0.01, + label: 'Flow Speed', + }, + flowIntensity: { + value: SHIELD_DEFAULTS.flowIntensity, + min: 0, + max: 4, + step: 0.05, + label: 'Flow Strength', + }, + hitIntensity: { + value: SHIELD_DEFAULTS.hitIntensity, + min: 0, + max: 8, + step: 0.1, + label: 'Hit Glow', + }, + 'Reset Life': button(() => { + lifeRef.current = 1.0 + }), + })) + + return [{ ...SHIELD_DEFAULTS, ...controls }, setControls] as const +} diff --git a/components/scenes/flow-shield/src/components/overlay/LoadingOverlay.module.css b/components/scenes/flow-shield/src/components/overlay/LoadingOverlay.module.css new file mode 100644 index 0000000000..1df1f46e40 --- /dev/null +++ b/components/scenes/flow-shield/src/components/overlay/LoadingOverlay.module.css @@ -0,0 +1,40 @@ +.overlay { + position: fixed; + inset: 0; + z-index: 52; + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; + background: rgba(14, 13, 12, 0.5); +} + +.content { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; +} + +.spinner { + width: 28px; + height: 28px; + border: 2px solid var(--color-border); + border-top-color: var(--color-accent); + border-radius: 50%; + animation: spin 0.8s linear infinite; +} + +.text { + font-family: var(--font-ibm-mono), monospace; + font-size: 12px; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--color-accent); +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} diff --git a/components/scenes/flow-shield/src/components/overlay/LoadingOverlay.tsx b/components/scenes/flow-shield/src/components/overlay/LoadingOverlay.tsx new file mode 100644 index 0000000000..8f63e6dd76 --- /dev/null +++ b/components/scenes/flow-shield/src/components/overlay/LoadingOverlay.tsx @@ -0,0 +1,20 @@ +'use client' + +import styles from './LoadingOverlay.module.css' + +interface LoadingOverlayProps { + visible: boolean +} + +export default function LoadingOverlay({ visible }: LoadingOverlayProps) { + if (!visible) return null + + return ( +
+
+
+ Loading Model... +
+
+ ) +} diff --git a/components/scenes/flow-shield/src/components/overlay/OverlayButtons.module.css b/components/scenes/flow-shield/src/components/overlay/OverlayButtons.module.css new file mode 100644 index 0000000000..3ad62829a9 --- /dev/null +++ b/components/scenes/flow-shield/src/components/overlay/OverlayButtons.module.css @@ -0,0 +1,88 @@ +.container { + position: fixed; + bottom: 74px; + right: 24px; + z-index: 55; + display: flex; + gap: 8px; + pointer-events: none; +} + +.btn { + display: flex; + align-items: center; + justify-content: center; + height: 36px; + min-width: 36px; + padding: 0; + border: 1px solid var(--overlay-border); + border-radius: 4px; + background: var(--overlay-bg); + color: var(--overlay-text); + cursor: pointer; + font-family: var(--font-ibm-mono), monospace; + font-size: 11px; + letter-spacing: 0.05em; + transition: + border-color 0.2s, + background 0.2s, + opacity 0.2s; + pointer-events: auto; +} + +.btn:hover { + border-color: var(--overlay-accent); +} + +.active { + border-color: var(--overlay-accent); + background: var(--overlay-surface); +} + +.inactive { + opacity: 0.6; +} + +/* Import GLB — wider button with text + icon */ +.importBtn { + composes: btn; + gap: 6px; + padding: 0 12px; + white-space: nowrap; +} + +.importBtn svg { + flex-shrink: 0; +} + +.importLabel { + font-size: 11px; + line-height: 1; +} + +.fileInput { + display: none; +} + +/* Preset selector buttons */ +.presetGroup { + display: flex; + gap: 4px; + pointer-events: auto; +} + +.presetBtn { + composes: btn; + padding: 0 10px; + white-space: nowrap; + font-size: 10px; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.separator { + width: 1px; + background: var(--overlay-border); + margin: 4px 0; + pointer-events: none; +} diff --git a/components/scenes/flow-shield/src/components/overlay/OverlayButtons.tsx b/components/scenes/flow-shield/src/components/overlay/OverlayButtons.tsx new file mode 100644 index 0000000000..682e56bd9b --- /dev/null +++ b/components/scenes/flow-shield/src/components/overlay/OverlayButtons.tsx @@ -0,0 +1,161 @@ +'use client' + +import { useRef } from 'react' +import { COLORS } from '../theme/theme' +import styles from './OverlayButtons.module.css' +import type { Preset } from '../../types' + +interface OverlayButtonsProps { + showGrid: boolean + onToggleGrid: () => void + hideLeva: boolean + onToggleLeva: () => void + hasGlb: boolean + onLoadGlb: (file: File) => void + onClearGlb: () => void + preset: Preset + onSetPreset: (p: Preset) => void +} + +export default function OverlayButtons({ + showGrid, + onToggleGrid, + hideLeva, + onToggleLeva, + hasGlb, + onLoadGlb, + onClearGlb, + preset, + onSetPreset, +}: OverlayButtonsProps) { + const fileInputRef = useRef(null) + + const handleFileChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0] + if (file) onLoadGlb(file) + e.target.value = '' + } + + return ( +
+ {/* Preset selector */} +
+ + +
+ +
+ + {/* Load GLB */} + + + + {/* Clear GLB */} + {hasGlb && ( + + )} + + {/* Toggle Grid */} + + + {/* Toggle Leva Controls */} + +
+ ) +} diff --git a/components/scenes/flow-shield/src/components/overlay/OverlayHeader.tsx b/components/scenes/flow-shield/src/components/overlay/OverlayHeader.tsx new file mode 100644 index 0000000000..3874fc009e --- /dev/null +++ b/components/scenes/flow-shield/src/components/overlay/OverlayHeader.tsx @@ -0,0 +1,81 @@ +'use client' + +export default function OverlayHeader() { + const now = new Date() + const dateStr = now.toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: '2-digit', + }) + + return ( +
+ {/* Classification label */} +
+ SHIELD VFX WITH HIT DETECTION +
+ + {/* Horizontal rule */} +
+ + {/* Title */} +

+ SHIELD VFX +

+ + {/* Designation subtitle */} +
+ 3D PLAYGROUND ENVIRONMENT +
+ + {/* Rev / Date metadata */} +
+ V 1.01 — {dateStr.toUpperCase()} — R3F / DREI / LEVA +
+
+ ) +} diff --git a/components/scenes/flow-shield/src/components/overlay/UIOverlay.tsx b/components/scenes/flow-shield/src/components/overlay/UIOverlay.tsx new file mode 100644 index 0000000000..91f769aa27 --- /dev/null +++ b/components/scenes/flow-shield/src/components/overlay/UIOverlay.tsx @@ -0,0 +1,18 @@ +'use client' + +import OverlayHeader from './OverlayHeader' + +export default function UIOverlay() { + return ( +
+ +
+ ) +} diff --git a/components/scenes/flow-shield/src/components/playground/DemoSphere.tsx b/components/scenes/flow-shield/src/components/playground/DemoSphere.tsx new file mode 100644 index 0000000000..8f856dd80b --- /dev/null +++ b/components/scenes/flow-shield/src/components/playground/DemoSphere.tsx @@ -0,0 +1,26 @@ +'use client' + +import { useRef } from 'react' +import { useFrame } from '@react-three/fiber' +import type { Mesh } from 'three' +import type { SceneMode } from './SceneContent' + +export default function DemoSphere({ mode }: { mode: SceneMode }) { + const meshRef = useRef(null!) + const wireframe = mode === 'Frame' + + useFrame(() => { + meshRef.current.position.y = 1 + Math.sin(Date.now() * 0.001) * 0.2 + }) + + return ( + + + {wireframe ? ( + + ) : ( + + )} + + ) +} diff --git a/components/scenes/flow-shield/src/components/playground/Droideka.tsx b/components/scenes/flow-shield/src/components/playground/Droideka.tsx new file mode 100644 index 0000000000..0cf8aad5d4 --- /dev/null +++ b/components/scenes/flow-shield/src/components/playground/Droideka.tsx @@ -0,0 +1,780 @@ +/* +Auto-generated by: https://github.com/pmndrs/gltfjsx +Author: PokCat (https://sketchfab.com/PokCat) +License: CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/) +Source: https://sketchfab.com/3d-models/droideka-42b2a42130f94d73a29eda6ecfdce98f +Title: Droideka +*/ +// @ts-nocheck + +import { useEffect } from 'react' +import { useGLTF } from '@react-three/drei' + +const DROIDEKA_URL = '/static/models/flow-shield/droideka.glb' + +export function Droideka({ onLoaded }: { onLoaded?: () => void }) { + const { nodes, materials } = useGLTF(DROIDEKA_URL) + + useEffect(() => { + onLoaded?.() + }, [onLoaded]) + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + +useGLTF.preload(DROIDEKA_URL) diff --git a/components/scenes/flow-shield/src/components/playground/GlbModel.tsx b/components/scenes/flow-shield/src/components/playground/GlbModel.tsx new file mode 100644 index 0000000000..59f6a188cd --- /dev/null +++ b/components/scenes/flow-shield/src/components/playground/GlbModel.tsx @@ -0,0 +1,91 @@ +'use client' + +import { useMemo, useEffect } from 'react' +import { useGLTF } from '@react-three/drei' +import { useControls, folder } from 'leva' +import { Box3, Vector3, MathUtils, Mesh } from 'three' +import type { Material } from 'three' +import type { SceneMode } from './SceneContent' + +interface GlbModelProps { + url: string + onLoaded?: () => void + mode: SceneMode +} + +export default function GlbModel({ url, onLoaded, mode }: GlbModelProps) { + const { scene } = useGLTF(url) + const wireframe = mode === 'Frame' + + useEffect(() => { + onLoaded?.() + }, [scene, onLoaded]) + + const autoScale = useMemo(() => { + const box = new Box3().setFromObject(scene) + const center = new Vector3() + box.getCenter(center) + scene.position.sub(center) + + const size = new Vector3() + box.getSize(size) + const maxDim = Math.max(size.x, size.y, size.z) + const s = maxDim > 0 ? 3 / maxDim : 1 + + scene.scale.setScalar(s) + + scene.traverse((child) => { + if (!(child instanceof Mesh)) return + child.castShadow = true + child.receiveShadow = true + }) + + return s + }, [scene]) + + useEffect(() => { + scene.traverse((child) => { + if (!(child instanceof Mesh)) return + + const materials = Array.isArray(child.material) ? child.material : [child.material] + materials.forEach((material) => { + setWireframe(material, wireframe) + }) + }) + }, [scene, wireframe]) + + const { scale, posX, posY, posZ, rotX, rotY, rotZ } = useControls('Model', { + Transform: folder( + { + scale: { value: autoScale, min: 0.01, max: 20, step: 0.01, label: 'Scale' }, + posX: { value: 0, min: -10, max: 10, step: 0.1, label: 'Pos X' }, + posY: { value: 1, min: -10, max: 10, step: 0.1, label: 'Pos Y' }, + posZ: { value: 0, min: -10, max: 10, step: 0.1, label: 'Pos Z' }, + }, + { collapsed: false } + ), + Rotation: folder( + { + rotX: { value: 0, min: -180, max: 180, step: 1, label: 'Rot X' }, + rotY: { value: 0, min: -180, max: 180, step: 1, label: 'Rot Y' }, + rotZ: { value: 0, min: -180, max: 180, step: 1, label: 'Rot Z' }, + }, + { collapsed: true } + ), + }) + + return ( + + + + ) +} + +function setWireframe(material: Material, wireframe: boolean) { + if ('wireframe' in material) { + material.wireframe = wireframe + } +} diff --git a/components/scenes/flow-shield/src/components/playground/GridFloor.tsx b/components/scenes/flow-shield/src/components/playground/GridFloor.tsx new file mode 100644 index 0000000000..a17927ab4c --- /dev/null +++ b/components/scenes/flow-shield/src/components/playground/GridFloor.tsx @@ -0,0 +1,75 @@ +'use client' + +import { useMemo } from 'react' +import * as THREE from 'three' +import { Grid, MeshReflectorMaterial } from '@react-three/drei' +import { COLORS } from '../theme/theme' +import type { SceneMode } from './SceneContent' + +function useRadialAlphaMap(size: number, innerStop: number, outerStop: number) { + return useMemo(() => { + const canvas = document.createElement('canvas') + canvas.width = size + canvas.height = size + const ctx = canvas.getContext('2d')! + + const half = size / 2 + const gradient = ctx.createRadialGradient(half, half, 0, half, half, half) + gradient.addColorStop(0, '#ffffff') + gradient.addColorStop(Math.min(innerStop, 0.99), '#ffffff') + gradient.addColorStop(Math.min(outerStop, 1), '#000000') + gradient.addColorStop(1, '#000000') + + ctx.fillStyle = gradient + ctx.fillRect(0, 0, size, size) + + const texture = new THREE.CanvasTexture(canvas) + texture.needsUpdate = true + return texture + }, [size, innerStop, outerStop]) +} + +export default function GridFloor({ mode }: { mode: SceneMode }) { + const alphaMap = useRadialAlphaMap(512, 0.16, 0.66) + const showFloor = mode === 'Background' + + return ( + + {/* Reflective floor plane */} + {showFloor && ( + + + + + )} + + {/* Visual grid rendered on top */} + + + ) +} diff --git a/components/scenes/flow-shield/src/components/playground/PlaygroundCanvas.tsx b/components/scenes/flow-shield/src/components/playground/PlaygroundCanvas.tsx new file mode 100644 index 0000000000..760a67b6f0 --- /dev/null +++ b/components/scenes/flow-shield/src/components/playground/PlaygroundCanvas.tsx @@ -0,0 +1,93 @@ +'use client' + +import { useState, useCallback, useRef } from 'react' +import { Canvas } from '@react-three/fiber' +import { Leva, useControls } from 'leva' +import { LEVA_THEME } from '../theme/theme' +import SceneContent from './SceneContent' +import type { SceneMode } from './SceneContent' +import UIOverlay from '../overlay/UIOverlay' +import OverlayButtons, { type Preset } from '../overlay/OverlayButtons' +import LoadingOverlay from '../overlay/LoadingOverlay' + +export default function PlaygroundCanvas() { + const [showGrid, setShowGrid] = useState(true) + const [hideLeva, setHideLeva] = useState(false) + const [glbUrl, setGlbUrl] = useState(null) + const [preset, setPreset] = useState('default') + const [isLoadingModel, setIsLoadingModel] = useState(false) + const glbUrlRef = useRef(null) + + const handleLoadGlb = useCallback((file: File) => { + if (glbUrlRef.current) URL.revokeObjectURL(glbUrlRef.current) + const url = URL.createObjectURL(file) + glbUrlRef.current = url + setIsLoadingModel(true) + setGlbUrl(url) + }, []) + + const handleModelLoaded = useCallback(() => { + setIsLoadingModel(false) + }, []) + + const handleClearGlb = useCallback(() => { + if (glbUrlRef.current) URL.revokeObjectURL(glbUrlRef.current) + glbUrlRef.current = null + setGlbUrl(null) + }, []) + + const { mode } = useControls( + 'Scene', + { + mode: { + value: 'Background' as SceneMode, + options: ['Background', 'Frame'] as SceneMode[], + label: 'Mode', + }, + }, + { collapsed: true } + ) + + return ( + <> +
diff --git a/layouts/PostLayout.tsx b/layouts/PostLayout.tsx index 7d9ac2a5ac..90f00ad08a 100644 --- a/layouts/PostLayout.tsx +++ b/layouts/PostLayout.tsx @@ -87,6 +87,19 @@ export default function PostLayout({ content, authorDetails, next, prev, childre )} +
YouTube
+
+ {author.youtube && ( + + {author.youtube + .replace('https://www.youtube.com/', '') + .replace('https://youtube.com/', '')} + + )} +
))} diff --git a/next.config.js b/next.config.js index 83fdbb99f9..8cc307ba9a 100644 --- a/next.config.js +++ b/next.config.js @@ -19,7 +19,7 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({ const ContentSecurityPolicy = ` style-src 'self' 'unsafe-inline'; img-src * blob: data:; - media-src *.s3.amazonaws.com; + media-src 'self' *.s3.amazonaws.com; font-src 'self'; frame-src giscus.app ` diff --git a/package.json b/package.json index f3e91a879d..e5cbad1a0f 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "gray-matter": "^4.0.3", "hast-util-from-html-isomorphic": "^2.0.0", "image-size": "2.0.2", + "leva": "^0.10.1", "next": "15.5.12", "next-contentlayer2": "0.5.8", "next-themes": "^0.4.6", diff --git a/public/static/blog/creating-flow-shield/Fresnel.mp4 b/public/static/blog/creating-flow-shield/Fresnel.mp4 new file mode 100644 index 0000000000..a6366f5a41 Binary files /dev/null and b/public/static/blog/creating-flow-shield/Fresnel.mp4 differ diff --git a/public/static/blog/creating-flow-shield/Shield-Intro-480.mp4 b/public/static/blog/creating-flow-shield/Shield-Intro-480.mp4 new file mode 100644 index 0000000000..91f5fe3074 Binary files /dev/null and b/public/static/blog/creating-flow-shield/Shield-Intro-480.mp4 differ diff --git a/public/static/blog/creating-flow-shield/flicker.mp4 b/public/static/blog/creating-flow-shield/flicker.mp4 new file mode 100644 index 0000000000..8f5a90cca6 Binary files /dev/null and b/public/static/blog/creating-flow-shield/flicker.mp4 differ diff --git a/public/static/blog/creating-flow-shield/flow-field-noise.mp4 b/public/static/blog/creating-flow-shield/flow-field-noise.mp4 new file mode 100644 index 0000000000..c705b8d4ee Binary files /dev/null and b/public/static/blog/creating-flow-shield/flow-field-noise.mp4 differ diff --git a/public/static/blog/creating-flow-shield/flow-shield-social.jpg b/public/static/blog/creating-flow-shield/flow-shield-social.jpg new file mode 100644 index 0000000000..092359023f Binary files /dev/null and b/public/static/blog/creating-flow-shield/flow-shield-social.jpg differ diff --git a/public/static/blog/creating-flow-shield/full-tweeking-cut.mp4 b/public/static/blog/creating-flow-shield/full-tweeking-cut.mp4 new file mode 100644 index 0000000000..8c4285257a Binary files /dev/null and b/public/static/blog/creating-flow-shield/full-tweeking-cut.mp4 differ diff --git a/public/static/blog/creating-flow-shield/hex-system.mp4 b/public/static/blog/creating-flow-shield/hex-system.mp4 new file mode 100644 index 0000000000..233b61d653 Binary files /dev/null and b/public/static/blog/creating-flow-shield/hex-system.mp4 differ diff --git a/public/static/blog/creating-flow-shield/hit-no-noise.mp4 b/public/static/blog/creating-flow-shield/hit-no-noise.mp4 new file mode 100644 index 0000000000..8491640b58 Binary files /dev/null and b/public/static/blog/creating-flow-shield/hit-no-noise.mp4 differ diff --git a/public/static/blog/creating-flow-shield/hit-noise.mp4 b/public/static/blog/creating-flow-shield/hit-noise.mp4 new file mode 100644 index 0000000000..8666a99471 Binary files /dev/null and b/public/static/blog/creating-flow-shield/hit-noise.mp4 differ diff --git a/public/static/blog/creating-flow-shield/image 1.webp b/public/static/blog/creating-flow-shield/image 1.webp new file mode 100644 index 0000000000..3a3c97a946 Binary files /dev/null and b/public/static/blog/creating-flow-shield/image 1.webp differ diff --git a/public/static/blog/creating-flow-shield/image 2.webp b/public/static/blog/creating-flow-shield/image 2.webp new file mode 100644 index 0000000000..af02afa56d Binary files /dev/null and b/public/static/blog/creating-flow-shield/image 2.webp differ diff --git a/public/static/blog/creating-flow-shield/image 3.webp b/public/static/blog/creating-flow-shield/image 3.webp new file mode 100644 index 0000000000..bcfb09e793 Binary files /dev/null and b/public/static/blog/creating-flow-shield/image 3.webp differ diff --git a/public/static/blog/creating-flow-shield/image 4.webp b/public/static/blog/creating-flow-shield/image 4.webp new file mode 100644 index 0000000000..6b98c82157 Binary files /dev/null and b/public/static/blog/creating-flow-shield/image 4.webp differ diff --git a/public/static/blog/creating-flow-shield/image 5.webp b/public/static/blog/creating-flow-shield/image 5.webp new file mode 100644 index 0000000000..00ae2bf586 Binary files /dev/null and b/public/static/blog/creating-flow-shield/image 5.webp differ diff --git a/public/static/blog/creating-flow-shield/image.webp b/public/static/blog/creating-flow-shield/image.webp new file mode 100644 index 0000000000..9cf8fd61d4 Binary files /dev/null and b/public/static/blog/creating-flow-shield/image.webp differ diff --git a/public/static/blog/creating-flow-shield/impact.mp4 b/public/static/blog/creating-flow-shield/impact.mp4 new file mode 100644 index 0000000000..7b190e4283 Binary files /dev/null and b/public/static/blog/creating-flow-shield/impact.mp4 differ diff --git a/public/static/blog/creating-flow-shield/life-color.mp4 b/public/static/blog/creating-flow-shield/life-color.mp4 new file mode 100644 index 0000000000..e207d08774 Binary files /dev/null and b/public/static/blog/creating-flow-shield/life-color.mp4 differ diff --git a/public/static/blog/creating-flow-shield/negative-1-layer.mp4 b/public/static/blog/creating-flow-shield/negative-1-layer.mp4 new file mode 100644 index 0000000000..f3c12e9ec2 Binary files /dev/null and b/public/static/blog/creating-flow-shield/negative-1-layer.mp4 differ diff --git a/public/static/blog/creating-flow-shield/negative-2-layer.mp4 b/public/static/blog/creating-flow-shield/negative-2-layer.mp4 new file mode 100644 index 0000000000..1b72af797c Binary files /dev/null and b/public/static/blog/creating-flow-shield/negative-2-layer.mp4 differ diff --git a/public/static/blog/creating-flow-shield/reveal-final-effect.mp4 b/public/static/blog/creating-flow-shield/reveal-final-effect.mp4 new file mode 100644 index 0000000000..c5d2ab3a6a Binary files /dev/null and b/public/static/blog/creating-flow-shield/reveal-final-effect.mp4 differ diff --git a/public/static/images/cortiz.webp b/public/static/images/cortiz.webp new file mode 100644 index 0000000000..8effb6d526 Binary files /dev/null and b/public/static/images/cortiz.webp differ diff --git a/public/static/models/flow-shield/droideka.glb b/public/static/models/flow-shield/droideka.glb new file mode 100644 index 0000000000..f26e7baed9 Binary files /dev/null and b/public/static/models/flow-shield/droideka.glb differ diff --git a/yarn.lock b/yarn.lock index 0153df2cfc..bd3416dd57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2470,7 +2470,7 @@ __metadata: languageName: node linkType: hard -"@floating-ui/react-dom@npm:^2.1.2": +"@floating-ui/react-dom@npm:^2.0.0, @floating-ui/react-dom@npm:^2.1.2": version: 2.1.8 resolution: "@floating-ui/react-dom@npm:2.1.8" dependencies: @@ -3465,6 +3465,32 @@ __metadata: languageName: node linkType: hard +"@radix-ui/primitive@npm:1.1.3": + version: 1.1.3 + resolution: "@radix-ui/primitive@npm:1.1.3" + checksum: ee27abbff0d6d305816e9314655eb35e72478ba47416bc9d5cb0581728be35e3408cfc0748313837561d635f0cb7dfaae26e61831f0e16c0fd7d669a612f2cb0 + languageName: node + linkType: hard + +"@radix-ui/react-arrow@npm:1.1.7": + version: 1.1.7 + resolution: "@radix-ui/react-arrow@npm:1.1.7" + dependencies: + "@radix-ui/react-primitive": 2.1.3 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 6cdf74f06090f8994cdf6d3935a44ea3ac309163a4f59c476482c4907e8e0775f224045030abf10fa4f9e1cb7743db034429249b9e59354988e247eeb0f4fdcf + languageName: node + linkType: hard + "@radix-ui/react-compose-refs@npm:1.1.0": version: 1.1.0 resolution: "@radix-ui/react-compose-refs@npm:1.1.0" @@ -3478,6 +3504,118 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-compose-refs@npm:1.1.2": + version: 1.1.2 + resolution: "@radix-ui/react-compose-refs@npm:1.1.2" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 9a91f0213014ffa40c5b8aae4debb993be5654217e504e35aa7422887eb2d114486d37e53c482d0fffb00cd44f51b5269fcdf397b280c71666fa11b7f32f165d + languageName: node + linkType: hard + +"@radix-ui/react-context@npm:1.1.2": + version: 1.1.2 + resolution: "@radix-ui/react-context@npm:1.1.2" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 6d08437f23df362672259e535ae463e70bf7a0069f09bfa06c983a5a90e15250bde19da1d63ef8e3da06df1e1b4f92afa9d28ca6aa0297bb1c8aaf6ca83d28c5 + languageName: node + linkType: hard + +"@radix-ui/react-dismissable-layer@npm:1.1.11": + version: 1.1.11 + resolution: "@radix-ui/react-dismissable-layer@npm:1.1.11" + dependencies: + "@radix-ui/primitive": 1.1.3 + "@radix-ui/react-compose-refs": 1.1.2 + "@radix-ui/react-primitive": 2.1.3 + "@radix-ui/react-use-callback-ref": 1.1.1 + "@radix-ui/react-use-escape-keydown": 1.1.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 8fc9f027c9f68940c69c9cc117c43e1313d1a78ae4109cf809868b82837e5e2a7d410adf78e97328d9d5a080a63e399918414985658ab029a8df7d775af23b68 + languageName: node + linkType: hard + +"@radix-ui/react-id@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-id@npm:1.1.1" + dependencies: + "@radix-ui/react-use-layout-effect": 1.1.1 + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 8d68e200778eb3038906870fc869b3d881f4a46715fb20cddd9c76cba42fdaaa4810a3365b6ec2daf0f185b9201fc99d009167f59c7921bc3a139722c2e976db + languageName: node + linkType: hard + +"@radix-ui/react-popper@npm:1.2.8": + version: 1.2.8 + resolution: "@radix-ui/react-popper@npm:1.2.8" + dependencies: + "@floating-ui/react-dom": ^2.0.0 + "@radix-ui/react-arrow": 1.1.7 + "@radix-ui/react-compose-refs": 1.1.2 + "@radix-ui/react-context": 1.1.2 + "@radix-ui/react-primitive": 2.1.3 + "@radix-ui/react-use-callback-ref": 1.1.1 + "@radix-ui/react-use-layout-effect": 1.1.1 + "@radix-ui/react-use-rect": 1.1.1 + "@radix-ui/react-use-size": 1.1.1 + "@radix-ui/rect": 1.1.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 51370bc4868542ab8b807da0b43158d699715c13f5e31a5236861a172b75eb68ab9556945bbddbc0cb408bcc8da4f4569f42d657b19925e89501797e4eb3738b + languageName: node + linkType: hard + +"@radix-ui/react-portal@npm:1.1.9": + version: 1.1.9 + resolution: "@radix-ui/react-portal@npm:1.1.9" + dependencies: + "@radix-ui/react-primitive": 2.1.3 + "@radix-ui/react-use-layout-effect": 1.1.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: bd6be39bf021d5c917e2474ecba411e2625171f7ef96862b9af04bbd68833bb3662a7f1fbdeb5a7a237111b10e811e76d2cd03e957dadd6e668ef16541bfbd68 + languageName: node + linkType: hard + "@radix-ui/react-portal@npm:^1.0.1": version: 1.1.1 resolution: "@radix-ui/react-portal@npm:1.1.1" @@ -3498,6 +3636,46 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-portal@npm:^1.1.4": + version: 1.1.10 + resolution: "@radix-ui/react-portal@npm:1.1.10" + dependencies: + "@radix-ui/react-primitive": 2.1.4 + "@radix-ui/react-use-layout-effect": 1.1.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: d058041416f70ff24aebd06be74681a67591c8eac4e8bd2bfc8db2995f87b8fd89c9c12c002288ccf91d70395bfc21f5462cc564662e986686705b84d666bf09 + languageName: node + linkType: hard + +"@radix-ui/react-presence@npm:1.1.5": + version: 1.1.5 + resolution: "@radix-ui/react-presence@npm:1.1.5" + dependencies: + "@radix-ui/react-compose-refs": 1.1.2 + "@radix-ui/react-use-layout-effect": 1.1.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 05f1b8e80d3d878efab44304ce55d0b9e6c7050e8345f9da95d0597a716121fb2467c3247c847c51a6cb27edd00e86ac36b2635e4c00ea79d91cfc26c930da81 + languageName: node + linkType: hard + "@radix-ui/react-primitive@npm:2.0.0": version: 2.0.0 resolution: "@radix-ui/react-primitive@npm:2.0.0" @@ -3517,6 +3695,44 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-primitive@npm:2.1.3": + version: 2.1.3 + resolution: "@radix-ui/react-primitive@npm:2.1.3" + dependencies: + "@radix-ui/react-slot": 1.2.3 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 01f82e4bad76b57767198762c905e5bcea04f4f52129749791e31adfcb1b36f6fdc89c73c40017d812b6e25e4ac925d837214bb280cfeaa5dc383457ce6940b0 + languageName: node + linkType: hard + +"@radix-ui/react-primitive@npm:2.1.4": + version: 2.1.4 + resolution: "@radix-ui/react-primitive@npm:2.1.4" + dependencies: + "@radix-ui/react-slot": 1.2.4 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 8b4cf865b4d4d135c9c6768856fdad0f62cbe43a2819471eb682061d2076222c3368ae0b771516426b264afcaf0a0032943408b3a789cbb77273152dd4a06d05 + languageName: node + linkType: hard + "@radix-ui/react-slot@npm:1.1.0": version: 1.1.0 resolution: "@radix-ui/react-slot@npm:1.1.0" @@ -3532,6 +3748,125 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-slot@npm:1.2.3": + version: 1.2.3 + resolution: "@radix-ui/react-slot@npm:1.2.3" + dependencies: + "@radix-ui/react-compose-refs": 1.1.2 + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 2731089e15477dd5eef98a5757c36113dd932d0c52ff05123cd89f05f0412e95e5b205229185d1cd705cda4a674a838479cce2b3b46ed903f82f5d23d9e3f3c2 + languageName: node + linkType: hard + +"@radix-ui/react-slot@npm:1.2.4": + version: 1.2.4 + resolution: "@radix-ui/react-slot@npm:1.2.4" + dependencies: + "@radix-ui/react-compose-refs": 1.1.2 + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 6e1cda512f649ca9df8e746a5059c69aa7539b43870ff6def360103590d68d1ea3adf8a216092107b3e6476d55a3d71707e162b5be04574126bcc6fdfffefe6a + languageName: node + linkType: hard + +"@radix-ui/react-tooltip@npm:^1.1.8": + version: 1.2.8 + resolution: "@radix-ui/react-tooltip@npm:1.2.8" + dependencies: + "@radix-ui/primitive": 1.1.3 + "@radix-ui/react-compose-refs": 1.1.2 + "@radix-ui/react-context": 1.1.2 + "@radix-ui/react-dismissable-layer": 1.1.11 + "@radix-ui/react-id": 1.1.1 + "@radix-ui/react-popper": 1.2.8 + "@radix-ui/react-portal": 1.1.9 + "@radix-ui/react-presence": 1.1.5 + "@radix-ui/react-primitive": 2.1.3 + "@radix-ui/react-slot": 1.2.3 + "@radix-ui/react-use-controllable-state": 1.2.2 + "@radix-ui/react-visually-hidden": 1.2.3 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: acd2606793f05e77c0fe1cc98d97bc1e9d07cdfd48fdebd23e4555676b9acaafbf70e518fbde943a9304cd086d85c2c78bcb9470d9128c2dc8cb61b02531311e + languageName: node + linkType: hard + +"@radix-ui/react-use-callback-ref@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-callback-ref@npm:1.1.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: cde8c40f1d4e79e6e71470218163a746858304bad03758ac84dc1f94247a046478e8e397518350c8d6609c84b7e78565441d7505bb3ed573afce82cfdcd19faf + languageName: node + linkType: hard + +"@radix-ui/react-use-controllable-state@npm:1.2.2": + version: 1.2.2 + resolution: "@radix-ui/react-use-controllable-state@npm:1.2.2" + dependencies: + "@radix-ui/react-use-effect-event": 0.0.2 + "@radix-ui/react-use-layout-effect": 1.1.1 + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: b438ee199d0630bf95eaafe8bf4bce219e73b371cfc8465f47548bfa4ee231f1134b5c6696b242890a01a0fd25fa34a7b172346bbfc5ee25cfb28b3881b1dc92 + languageName: node + linkType: hard + +"@radix-ui/react-use-effect-event@npm:0.0.2": + version: 0.0.2 + resolution: "@radix-ui/react-use-effect-event@npm:0.0.2" + dependencies: + "@radix-ui/react-use-layout-effect": 1.1.1 + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 5a1950a30a399ea7e4b98154da9f536737a610de80189b7aacd4f064a89a3cd0d2a48571d527435227252e72e872bdb544ff6ffcfbdd02de2efd011be4aaa902 + languageName: node + linkType: hard + +"@radix-ui/react-use-escape-keydown@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-escape-keydown@npm:1.1.1" + dependencies: + "@radix-ui/react-use-callback-ref": 1.1.1 + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 0eb0756c2c55ddcde9ff01446ab01c085ab2bf799173e97db7ef5f85126f9e8600225570801a1f64740e6d14c39ffe8eed7c14d29737345a5797f4622ac96f6f + languageName: node + linkType: hard + "@radix-ui/react-use-layout-effect@npm:1.1.0": version: 1.1.0 resolution: "@radix-ui/react-use-layout-effect@npm:1.1.0" @@ -3545,6 +3880,75 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-use-layout-effect@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-layout-effect@npm:1.1.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: bad2ba4f206e6255263582bedfb7868773c400836f9a1b423c0b464ffe4a17e13d3f306d1ce19cf7a19a492e9d0e49747464f2656451bb7c6a99f5a57bd34de2 + languageName: node + linkType: hard + +"@radix-ui/react-use-rect@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-rect@npm:1.1.1" + dependencies: + "@radix-ui/rect": 1.1.1 + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 116461bebc49472f7497e66a9bd413541181b3d00c5e0aaeef45d790dc1fbd7c8dcea80b169ea273306228b9a3c2b70067e902d1fd5004b3057e3bbe35b9d55d + languageName: node + linkType: hard + +"@radix-ui/react-use-size@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-size@npm:1.1.1" + dependencies: + "@radix-ui/react-use-layout-effect": 1.1.1 + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 64e61f65feb67ffc80e1fc4a8d5e32480fb6d68475e2640377e021178dead101568cba5f936c9c33e6c142c7cf2fb5d76ad7b23ef80e556ba142d56cf306147b + languageName: node + linkType: hard + +"@radix-ui/react-visually-hidden@npm:1.2.3": + version: 1.2.3 + resolution: "@radix-ui/react-visually-hidden@npm:1.2.3" + dependencies: + "@radix-ui/react-primitive": 2.1.3 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 42296bde1ddf4af4e7445e914c35d6bc8406d6ede49f0a959a553e75b3ed21da09fda80a81c48d8ec058ed8129ce7137499d02ee26f90f0d3eaa2417922d6509 + languageName: node + linkType: hard + +"@radix-ui/rect@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/rect@npm:1.1.1" + checksum: c1c111edeab70b14a735bca43601de6468c792482864b766ac8940b43321492e5c0ae62f92b156cecdc9265ec3c680c32b3fa0c8a90b5e796923a9af13c5dc20 + languageName: node + linkType: hard + "@reach/observe-rect@npm:^1.1.0": version: 1.2.0 resolution: "@reach/observe-rect@npm:1.2.0" @@ -3813,6 +4217,15 @@ __metadata: languageName: node linkType: hard +"@stitches/react@npm:^1.2.8": + version: 1.2.8 + resolution: "@stitches/react@npm:1.2.8" + peerDependencies: + react: ">= 16.3.0" + checksum: 029795323cdedb5599ec0636526fc233b2cf41fa9c1f4a704b1f7f457c6170053384a7430afed45e25f73e348d7c391cf1379bc079943cd9cad44302234a5244 + languageName: node + linkType: hard + "@svgr/babel-plugin-add-jsx-attribute@npm:8.0.0": version: 8.0.0 resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:8.0.0" @@ -4570,7 +4983,7 @@ __metadata: languageName: node linkType: hard -"@use-gesture/react@npm:^10.3.1": +"@use-gesture/react@npm:^10.2.5, @use-gesture/react@npm:^10.3.1": version: 10.3.1 resolution: "@use-gesture/react@npm:10.3.1" dependencies: @@ -4957,6 +5370,13 @@ __metadata: languageName: node linkType: hard +"assign-symbols@npm:^1.0.0": + version: 1.0.0 + resolution: "assign-symbols@npm:1.0.0" + checksum: c0eb895911d05b6b2d245154f70461c5e42c107457972e5ebba38d48967870dee53bcdf6c7047990586daa80fab8dab3cc6300800fbd47b454247fdedd859a2c + languageName: node + linkType: hard + "ast-types-flow@npm:^0.0.8": version: 0.0.8 resolution: "ast-types-flow@npm:0.0.8" @@ -4994,6 +5414,13 @@ __metadata: languageName: node linkType: hard +"attr-accept@npm:^2.2.2": + version: 2.2.5 + resolution: "attr-accept@npm:2.2.5" + checksum: e6a23183c112f5d313ebfc7e63e454de0600caffe9ab88f86e9df420d2399a48e27e6c46ee8de2fc6f34fee3541ecdb557f2b86e6d8bd7d24fd3a66cc75e6349 + languageName: node + linkType: hard + "autoprefixer@npm:^10.4.13": version: 10.4.19 resolution: "autoprefixer@npm:10.4.19" @@ -5550,6 +5977,13 @@ __metadata: languageName: node linkType: hard +"colord@npm:^2.9.2": + version: 2.9.3 + resolution: "colord@npm:2.9.3" + checksum: 95d909bfbcfd8d5605cbb5af56f2d1ce2b323990258fd7c0d2eb0e6d3bb177254d7fb8213758db56bb4ede708964f78c6b992b326615f81a18a6aaf11d64c650 + languageName: node + linkType: hard + "colorette@npm:^2.0.20": version: 2.0.20 resolution: "colorette@npm:2.0.20" @@ -6004,7 +6438,7 @@ __metadata: languageName: node linkType: hard -"dequal@npm:^2.0.0": +"dequal@npm:^2.0.0, dequal@npm:^2.0.2": version: 2.0.3 resolution: "dequal@npm:2.0.3" checksum: 8679b850e1a3d0ebbc46ee780d5df7b478c23f335887464023a631d1b9af051ad4a6595a44220f9ff8ff95a8ddccf019b5ad778a976fd7bbf77383d36f412f90 @@ -7115,6 +7549,16 @@ __metadata: languageName: node linkType: hard +"extend-shallow@npm:^3.0.0": + version: 3.0.2 + resolution: "extend-shallow@npm:3.0.2" + dependencies: + assign-symbols: ^1.0.0 + is-extendable: ^1.0.1 + checksum: a920b0cd5838a9995ace31dfd11ab5e79bf6e295aa566910ce53dff19f4b1c0fda2ef21f26b28586c7a2450ca2b42d97bd8c0f5cec9351a819222bf861e02461 + languageName: node + linkType: hard + "extend@npm:^3.0.0": version: 3.0.2 resolution: "extend@npm:3.0.2" @@ -7245,6 +7689,15 @@ __metadata: languageName: node linkType: hard +"file-selector@npm:^0.5.0": + version: 0.5.0 + resolution: "file-selector@npm:0.5.0" + dependencies: + tslib: ^2.0.3 + checksum: f95a06938123a2b765d136a4430cc8b19165f06a53e7ae1dcca4947716d61e9181453fcfeb2358c2660cbcecf96d7334f0528ba60071fed81f8bd358ea08454a + languageName: node + linkType: hard + "fill-range@npm:^7.1.1": version: 7.1.1 resolution: "fill-range@npm:7.1.1" @@ -7299,6 +7752,13 @@ __metadata: languageName: node linkType: hard +"for-in@npm:^1.0.2": + version: 1.0.2 + resolution: "for-in@npm:1.0.2" + checksum: 09f4ae93ce785d253ac963d94c7f3432d89398bf25ac7a24ed034ca393bf74380bdeccc40e0f2d721a895e54211b07c8fad7132e8157827f6f7f059b70b4043d + languageName: node + linkType: hard + "foreground-child@npm:^3.1.0": version: 3.2.1 resolution: "foreground-child@npm:3.2.1" @@ -7535,6 +7995,13 @@ __metadata: languageName: node linkType: hard +"get-value@npm:^2.0.6": + version: 2.0.6 + resolution: "get-value@npm:2.0.6" + checksum: 5c3b99cb5398ea8016bf46ff17afc5d1d286874d2ad38ca5edb6e87d75c0965b0094cb9a9dddef2c59c23d250702323539a7fbdd870620db38c7e7d7ec87c1eb + languageName: node + linkType: hard + "giscus@npm:^1.5.0": version: 1.5.0 resolution: "giscus@npm:1.5.0" @@ -8552,13 +9019,22 @@ __metadata: languageName: node linkType: hard -"is-extendable@npm:^0.1.0": +"is-extendable@npm:^0.1.0, is-extendable@npm:^0.1.1": version: 0.1.1 resolution: "is-extendable@npm:0.1.1" checksum: 3875571d20a7563772ecc7a5f36cb03167e9be31ad259041b4a8f73f33f885441f778cee1f1fe0085eb4bc71679b9d8c923690003a36a6a5fdf8023e6e3f0672 languageName: node linkType: hard +"is-extendable@npm:^1.0.0, is-extendable@npm:^1.0.1": + version: 1.0.1 + resolution: "is-extendable@npm:1.0.1" + dependencies: + is-plain-object: ^2.0.4 + checksum: db07bc1e9de6170de70eff7001943691f05b9d1547730b11be01c0ebfe67362912ba743cf4be6fd20a5e03b4180c685dad80b7c509fe717037e3eee30ad8e84f + languageName: node + linkType: hard + "is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" @@ -8670,6 +9146,15 @@ __metadata: languageName: node linkType: hard +"is-plain-object@npm:^2.0.3, is-plain-object@npm:^2.0.4": + version: 2.0.4 + resolution: "is-plain-object@npm:2.0.4" + dependencies: + isobject: ^3.0.1 + checksum: 2a401140cfd86cabe25214956ae2cfee6fbd8186809555cd0e84574f88de7b17abacb2e477a6a658fa54c6083ecbda1e6ae404c7720244cd198903848fca70ca + languageName: node + linkType: hard + "is-plain-object@npm:^5.0.0": version: 5.0.0 resolution: "is-plain-object@npm:5.0.0" @@ -8867,6 +9352,13 @@ __metadata: languageName: node linkType: hard +"isobject@npm:^3.0.1": + version: 3.0.1 + resolution: "isobject@npm:3.0.1" + checksum: db85c4c970ce30693676487cca0e61da2ca34e8d4967c2e1309143ff910c207133a969f9e4ddb2dc6aba670aabce4e0e307146c310350b298e74a31f7d464703 + languageName: node + linkType: hard + "iterator.prototype@npm:^1.1.5": version: 1.1.5 resolution: "iterator.prototype@npm:1.1.5" @@ -9106,6 +9598,28 @@ __metadata: languageName: node linkType: hard +"leva@npm:^0.10.1": + version: 0.10.1 + resolution: "leva@npm:0.10.1" + dependencies: + "@radix-ui/react-portal": ^1.1.4 + "@radix-ui/react-tooltip": ^1.1.8 + "@stitches/react": ^1.2.8 + "@use-gesture/react": ^10.2.5 + colord: ^2.9.2 + dequal: ^2.0.2 + merge-value: ^1.0.0 + react-colorful: ^5.5.1 + react-dropzone: ^12.0.0 + v8n: ^1.3.3 + zustand: ^3.6.9 + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 322a458e1b2b5b86f0b653b79d7a0c66ab522f18f52e4944b51fc6dbde05f78af7993bb6bf58dc8987e5b30cff3a22995191ffab51902afb0c450f49c9ea6bf8 + languageName: node + linkType: hard + "levn@npm:^0.4.1": version: 0.4.1 resolution: "levn@npm:0.4.1" @@ -9773,6 +10287,18 @@ __metadata: languageName: node linkType: hard +"merge-value@npm:^1.0.0": + version: 1.0.0 + resolution: "merge-value@npm:1.0.0" + dependencies: + get-value: ^2.0.6 + is-extendable: ^1.0.0 + mixin-deep: ^1.2.0 + set-value: ^2.0.0 + checksum: 32c0ecaac8513d43389e979fa3f4bc73f6599ac7440f25721714bc4afc3c78bc55f251817e9ee22e94116791e493a789bf308dfe9e65601e5c8c267be47758f5 + languageName: node + linkType: hard + "merge2@npm:^1.3.0": version: 1.4.1 resolution: "merge2@npm:1.4.1" @@ -10445,6 +10971,16 @@ __metadata: languageName: node linkType: hard +"mixin-deep@npm:^1.2.0": + version: 1.3.2 + resolution: "mixin-deep@npm:1.3.2" + dependencies: + for-in: ^1.0.2 + is-extendable: ^1.0.1 + checksum: 820d5a51fcb7479f2926b97f2c3bb223546bc915e6b3a3eb5d906dda871bba569863595424a76682f2b15718252954644f3891437cb7e3f220949bed54b1750d + languageName: node + linkType: hard + "mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": version: 1.0.4 resolution: "mkdirp@npm:1.0.4" @@ -11418,6 +11954,16 @@ __metadata: languageName: node linkType: hard +"react-colorful@npm:^5.5.1": + version: 5.6.1 + resolution: "react-colorful@npm:5.6.1" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: e432b7cb0df57e8f0bcdc3b012d2e93fcbcb6092c9e0f85654788d5ebfc4442536d8cc35b2418061ba3c4afb8b7788cc101c606d86a1732407921de7a9244c8d + languageName: node + linkType: hard + "react-dom@npm:19.2.4": version: 19.2.4 resolution: "react-dom@npm:19.2.4" @@ -11429,6 +11975,19 @@ __metadata: languageName: node linkType: hard +"react-dropzone@npm:^12.0.0": + version: 12.1.0 + resolution: "react-dropzone@npm:12.1.0" + dependencies: + attr-accept: ^2.2.2 + file-selector: ^0.5.0 + prop-types: ^15.8.1 + peerDependencies: + react: ">= 16.8" + checksum: 1be37433cf42b8a9f98c8f59678e30fffc1e9b8e3fdb20f3a376557948f727156123ca0a7e45cd3882606184d945ea1139f17da0e1e5ba0b646a23be0ed65fb3 + languageName: node + linkType: hard + "react-is@npm:^16.13.1": version: 16.13.1 resolution: "react-is@npm:16.13.1" @@ -12439,6 +12998,18 @@ __metadata: languageName: node linkType: hard +"set-value@npm:^2.0.0": + version: 2.0.1 + resolution: "set-value@npm:2.0.1" + dependencies: + extend-shallow: ^2.0.1 + is-extendable: ^0.1.1 + is-plain-object: ^2.0.3 + split-string: ^3.0.1 + checksum: 09a4bc72c94641aeae950eb60dc2755943b863780fcc32e441eda964b64df5e3f50603d5ebdd33394ede722528bd55ed43aae26e9df469b4d32e2292b427b601 + languageName: node + linkType: hard + "sharp@npm:^0.34.3": version: 0.34.5 resolution: "sharp@npm:0.34.5" @@ -12720,6 +13291,15 @@ __metadata: languageName: node linkType: hard +"split-string@npm:^3.0.1": + version: 3.1.0 + resolution: "split-string@npm:3.1.0" + dependencies: + extend-shallow: ^3.0.0 + checksum: ae5af5c91bdc3633628821bde92fdf9492fa0e8a63cf6a0376ed6afde93c701422a1610916f59be61972717070119e848d10dfbbd5024b7729d6a71972d2a84c + languageName: node + linkType: hard + "sprintf-js@npm:^1.1.3": version: 1.1.3 resolution: "sprintf-js@npm:1.1.3" @@ -13182,6 +13762,7 @@ __metadata: hast-util-from-html-isomorphic: ^2.0.0 husky: ^9.1.7 image-size: 2.0.2 + leva: ^0.10.1 lint-staged: ^16.2.7 next: 15.5.12 next-contentlayer2: 0.5.8 @@ -13892,6 +14473,13 @@ __metadata: languageName: node linkType: hard +"v8n@npm:^1.3.3": + version: 1.5.1 + resolution: "v8n@npm:1.5.1" + checksum: 96c8dff9144001da46152f37b9323e2bf9a1f915c6a3f6f5e8683f7a540a6551a18e937267e257f8753da594f33a0b1724770cd50f73e6ea7dc3ceb0510ca72f + languageName: node + linkType: hard + "vfile-location@npm:^5.0.0": version: 5.0.3 resolution: "vfile-location@npm:5.0.3" @@ -14280,6 +14868,18 @@ __metadata: languageName: node linkType: hard +"zustand@npm:^3.6.9": + version: 3.7.2 + resolution: "zustand@npm:3.7.2" + peerDependencies: + react: ">=16.8" + peerDependenciesMeta: + react: + optional: true + checksum: 18f025b1b666a311121d3855303ff58e6a21fd107920ca474307e86984c13338d6c4cfa5cdf13382a9e0f76821f2554a12d4d200a98a66b58637e729f149797b + languageName: node + linkType: hard + "zustand@npm:^4.3.2": version: 4.5.4 resolution: "zustand@npm:4.5.4"