{
 "site": "Clever°Franke",
 "slug": "cleverfranke",
 "urls": [
  "https://www.cleverfranke.com/",
  "https://www.cleverfranke.com/project/wellcome-design-system"
 ],
 "stack": {
  "framework": "Next.js 12.3.1 (pages router, getStaticProps) on React 18.3.1, hosted on Vercel",
  "cms": "Sanity (project bgm3v1ft, dataset production; studio framed from cms.cleverfranke.com)",
  "animation": [
   "framer-motion (DOM labels, sheets, word reveals; MotionConfig reducedMotion user)",
   "react-spring v9 (3D springs: fade, hover, dwell, reveal, outro)",
   "d3-force-3d with a d3-force-cluster style cluster force (disc layout)"
  ],
  "scroll": "none: the cloud sets body overflow hidden; html scroll-behavior smooth; no smooth-scroll library",
  "three_d": "three.js r143 via @react-three/fiber v8 and drei: one InstancedMesh of 282 SphereGeometry(1, 64, 32) instances with transparent MeshBasicMaterial (one drawElementsInstanced per frame); matched case image as Points on a subdivided PlaneGeometry with a custom luma-and-noise shader",
  "fonts": [
   "Neutral (NeutralStd-Regular.woff2, NeutralStd-Medium.woff2, self-hosted and preloaded)"
  ],
  "evidence": [
   "headers: x-powered-by Next.js; server Vercel; x-vercel-cache HIT",
   "main-97b1af3c27ac5753.js contains \"12.3.1\"; framework-0c70fb2ef59568fa.js contains \"18.3.1\"",
   "runtime window.__THREE__ = \"143\"; chunk 020d8314-9e90170b4660584f.js REVISION \"143\"",
   "chunk 600-ec8a3c39732ab4c6.js: rendererPackageName \"@react-three/fiber\", reconciler 18.0.0-fc46dba67-20220329",
   "index chunk: next/dynamic CaseExperience with ssr false, lazy chunks 546, 600, 46, 705",
   "chunk 705.64c101786dbad2d5.js: sphereGeometry args [1, 2*segments, segments], segments 32; meshBasicMaterial transparent; OrbitControls enabled false; d3 forces radial, collide, cluster, numDimensions(3)",
   "draw-call wrap (3 s): 1 drawElementsInstanced per frame, 282 instances, 11904 indices",
   "<style id=\"stitches\"> in the HTML; only stylesheet b39dbd97004bc4b5.css is 658 bytes",
   "_app: next-themes (storageKey theme, attribute class, defaultTheme system); react-cookie-consent (#rcc-decline-button)",
   "og:image on cdn.sanity.io/images/bgm3v1ft/production; CSP frame-ancestors cms.cleverfranke.com and localhost:3333",
   "probe scripts: GTM-W2XNWDJ, G-6MZF471GT9, UA-3211909-3, AW-991534915, /va/script.js",
   "document.fonts: Neutral 400, Neutral 500 900"
  ]
 },
 "components": [
  {
   "name": "Dot cloud (the orb)",
   "owner_words": "I really like the homepage here how the dots are floating in a cloud kind of thing. … it looks very much like a freer mind kind of relaxing and interesting.",
   "selectors": [
    "canvas (inside div[style*='touch-action: none'])",
    "div.c-hzpbim (fixed label, left 37.5px, bottom 40px)",
    "h2.c-crjpHX (headline over the cloud)"
   ],
   "mechanism": "282 discs, one per palette colour of every case (35 experiences, 86 cases), radius 2.5*clamp(ratio,.2,.85) (touch 2*clamp(ratio,.5,.85)); each colour assigned to the nearest of ten hues; d3-force-3d lays them out (start: radial 31 burst past the camera; orb: radial 10 grouped by hue); drawn as one instanced mesh of unlit spheres at 0.87 opacity through a wide perspective camera so edge discs read as ellipses; per-frame spin of 0.0015 rad, bob, pointer tilt of up to about 0.4 rad; wheel dolly z 20 to 50 (the applied offset is clamped to -10..20, the running wheel sum is not); drag disabled",
   "libraries": [
    "three.js r143",
    "@react-three/fiber v8",
    "drei (Instances, OrbitControls disabled)",
    "d3-force-3d",
    "react-spring v9",
    "zustand"
   ],
   "states": [
    "fade-in ball",
    "start (burst, scattered field)",
    "explore/orb (ball sorted by hue)",
    "hover (disc 1.5x)",
    "wheel zoom",
    "dark theme (scene black, from source)"
   ],
   "timings": "fade 550 ms easeInQuart to 0.87 (the 300 ms delay written in the spring config is ignored: react-spring reads delay from props only); start 175 ticks (about 2.9 s at 60 fps); orb 100 ticks; hover grow 500 ms linear, shrink 150 ms; spin about 5 degrees a second (turn in about 70 s); all positional motion counted per frame",
   "shots": [
    "shots/d01-arrival-banner.png",
    "shots/d02-intro-sheet.png",
    "shots/d03-rest.png",
    "shots/d04-idle-sheet.png",
    "shots/d10-orb-forming.png",
    "shots/d11-orb.png",
    "shots/d20-d25-drag-wheel-sheet.png",
    "shots/m02-start.png",
    "shots/m03-orb.png",
    "shots/verify/verify-zoom-a-orb.png",
    "shots/verify/verify-zoom-b-wheel-up-7500-20s.png",
    "shots/verify/verify-zoom-c-wheel-down-5000-20s.png",
    "shots/verify/verify-zoom-d-wheel-down-7500-20s.png"
   ],
   "rebuild": "One instanced mesh of light discs (16x8 sphere or camera-facing circle), one node per speaker coloured by track or day, positions precomputed on a sphere grouped by colour (or d3-force-3d radial + collide + group anchors, then frozen); time-based spin, bob and pointer tilt; raycast hover writes the name into the bar, click opens the speaker drawer; add damped drag rotation and clamped pinch or wheel zoom; static under reduced motion; DPR cap 2",
   "confidence": "high"
  },
  {
   "name": "Explore by color (colour view and three picks)",
   "owner_words": "You could kind of zoom around or rotate it",
   "selectors": [
    "a.c-ilHjrI (phone button 'Explore by color', fixed right 20px bottom 16px, hidden from 840 px)",
    "div.c-hzpbim (label: 'Hover over circles to explore our portfolio by color' / 'Touch circles …')",
    "div[style*='pointer-events: none'] > svg circle[r='18'] (cursor ring)"
   ],
   "mechanism": "Desktop: the first hover over any disc switches exploreState orb to cluster (radial 20, spacing 1.5, camera z 35); an 800 ms hover dwell picks a colour, shown by a 60 px cursor ring. Phone: the button hides headline and nav and enters the view; taps pick at once. Each pick flies to a row of three at 3.2x (4x touch); camera z 40, 50, 60; labels 'Hover 2 more colors'; six seconds without a pick resets; after three picks a 2.5 s countdown, then a Web Worker returns the case whose palette has the smallest mean nearest-colour RGB distance",
   "libraries": [
    "@react-three/fiber pointer events",
    "react-spring (dwell)",
    "framer-motion (labels)",
    "Web Worker"
   ],
   "states": [
    "orb",
    "cluster with no picks",
    "one pick",
    "two picks",
    "three picks countdown",
    "reset after 6 s",
    "loading ('matching colors to portfolio image')"
   ],
   "timings": "dwell 800 ms linear; reset 6 s; confirm 2.5 s; on match the other discs shrink 5% per frame, then the three picks shrink into the centre over 2 s easeInExpo (the 1 s delay in its config is ignored); label swap 0.3 s",
   "shots": [
    "shots/d12-cluster-sheet.png",
    "shots/d12-cluster-00-crops.png",
    "shots/m04-orb-later.png",
    "shots/m05-explore-tapped.png",
    "shots/m06-picks.png",
    "shots/m07-match-sheet.png"
   ],
   "rebuild": "Use the colour view as 'by track' or 'by day': re-target group anchors so the ball loosens into colour groups and the bar names the mode; one click or tap opens a drawer (no three-pick game); keep the dwell ring as a desktop reactive cursor; the nearest-palette match can pair a visitor's three interests with a session",
   "confidence": "medium"
  },
  {
   "name": "Match and case reveal (click to expand)",
   "owner_words": "and click on them to expand",
   "selectors": [
    "canvas points (casePlaneMaterial)",
    "black sheet: 'Show meta data', button 'Visit this case', 'Back to all C°F colors' with countdown ring"
   ],
   "mechanism": "The matched case image (Sanity, 640 px, q50) drawn as Points on a PlaneGeometry subdivided round(width/13*3.5) each way; vertex shader pushes points by luminance and 3D Perlin noise; three chained springs settle it; a black sheet slides up (phone) or in from the left (desktop, source); 'Visit this case' recolours the points to the case's dominant colour and floods the scene background, then router.push('/project/<slug>')",
   "libraries": [
    "three.js Points + ShaderMaterial (drei shaderMaterial)",
    "react-spring useChain",
    "framer-motion (sheet)",
    "next/router"
   ],
   "states": [
    "loading",
    "matched (points settle)",
    "sheet open",
    "outro flood",
    "case page"
   ],
   "timings": "points 2.5 s easeOutQuint, 3 s easeOutCirc, 2.5 s easeOutCirc; sheet 0.7 s easeInOut; auto return 18 s; outro spring mass 4.2 tension 316 friction 12; background 1.5 s easeInExpo (the 200 ms delay in its config is ignored)",
   "shots": [
    "shots/m07-match-07.png",
    "shots/m08-outro-sheet.png",
    "shots/m09-after-visit.png"
   ],
   "rebuild": "Speaker drawer opening with the photo (or the marker sketch) assembling from points: three.js Points on a subdivided plane, texture lookup in the vertex shader, depth from luminance, noise amplitude springing from 1 to 0.2; drawer in DOM with framer-motion, Motion One or GSAP; colour flood as the transition to a full speaker page; 10 to 40 thousand points on phones",
   "confidence": "high"
  }
 ],
 "transitions": [
  {
   "name": "Arrival",
   "from": "blank",
   "to": "start field",
   "layers": [
    "canvas fade-in ball",
    "headline words rise from masks (y 100% to 0, opacity 0 to 1)",
    "nav items one by one"
   ],
   "duration_ms": 1300,
   "shots": [
    "shots/d01-arrival-banner.png",
    "shots/m01-arrival-banner.png"
   ]
  },
  {
   "name": "Burst",
   "from": "fade-in ball",
   "to": "scattered start field",
   "layers": [
    "canvas: radial 31 force simulation past the camera"
   ],
   "duration_ms": 2900,
   "shots": [
    "shots/d02-intro-sheet.png",
    "shots/m02-start.png"
   ]
  },
  {
   "name": "Start to orb",
   "from": "start field",
   "to": "orb sorted by hue",
   "layers": [
    "canvas: radial 10 simulation, per-frame easing",
    "label fades and rises in"
   ],
   "duration_ms": 1700,
   "shots": [
    "shots/d10-orb-forming.png",
    "shots/d11-orb.png",
    "shots/m03-orb.png"
   ]
  },
  {
   "name": "Orb to colour view",
   "from": "orb",
   "to": "cluster (colour groups)",
   "layers": [
    "canvas: radial 20, camera z 35",
    "label swap",
    "phone: headline and nav hidden, close button"
   ],
   "duration_ms": 300,
   "shots": [
    "shots/d12-cluster-sheet.png",
    "shots/m05-explore-tapped.png"
   ]
  },
  {
   "name": "Picks to match",
   "from": "three picks",
   "to": "matched case points",
   "layers": [
    "countdown ring",
    "worker",
    "non-picked discs shrink per frame, then the picks shrink into the centre",
    "points plane settles",
    "black sheet"
   ],
   "duration_ms": 12500,
   "shots": [
    "shots/m07-match-sheet.png"
   ]
  },
  {
   "name": "Outro to case page",
   "from": "matched case",
   "to": "/project/<slug>",
   "layers": [
    "sheet leaves",
    "points recolour and swell (spring)",
    "scene background floods the dominant colour",
    "client route change"
   ],
   "duration_ms": 1500,
   "shots": [
    "shots/m08-outro-sheet.png",
    "shots/m09-after-visit.png"
   ]
  }
 ],
 "not_confirmed": [
  "Desktop 800 ms hover-dwell pick: none of eight headless attempts (1.1 s holds) registered; likely because react-spring advances about 64 ms per frame at 3 fps (the dwell needs about 4 s), and from the orb the first pointer-over only switches to the colour view, with the 6 s reset returning it to the orb; the desktop matched view and its left-hand sheet were not captured",
  "Cursor ring look: in the DOM, not visible in any frame",
  "Reduced motion: 3D chunks never query it (source); no capture with reducedMotion reduce (capture.cjs has no option)",
  "Laptop frame cost: headless SwiftShader gave 3.0 fps desktop and 4.3 fps phone; 2880x1800 backing store on a 2x laptop assumed from R3F's default dpr [1, 2]",
  "Field of view 75 degrees assumed from R3F v8's default camera (no camera prop on the Canvas); not read at runtime",
  "Exact versions of @react-three/fiber, drei, react-spring, zustand, framer-motion",
  "Dark theme scene switch (source only); 'Show meta data' content; /va/script.js identity; rendered colour fidelity versus CMS hex",
  "Real-time durations of frame-counted motion (computed for 60 fps from source); react-spring and framer-motion steps also ran slow headless, so their real pace was not measured",
  "Two CSS rules mentioning view-transition counted by the probe: not in the homepage HTML, stylesheet or the 15 chunks read; source unknown",
  "Which three discs were the phone picks (four large discs in m07-match-03)"
 ]
}