NEMAWASHI LAB

Chrome

three.js

The washi mark as a chrome turntable -- the tool the brand's 3D render came from, ported from design/mobile-chrome.html. The Mobile.svg outline is parsed with SVGLoader, sharp corners (beak, wing joints) are rounded so the fillet offset stays well-behaved, then a custom extruder gives every contour its own quarter-circle fillet -- 1.4 on the body, 0.4 on the eye hole and pupil, which ExtrudeGeometry's single bevel would turn into a funnel and a stalk. A procedural studio (black room, five hard strip lights, PMREM'd) renders on the first frame; a 2k Polyhaven HDRI then streams in and is sampled straight from the equirect by a small mirror shader -- PMREM would resample the panorama into a soft cube and a flat mirror magnifies that -- with a mip level derived from fwidth of the reflection vector so tight fillets filter instead of alias. Transparent canvas, so it sits on paper or dark. The same component spins on the cover of /deck.

Source

ChromeCanvas.tsx95 lines
"use client";

import { useState } from "react";
import ChromeBird, { HDRIS, type ChromeEnv } from "@/components/ChromeBird";
import DraggablePanel from "../DraggablePanel";

/* Chrome — the washi mark as a chrome turntable (port of
   design/mobile-chrome.html, the tool the brand render came from).
   The Mobile.svg outline is extruded with per-contour fillets and mirrored
   against an HDRI, sampled straight from the equirect so the flat faces stay
   sharp. Also drives the cover of /deck. */

const ENVS: ChromeEnv[] = ["studio", ...(Object.keys(HDRIS) as (keyof typeof HDRIS)[])];

export default function ChromeCanvas() {
  const [speed, setSpeed] = useState(0.6);
  const [tilt, setTilt] = useState(0);
  const [roughness, setRoughness] = useState(0.23);
  const [intensity, setIntensity] = useState(1.25);
  const [envRotate, setEnvRotate] = useState(0);
  const [env, setEnv] = useState<ChromeEnv>("brown photostudio");
  const [dark, setDark] = useState(true);

  const bg = dark ? "#0e0e0e" : "#ffffff";

  return (
    <div className="fixed inset-0 overflow-hidden select-none" style={{ background: bg }}>
      <ChromeBird
        className="absolute inset-0 [&>canvas]:block [&>canvas]:!w-full [&>canvas]:!h-full"
        speed={speed}
        tilt={tilt}
        roughness={roughness}
        intensity={intensity}
        env={env}
        envRotate={envRotate}
      />

      <DraggablePanel
        isLight={!dark}
        controls={[
          { label: "speed", value: speed, set: setSpeed, min: 0, max: 3, step: 0.05 },
          { label: "tilt", value: tilt, set: setTilt, min: -45, max: 45, step: 1 },
          { label: "roughness", value: roughness, set: setRoughness, min: 0, max: 0.5, step: 0.005 },
          { label: "reflection", value: intensity, set: setIntensity, min: 0.2, max: 3, step: 0.05 },
          { label: "env rotate", value: envRotate, set: setEnvRotate, min: 0, max: 360, step: 1 },
        ]}
      >
        <div
          style={{
            fontSize: 10,
            letterSpacing: "0.06em",
            textTransform: "uppercase",
            opacity: 0.5,
            marginBottom: 6,
            fontVariationSettings: "'wght' 300, 'SRIF' 100",
          }}
        >
          reflections
        </div>
        <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 12 }}>
          {ENVS.map((e) => (
            <button
              key={e}
              onClick={() => setEnv(e)}
              style={{
                fontSize: 10,
                letterSpacing: "0.04em",
                padding: "4px 8px",
                borderRadius: 999,
                border: "1px solid currentColor",
                opacity: env === e ? 1 : 0.4,
                fontVariationSettings: "'wght' 400, 'SRIF' 100",
              }}
            >
              {e}
            </button>
          ))}
        </div>
        <button
          onClick={() => setDark((d) => !d)}
          style={{
            fontSize: 10,
            letterSpacing: "0.06em",
            textTransform: "uppercase",
            opacity: 0.6,
            fontVariationSettings: "'wght' 400, 'SRIF' 100",
          }}
        >
          background: {dark ? "dark" : "paper"}
        </button>
      </DraggablePanel>
    </div>
  );
}

NEMAWASHI — Kotaro Abe