Skip to main content

ns-ui / loud

Tricone Bit Teeth

A full-bleed rock face worked by a rotary tricone bit: three cone cutters orbit the bit centre in a fixed 120-degree pattern, each tooth row stamping a crater cluster into the face that heals back to flat before the next pass, so the surface reads as a ring being continuously worked rather than a diffuse pockmarked plate.

Usage

registry/loud/tricone-bit-teeth/demo.tsx
"use client";

import { TriconeBitTeeth } from "./component";

// The rock face is already mid-cycle — a fresh strike ring building, an
// older cluster healing — before anything else on the page loads. Overlaid
// type sits on a token scrim, since the crater ring can pass under any line.
export default function TriconeBitTeethDemo() {
  return (
    <main className="h-screen w-full overflow-hidden bg-background">
      <TriconeBitTeeth>
        <div className="pointer-events-none flex h-full w-full flex-col items-center justify-end gap-4 px-6 pb-14 text-center sm:pb-20">
          <div className="flex flex-col items-center gap-3 rounded-lg bg-background/70 px-7 py-5 backdrop-blur-md">
            <p className="font-mono text-[11px] uppercase tracking-[0.28em] text-ns-muted">
              ns-ui / tricone-bit-teeth
            </p>
            <h1 className="text-2xl font-medium tracking-tight text-foreground sm:text-3xl">
              Three cones, one bit
            </h1>
            <p className="max-w-sm text-sm text-ns-muted">
              Each pass strikes and scrapes the face — the ring never fills in, and it never empties out.
            </p>
          </div>
          <a
            href="#docs"
            className="pointer-events-auto mt-1 inline-flex w-fit items-center justify-center rounded-sm bg-ns-accent px-5 py-2.5 text-sm font-medium text-white transition-colors duration-150 hover:bg-ns-accent-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ns-accent"
          >
            Read the docs
          </a>
        </div>
      </TriconeBitTeeth>
    </main>
  );
}

Component

registry/loud/tricone-bit-teeth/component.tsx
"use client";

import { useEffect, useId, useRef } from "react";

// ---------------------------------------------------------------------------
// TriconeBitTeeth — a full-bleed hero built on a rotary tricone rock bit: the
// three cone cutters at a bit's periphery orbit the borehole axis while each
// spins on its own skewed journal, so their teeth both crush and scrape the
// formation with every pass. Unlike edm-crater-field's steady-state RANDOM
// discharge scatter, this crater field is never diffuse or uniform — every
// dent lands only where a cone's tooth row actually swept, in three
// simultaneous, 120-degree-offset clusters, so the field always reads as a
// RING traced by a rotating tool rather than a pockmarked plate. The bit
// bodies themselves are drawn as visible orbiting wedges so "three cones
// rotating" is legible on its own, independent of the crater ring it leaves.
//
// The real bit rotates around 60-120 RPM and strikes 33 times/lap (3 cones x
// 11 teeth) — both far too fast to paint 1:1 against a 60Hz page without
// aliasing into a strobe (round 9's meter-matrix-scan lesson). Both rates are
// deliberately decoupled: the bit is rendered orbiting at an 8-RPM-equivalent
// sweep (one lap every 7.5s), and the 33 individual tooth strikes per lap are
// collapsed into one 3-cone simultaneous cluster roughly every 680ms, which
// is what a viewer can actually track landing and healing.
//
// IMPLEMENTATION: like edm-crater-field, the rock face is a small CPU-side
// height/depth buffer (cells derived from the container's SMALLER dimension,
// 96 cells across it) that strikes stamp a crater into and that decays back
// toward flat every frame — never a monotonic fill, a resident population in
// equilibrium. The buffer is painted to an offscreen ImageData canvas and
// blitted, scaled and smoothed, onto the display canvas; the three cone
// bodies and the mud-jet sweep ring are drawn on top in vector form each
// frame so the "rotating tool" reading never depends on the field alone.
//
// Colours: three luminance stops derived from --background, --foreground and
// --ns-muted (getComputedStyle at mount, re-read on a MutationObserver
// watching documentElement's class) — no literals anywhere. --ns-accent is
// never touched: there is no interactive climactic moment (an optional
// pointer only brightens the swept ring in luminance), matching the showpiece
// recipe's standing "accent-tinted pointer highlight" defect.
// ---------------------------------------------------------------------------

export interface TriconeBitTeethProps {
  /** Freezes the surface on a composed post-strike still frame without unmounting. */
  paused?: boolean;
  /** Rendered in the DOM over the field — eyebrow, headline, CTA. */
  children?: React.ReactNode;
  className?: string;
  style?: React.CSSProperties;
}

type RGB = [number, number, number];

function parseHex(raw: string): RGB | null {
  const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(raw.trim());
  if (!m) return null;
  let h = m[1];
  if (h.length === 3) h = h.split("").map((c) => c + c).join("");
  const n = parseInt(h, 16);
  return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];
}

function mixRGB(a: RGB, b: RGB, t: number): RGB {
  return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];
}

function luminance([r, g, b]: RGB): number {
  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}

function rgbCss([r, g, b]: RGB, a = 1): string {
  return `rgba(${Math.round(r * 255)},${Math.round(g * 255)},${Math.round(b * 255)},${a})`;
}

const CONES = 3;
const TEETH_PER_CONE = 11;
const LAP_SECONDS = 7.5; // 8-RPM-equivalent decoupled sweep, see header
const STRIKE_INTERVAL = 0.68; // s between simultaneous 3-cone strike clusters
const HEAL_RATE = 0.9; // depth *= exp(-HEAL_RATE * dt), ~90% refill in ~1.1s
const FIELD_CELLS = 96; // cells across the container's smaller dimension
const JET_PERIOD = 0.9; // s per mud-jet sweep lap
const JET_SPEED = 140; // px/s the sweep ring expands at

// The frame drawn under prefers-reduced-motion (and paused): right after a
// 3-cone simultaneous strike lands, at maximum unhealed depth — the single
// most structured frame, showing the tooth pattern rather than a mid-heal
// blur or the flat rock the loop would otherwise freeze on.
const STATIC_STRIKE_PHASE = 0; // seconds past the most recent strike event

export function TriconeBitTeeth({
  paused = false,
  children,
  className = "",
  style,
}: TriconeBitTeethProps) {
  const wrapRef = useRef<HTMLDivElement | null>(null);
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  const uid = useId();
  const pausedRef = useRef(paused);
  pausedRef.current = paused;

  useEffect(() => {
    const wrap = wrapRef.current;
    const canvas = canvasRef.current;
    if (!wrap || !canvas) return;
    const ctx = canvas.getContext("2d");
    if (!ctx) return;

    let cssW = 0;
    let cssH = 0;
    let dpr = 1;
    let disposed = false;
    let running = false;
    let raf = 0;
    let staticMode = false;
    let lastMs = performance.now();
    let simTime = 0;
    let lastStrikeAt = -STRIKE_INTERVAL;

    let fieldW = FIELD_CELLS;
    let fieldH = FIELD_CELLS;
    let depth = new Float32Array(fieldW * fieldH);
    const field = document.createElement("canvas");
    const fctx = field.getContext("2d", { willReadFrequently: true });
    let fieldImg: ImageData | null = null;

    let bg: RGB = [1, 1, 1];
    let raised: RGB = [0.86, 0.86, 0.86];
    let struckDeep: RGB = [0.06, 0.06, 0.06];
    let muted: RGB = [0.55, 0.55, 0.55];

    const readColors = () => {
      const cs = getComputedStyle(document.documentElement);
      const bgTok = parseHex(cs.getPropertyValue("--background")) ?? [1, 1, 1];
      const fg = parseHex(cs.getPropertyValue("--foreground")) ?? [0.09, 0.09, 0.09];
      const mut = parseHex(cs.getPropertyValue("--ns-muted")) ?? [0.55, 0.55, 0.55];
      bg = bgTok;
      muted = mut;
      // dark theme: unstruck rock sits slightly above background; struck
      // craters read near-black. Light theme needs the same three legible
      // steps compressed into far less headroom below --background, so the
      // dark anchor is pulled from --foreground rather than pure black.
      if (luminance(bgTok) < 0.5) {
        raised = mixRGB(bgTok, [1, 1, 1], 0.14);
        struckDeep = mixRGB(bgTok, [0, 0, 0], 0.55);
      } else {
        raised = mixRGB(bgTok, fg, 0.1);
        struckDeep = mixRGB(fg, [0, 0, 0], 0.25);
      }
    };
    readColors();

    const setFieldRes = () => {
      const ref = Math.min(cssW, cssH) || 1;
      fieldW = FIELD_CELLS;
      fieldH = Math.max(24, Math.round(FIELD_CELLS * (cssH / ref)));
      const w2 = Math.max(24, Math.round(FIELD_CELLS * (cssW / ref)));
      fieldW = w2;
      depth = new Float32Array(fieldW * fieldH);
      field.width = fieldW;
      field.height = fieldH;
      fieldImg = fctx ? fctx.createImageData(fieldW, fieldH) : null;
    };

    // A crater is a recessed bowl: a soft radial falloff stamped additively
    // into the depth buffer (0 = flat, up to 1 = deepest fresh crater).
    const stampCrater = (cx: number, cy: number, radiusCells: number, amp: number) => {
      const r = Math.max(1, radiusCells);
      const x0 = Math.max(0, Math.floor(cx - r));
      const x1 = Math.min(fieldW - 1, Math.ceil(cx + r));
      const y0 = Math.max(0, Math.floor(cy - r));
      const y1 = Math.min(fieldH - 1, Math.ceil(cy + r));
      for (let y = y0; y <= y1; y++) {
        for (let x = x0; x <= x1; x++) {
          const dx = (x - cx) / r;
          const dy = (y - cy) / r;
          const d2 = dx * dx + dy * dy;
          if (d2 > 1) continue;
          const fall = Math.exp(-d2 * 2.2);
          const idx = y * fieldW + x;
          depth[idx] = Math.min(1, depth[idx] + amp * fall);
        }
      }
    };

    // The three cones sit 120 degrees apart at the bit's periphery and orbit
    // the bit centre together. Each cone's tooth row strikes a cluster of
    // TEETH_PER_CONE small craters scattered along a short arc of its own
    // track — this is what keeps the ring textured (individual tooth marks)
    // rather than a single smeared dent per cone per strike.
    const strikeCluster = (bitAngle: number) => {
      const ref = Math.min(fieldW, fieldH);
      const orbitR = ref * 0.32;
      const cx = fieldW / 2;
      const cy = fieldH / 2;
      for (let c = 0; c < CONES; c++) {
        const coneAngle = bitAngle + (c * Math.PI * 2) / CONES;
        const coneCx = cx + Math.cos(coneAngle) * orbitR;
        const coneCy = cy + Math.sin(coneAngle) * orbitR;
        for (let t = 0; t < TEETH_PER_CONE; t++) {
          // teeth spread across a short arc of the cone's own track, not a
          // single point, so one strike cluster reads as a row, not a dot
          const spread = ((t / (TEETH_PER_CONE - 1)) - 0.5) * 0.5;
          const ta = coneAngle + spread;
          const tr = orbitR * (0.94 + 0.09 * ((t % 3) - 1));
          const tx = cx + Math.cos(ta) * tr;
          const ty = cy + Math.sin(ta) * tr;
          stampCrater(tx, ty, ref * 0.028, 0.75 + 0.25 * Math.random());
        }
      }
    };

    const paintField = () => {
      if (!fctx || !fieldImg) return;
      const data = fieldImg.data;
      for (let i = 0; i < depth.length; i++) {
        const d = depth[i];
        // raised (unstruck) rock at d=0 down to the deepest struck stop at d=1
        const col = d <= 0.001 ? raised : mixRGB(raised, struckDeep, Math.min(1, d));
        const j = i * 4;
        data[j] = Math.round(col[0] * 255);
        data[j + 1] = Math.round(col[1] * 255);
        data[j + 2] = Math.round(col[2] * 255);
        data[j + 3] = 255;
      }
      fctx.putImageData(fieldImg, 0, 0);
    };

    const drawCones = (t: number, bitAngle: number) => {
      const ref = Math.min(cssW, cssH);
      const cx = cssW / 2;
      const cy = cssH / 2;
      const orbitR = ref * 0.32;
      const bodyR = ref * 0.1;
      for (let c = 0; c < CONES; c++) {
        const coneAngle = bitAngle + (c * Math.PI * 2) / CONES;
        const px = cx + Math.cos(coneAngle) * orbitR;
        const py = cy + Math.sin(coneAngle) * orbitR;
        const spin = t * 6.0 + c; // decorative own-axis spin, purely visual
        ctx.save();
        ctx.translate(px, py);
        ctx.rotate(spin);
        ctx.beginPath();
        ctx.moveTo(0, -bodyR);
        ctx.lineTo(bodyR * 0.82, bodyR * 0.62);
        ctx.lineTo(-bodyR * 0.82, bodyR * 0.62);
        ctx.closePath();
        ctx.fillStyle = rgbCss(muted, 0.9);
        ctx.fill();
        ctx.strokeStyle = rgbCss(struckDeep, 0.7);
        ctx.lineWidth = Math.max(1, bodyR * 0.05);
        ctx.stroke();
        ctx.restore();
      }
    };

    const drawJetSweep = (t: number) => {
      const ref = Math.min(cssW, cssH);
      const maxR = ref * 0.46;
      const phase = ((t % JET_PERIOD) / JET_PERIOD) * (JET_SPEED * JET_PERIOD);
      const r = Math.min(maxR, phase);
      const alpha = 0.08 * (1 - r / maxR);
      if (alpha <= 0.002) return;
      ctx.save();
      ctx.strokeStyle = rgbCss(mixRGB(raised, [1, 1, 1], 0.5), alpha);
      ctx.lineWidth = Math.max(2, ref * 0.02);
      ctx.beginPath();
      ctx.arc(cssW / 2, cssH / 2, r, 0, Math.PI * 2);
      ctx.stroke();
      ctx.restore();
    };

    const draw = () => {
      if (cssW <= 0 || cssH <= 0 || !fctx) return;
      const t = staticMode ? lastStrikeAt + STATIC_STRIKE_PHASE : simTime;
      const bitAngle = (t / LAP_SECONDS) * Math.PI * 2;

      paintField();
      ctx.save();
      ctx.imageSmoothingEnabled = true;
      ctx.fillStyle = rgbCss(raised);
      ctx.fillRect(0, 0, cssW, cssH);
      ctx.drawImage(field, 0, 0, cssW, cssH);
      ctx.restore();

      drawJetSweep(t);
      drawCones(t, bitAngle);
    };

    const loop = (nowMs: number) => {
      const rawMs = nowMs - lastMs;
      const dt = Math.min(0.05, Math.max(0, rawMs / 1000));
      lastMs = nowMs;
      simTime += dt;

      // heal every resident crater back toward flat, framerate-independent
      const heal = Math.exp(-HEAL_RATE * dt);
      for (let i = 0; i < depth.length; i++) depth[i] *= heal;

      const bitAngle = (simTime / LAP_SECONDS) * Math.PI * 2;
      if (simTime - lastStrikeAt >= STRIKE_INTERVAL) {
        lastStrikeAt = simTime;
        strikeCluster(bitAngle);
      }

      draw();
      raf = requestAnimationFrame(loop);
    };

    const wake = () => {
      if (running || disposed) return;
      running = true;
      lastMs = performance.now();
      raf = requestAnimationFrame(loop);
    };
    const sleep = () => {
      cancelAnimationFrame(raf);
      running = false;
    };

    const dprCap = () => Math.min(window.devicePixelRatio || 1, 2);

    const applyBacking = () => {
      if (cssW < 2 || cssH < 2) return;
      dpr = dprCap();
      const pw = Math.round(cssW * dpr);
      const ph = Math.round(cssH * dpr);
      if (canvas.width !== pw || canvas.height !== ph) {
        canvas.width = pw;
        canvas.height = ph;
      }
      canvas.style.width = `${cssW}px`;
      canvas.style.height = `${cssH}px`;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };

    const resize = () => {
      const rect = wrap.getBoundingClientRect();
      if (rect.width < 2 || rect.height < 2) return;
      cssW = rect.width;
      cssH = rect.height;
      applyBacking();
      setFieldRes();
      // seed a struck ring immediately so a freshly-mounted/resized surface
      // never shows a blank flat frame while waiting for the first cycle
      strikeCluster(0);
      strikeCluster((Math.PI * 2) / LAP_SECONDS / 3);
      draw();
    };

    const ro = new ResizeObserver(resize);
    ro.observe(wrap);
    resize();

    const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
    let reduced = mq.matches;
    const applyMode = () => {
      if (reduced || pausedRef.current) {
        staticMode = true;
        sleep();
        draw();
      } else {
        staticMode = false;
        wake();
      }
    };
    const onMq = () => {
      reduced = mq.matches;
      applyMode();
    };
    mq.addEventListener("change", onMq);

    let onScreen = true;
    const io = new IntersectionObserver(
      (entries) => {
        onScreen = entries.some((en) => en.isIntersecting);
        if (!onScreen) sleep();
        else if (!staticMode && !document.hidden) wake();
      },
      { threshold: 0 }
    );
    io.observe(wrap);

    const onVis = () => {
      if (document.hidden) sleep();
      else if (!staticMode && onScreen) wake();
    };
    document.addEventListener("visibilitychange", onVis);

    const themeObserver = new MutationObserver(() => {
      readColors();
      if (staticMode) draw();
    });
    themeObserver.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"],
    });

    let lastPolledPaused = pausedRef.current;
    let poll = 0;
    const tick = () => {
      if (pausedRef.current !== lastPolledPaused) {
        lastPolledPaused = pausedRef.current;
        applyMode();
      }
      poll = window.setTimeout(tick, 140);
    };
    tick();

    applyMode();

    return () => {
      disposed = true;
      ro.disconnect();
      io.disconnect();
      mq.removeEventListener("change", onMq);
      document.removeEventListener("visibilitychange", onVis);
      themeObserver.disconnect();
      window.clearTimeout(poll);
      sleep();
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <div
      ref={wrapRef}
      data-tricone-bit-teeth={uid}
      className={`relative isolate h-full w-full overflow-hidden bg-background ${className}`}
      style={style}
    >
      <canvas ref={canvasRef} aria-hidden="true" className="absolute inset-0 block h-full w-full" />
      {children ? <div className="relative z-[1] h-full w-full">{children}</div> : null}
    </div>
  );
}

TriconeBitTeeth.displayName = "TriconeBitTeeth";

The usage example imports ./component because it sits next to the component file in this repo. Installed, that file lands at components/ui/tricone-bit-teeth.tsx, so point the import there.

Use when Pick tricone-bit-teeth when the backdrop should read as a rock face being actively worked by a rotating cutting tool — three orbiting cone bodies visibly drive a textured ring of crater strikes, and the ring's fixed 120-degree structure is the legible feature. Pick edm-crater-field instead when the surface should read as a flat metal plate under diffuse, uniform-random erosion with no visible tool or directional structure at all — that component's craters land anywhere with equal probability and never trace a ring, which is the opposite read from this one's rotating-bit mechanic.

Install

npx shadcn add https://design.helpmarq.com/r/tricone-bit-teeth.json

Ask AI

Point an assistant at this component's docs (llms-full.txt) with one click.

Claude, ChatGPT, Grok, and Perplexity open with the prompt already in. Gemini copies it to your clipboard first. Paste it in once the chat opens.

Build spec

Build <TriconeBitTeeth paused? children? className? style?> as a full-bleed 2D-canvas hero built on a rotary tricone rock bit: three cone cutters orbit the bit's centre together (bitAngle = t / LAP_SECONDS * 2*PI, LAP_SECONDS=7.5, an 8-RPM-equivalent sweep deliberately decoupled from the real 60-120 RPM rate per the round 9 aliasing rule) while a CPU-side depth buffer records where their teeth have struck. REAL NUMBERS: 3 cones, 120 degrees apart, orbit radius = 0.32 * min(fieldW, fieldH); TEETH_PER_CONE=11 teeth per cone, spread across a short arc of the cone's own track (not a single point) so one strike cluster reads as a row; a simultaneous 3-cone STRIKE_INTERVAL=0.68s cluster (33 real tooth-strikes/lap collapsed into one legible 3-cone event, per the round 9 'roughly a second between discrete events' rule); each tooth stamps a soft radial-falloff crater (exp(-d^2*2.2) within radius ref*0.028) at amplitude 0.75-1.0 into a Float32Array depth buffer sized FIELD_CELLS=96 cells across the container's SMALLER dimension (aspect-scaled on the other axis); every real frame the WHOLE buffer decays via depth *= exp(-HEAL_RATE*dt), HEAL_RATE=0.9/s (~90% refill in ~1.1s) — birth and decay are both constant and independent, so the ring is a resident population in equilibrium, never a monotonic fill (same steady-state discipline as registry/loud/edm-crater-field, but here births are STRUCTURED — always at the three cones' current orbit position — never uniform-random, which is what keeps this from reading as edm-crater-field's diffuse plate). RENDERING: the depth buffer is painted into an offscreen low-res canvas each frame (raised rock at depth<=0 mixed toward a struck-deep stop as depth approaches 1, both derived from --background/--foreground) and blitted scaled-up with imageSmoothingEnabled=true onto the display canvas — a soft, non-pixel-hard crater read appropriate to rock rather than a machined plate. The three cone BODIES are drawn as small triangular wedges directly on the display canvas each frame, orbiting at bitAngle with an additional decorative own-axis spin (t*6 rad/s, cosmetic only, not tied to any real number) — this vector overlay is what makes 'three cones rotating' legible on its own, independent of the crater ring, and is the deliberate differentiator from edm-crater-field's shader-only height field with no visible tool. A faint mud-jet sweep ring (0.08 peak luminance, radiating from bit centre at JET_SPEED=140px/s, resetting every JET_PERIOD=0.9s) stands in for nozzle discharge and must stay under the crater ring in read priority — a wash, not a highlight. MUST NOT: tint the crater ring, cone bodies or jet sweep with --ns-accent — there is no interactive control on this component (a pointer, if ever added, may only brighten the ring in luminance near the cursor), matching the showpiece recipe's standing 'accent-tinted pointer highlight' defect (edge-yield, granule-churn, shear-billow). RESTING LOOP: t0 shows an already-mid-cycle ring (seeded by two immediate strikeCluster() calls in resize(), at bit angles 0 and one-third of a lap, so mount never shows a blank flat frame); by 2.5s roughly 3-4 more clusters have landed and one or two early clusters have healed back toward flat; by 5s the ring shows continuous turnover — fresh strikes at the leading edge, healed craters fading at the trailing edge — which is the 'alive at rest' proof, not a filling-in or emptying-out pattern. TOKENS: three luminance stops (raised rock, struck-deep, muted) derived from --background, --foreground, --ns-muted via getComputedStyle(document.documentElement) at mount, re-read on a MutationObserver watching documentElement's class — NO PAINT BEFORE THE FIRST READ, readColors() runs before the first resize()/draw(). Light theme (checked first): the dark anchor for struck-deep is mixed from --foreground rather than pure black, since light theme has far less headroom below --background than dark theme does above it — verify the raised/struck-deep/muted three-step ramp stays legible there specifically. `ResizeObserver` reallocates the depth buffer and reseeds two clusters; `IntersectionObserver` (threshold 0) and `visibilitychange` both pause the single rAF loop; `prefers-reduced-motion`/`paused` freeze on STATIC_STRIKE_PHASE — the instant right after the most recent strike lands, at maximum unhealed depth, the single most structured frame in the loop. Canvas is `aria-hidden` decoration with `w-full h-full`; children render over the field in a `relative z-[1]` layer and need a token scrim, since the crater ring can pass under any line of overlaid type.

Props

PropTypeDefaultDescription
paused?booleanfalseFreezes the surface on a composed post-strike still frame without unmounting.
children?React.ReactNode—Rendered in the DOM over the field — eyebrow, headline, CTA.
className?string——
style?React.CSSProperties——