NEMAWASHI LAB

Spoiler

Canvas

A phrase renders invisible while a canvas draws a cloud of particles sampled from its own glyph shapes: the text is painted once into a 2x offscreen canvas, every third pixel with alpha above 0.35 becomes a dot, and each dot twinkles on its own sine phase at 30 fps. A tap opens an iris -- a radial-gradient mask on the text grows from the touch point over 820 ms on an easeOut curve, while each particle keeps twinkling until the wavefront reaches its distance, then dies over the following 26 px, sliding outward along its own radius as it goes. Tile mode swaps the plain host for a solid swatch that shuffles every 1.3-3.2 s and re-rolls on hover, with particles and revealed text taking the swatch's contrast colour. Reduced motion falls back to a CSS blur that simply clears on tap.

Source

SpoilerText.tsx73 lines
"use client";

import { useState } from "react";
import Spoiler from "@/components/Spoiler";
import ScrambleLink from "../ScrambleLink";

/* The Telegram/Threads spoiler, shown on the three studio beliefs. Each phrase
   renders invisible under a cloud of particles sampled from its own glyphs; a
   tap opens an iris from the touch point and the cloud dissolves along the
   wavefront. `tile` swaps the plain host for a solid swatch that shuffles on
   its own and re-rolls on hover. Reveals are one-way, so `run` remounts the
   set to re-obscure it. */

const BELIEFS = [
  "Make it exist first",
  "Embrace imperfection",
  "God is in the details",
];

const FACE = "font-[family-name:var(--flux)]";

const action: React.CSSProperties = {
  color: "var(--faint)",
  fontFamily: "var(--flux)",
  fontVariationSettings: '"wght" 400, "SRIF" 100',
};

export default function SpoilerText() {
  const [run, setRun] = useState(0);
  const [tile, setTile] = useState(false);

  return (
    <div
      className="fixed inset-0 flex flex-col items-center justify-center gap-5 overflow-hidden px-6"
      style={{ background: "var(--paper)" }}
    >
      {BELIEFS.map((text) => (
        <Spoiler
          key={`${text}-${run}-${tile}`}
          text={text}
          tile={tile}
          // lab-keep: the spoiler's host IS the button, so the preview
          // recorder's chrome-hiding rule has to leave it alone (lab.css)
          className="lab-keep text-[clamp(1.1rem,2.6vw,2rem)] px-3 py-1 rounded-[10px]"
          textClassName={FACE}
        />
      ))}

      <div className="mt-8 flex gap-6 text-[11px] tracking-[0.1em] uppercase">
        <button onClick={() => setRun((n) => n + 1)} style={action}>
          re-obscure
        </button>
        <button
          onClick={() => setTile((t) => !t)}
          style={{ ...action, color: tile ? "var(--ink)" : "var(--faint)" }}
        >
          tile mode
        </button>
      </div>

      <div className="fixed bottom-6 left-6 z-10">
        <ScrambleLink
          from="SPOILER"
          to="← LAB"
          href="/lab"
          className="text-[11px] tracking-[0.1em] text-foreground/30 uppercase hover:text-foreground/60 transition-colors"
          style={{ fontVariationSettings: "'wght' 400, 'SRIF' 100" }}
        />
      </div>
    </div>
  );
}

NEMAWASHI — Kotaro Abe