Running text is interleaved with animated gradient chips -- small inline-block colour swatches that expand from zero width on mount with staggered CSS transitions (each chip delayed by index * 100 ms). Each chip renders a canvas with four slowly orbiting radial gradients composited in overlay blend mode over a static linear-gradient background, giving the colour blocks a slow lava-lamp shimmer. A toggle switches between "with colour" (chips visible) and "text only" (chips collapse back to zero width with a reversed stagger), demonstrating how inline media can punctuate editorial text flow.
GIRAGIRA isnew. Theexperiments aren't.
We exploretypeandin
things thatcatchlight.
Source
"use client";
import { useEffect, useRef, useState, useCallback } from "react";
import ScrambleLink from "../ScrambleLink";
// GRGR pastel palette for inline chips
const CHIPS = [
{ gradient: "linear-gradient(135deg, #f99a9e, #fbd9da)", label: "glare" },
{ gradient: "linear-gradient(135deg, #ecdca7, #f7f1de)", label: "light" },
{ gradient: "linear-gradient(135deg, #afcae4, #daebfb)", label: "colour" },
{ gradient: "linear-gradient(135deg, #a39af8, #dcd9fb)", label: "motion" },
{ gradient: "linear-gradient(135deg, #f0bfa3, #f8e6dc)", label: "type" },
{ gradient: "linear-gradient(135deg, #9dcdf6, #daebfb)", label: "play" },
{ gradient: "linear-gradient(135deg, #f99a9e, #a39af8)", label: "depth" },
];
interface Segment {
type: "text" | "chip";
value: string;
chipIdx?: number;
}
const SEGMENTS: Segment[] = [
{ type: "text", value: "GIRAGIRA is" },
{ type: "chip", value: "glare", chipIdx: 0 },
{ type: "text", value: "new. The" },
{ type: "chip", value: "light", chipIdx: 1 },
{ type: "text", value: "experiments aren't." },
{ type: "text", value: "\n" },
{ type: "text", value: "We explore" },
{ type: "chip", value: "colour", chipIdx: 2 },
{ type: "text", value: "type" },
{ type: "chip", value: "motion", chipIdx: 3 },
{ type: "text", value: "and" },
{ type: "chip", value: "play", chipIdx: 5 },
{ type: "text", value: "in" },
{ type: "text", value: "\n" },
{ type: "text", value: "things that" },
{ type: "chip", value: "type", chipIdx: 4 },
{ type: "text", value: "catch" },
{ type: "chip", value: "depth", chipIdx: 6 },
{ type: "text", value: "light." },
];
function AnimatedChip({
gradient,
show,
delay,
}: {
gradient: string;
show: boolean;
delay: number;
}) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const rafRef = useRef<number | null>(null);
useEffect(() => {
const cvs = canvasRef.current;
if (!cvs) return;
const ctx = cvs.getContext("2d")!;
const w = cvs.width;
const h = cvs.height;
function draw(t: number) {
ctx.clearRect(0, 0, w, h);
for (let i = 0; i < 4; i++) {
const x = w * 0.2 + Math.sin(t * 0.0008 + i * 1.8) * w * 0.3;
const y = h * 0.5 + Math.cos(t * 0.0012 + i * 2.1) * h * 0.25;
const r = 20 + Math.sin(t * 0.001 + i) * 8;
const hue = (t * 0.02 + i * 60) % 360;
const grad = ctx.createRadialGradient(x, y, 0, x, y, r);
grad.addColorStop(0, `hsla(${hue}, 70%, 82%, 0.6)`);
grad.addColorStop(1, `hsla(${hue}, 70%, 82%, 0)`);
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.fill();
}
rafRef.current = requestAnimationFrame(draw);
}
rafRef.current = requestAnimationFrame(draw);
return () => {
if (rafRef.current) cancelAnimationFrame(rafRef.current);
};
}, []);
return (
<span
className="inline-block overflow-hidden align-middle"
style={{
width: show ? "clamp(50px, 7vw, 110px)" : "0",
opacity: show ? 1 : 0,
marginLeft: show ? "0.35em" : "0",
marginRight: show ? "0.35em" : "0.25em",
borderRadius: "6px",
cursor: "pointer",
transform: "scale(1)",
transitionProperty: "width, opacity, margin-left, margin-right, transform",
transitionDuration: "0.5s, 0.35s, 0.5s, 0.5s, 0.2s",
transitionTimingFunction:
"cubic-bezier(0.4, 0, 0.2, 1), ease, cubic-bezier(0.4, 0, 0.2, 1), cubic-bezier(0.4, 0, 0.2, 1), ease",
transitionDelay: `${delay}s`,
}}
onMouseEnter={(e) => {
(e.currentTarget as HTMLElement).style.transform = "scale(1.08)";
}}
onMouseLeave={(e) => {
(e.currentTarget as HTMLElement).style.transform = "scale(1)";
}}
>
<span
className="block relative"
style={{
height: "clamp(35px, 4.95vw, 80px)",
background: gradient,
borderRadius: "6px",
overflow: "hidden",
}}
>
<canvas
ref={canvasRef}
width={120}
height={80}
className="absolute inset-0 w-full h-full"
style={{ mixBlendMode: "overlay", opacity: 0.7 }}
/>
</span>
</span>
);
}
export default function InlineText() {
const [mode, setMode] = useState<"chips" | "text">("chips");
const [revealed, setRevealed] = useState(false);
useEffect(() => {
const timer = setTimeout(() => setRevealed(true), 300);
return () => clearTimeout(timer);
}, []);
const showChips = mode === "chips" && revealed;
const chipCount = SEGMENTS.filter((s) => s.type === "chip").length;
let chipSeq = 0;
return (
<div
className="fixed inset-0 flex flex-col items-start justify-center select-none"
style={{
background: "#f5f3f0",
padding: "clamp(24px, 5vw, 80px)",
}}
>
<div className="flex flex-col">
<p
style={{
fontFamily: "var(--font-flux), system-ui, sans-serif",
fontSize: "clamp(32px, 4.5vw, 72px)",
fontVariationSettings: "'wght' 300, 'SRIF' 0",
lineHeight: 1.4,
color: "#1a1a2e",
}}
>
{SEGMENTS.map((seg, i) => {
if (seg.type === "text" && seg.value === "\n") {
return <br key={i} />;
}
if (seg.type === "chip" && seg.chipIdx != null) {
const chip = CHIPS[seg.chipIdx];
const idx = chipSeq++;
const delay = showChips
? idx * 0.1
: (chipCount - 1 - idx) * 0.05;
return (
<AnimatedChip
key={i}
gradient={chip.gradient}
show={showChips}
delay={delay}
/>
);
}
return (
<span key={i} className="whitespace-nowrap">
{seg.value}
</span>
);
})}
</p>
<div
className="flex gap-6"
style={{
marginTop: "clamp(40px, 5vw, 80px)",
fontFamily: "var(--font-flux), system-ui, sans-serif",
fontSize: "clamp(16px, 2vw, 32px)",
fontVariationSettings: "'wght' 300, 'SRIF' 0",
}}
>
<button
onClick={() => setMode("chips")}
className="border-0 bg-transparent cursor-pointer p-0"
style={{
fontFamily: "inherit",
fontSize: "inherit",
fontVariationSettings: "inherit",
letterSpacing: "-0.01em",
color: mode === "chips" ? "#1a1a2e" : "rgba(26,26,46,0.3)",
transition: "color 0.2s",
}}
>
with colour
</button>
<button
onClick={() => setMode("text")}
className="border-0 bg-transparent cursor-pointer p-0"
style={{
fontFamily: "inherit",
fontSize: "inherit",
fontVariationSettings: "inherit",
letterSpacing: "-0.01em",
color: mode === "text" ? "#1a1a2e" : "rgba(26,26,46,0.3)",
transition: "color 0.2s",
}}
>
text only
</button>
</div>
</div>
<div className="fixed bottom-6 left-6" style={{ zIndex: 10 }}>
<ScrambleLink
from="INLINE"
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>
);
}