An inline circled chip that opens a paper note without leaving the paragraph. The note renders inside a <p>, so its contents are phrasing content only -- spans and inline svg, no divs -- which is why the root diagram is drawn as raw paths rather than laid out. Hover peeks the note, a click pins it, Escape or an outside pointerdown closes it. A layout effect measures the popover against the viewport and writes a --shift custom property, so a chip near either edge slides its note back into view while the arrow stays put over the chip. The chip animates its own SRIF axis from 100 to 420 as it opens, so the circled word grows serifs while the note appears.
Nemawashi is a business design studio in Tokyo. Before code, we tend the — people aligned, viability proven. Then we ship. Fast.
We practice . Alignment before execution: stakeholders on one page, the business case proven in numbers. Then the loop runs — rough sketches become working wireframes in code, a design system grows in the same codebase. Human hands craft the screens that matter. AI carries the rest to production.
Source
"use client";
import { useState } from "react";
import Reveal from "@/components/Reveal";
import { RootsDetail, AugmentedDetail } from "@/components/RevealDetails";
import { paras, type Lang, type Seg } from "@/content/copy";
import ScrambleLink from "../ScrambleLink";
/* The inline reveal chip as the site uses it: a circled word inside running
prose that peeks its note on hover and pins it on click. The note is phrasing
content only (spans + svg) because it lives inside a <p>, and it shifts
itself back into the viewport when it would overflow. Copy comes straight
from content/copy.ts, so both chips (根回し and Augmented Design) carry their
real notes in both languages. */
const [intro, practice] = paras;
const face = (lang: Lang) =>
lang === "jp"
? "font-[family-name:var(--mincho)] [font-variation-settings:normal] tracking-[0.04em] leading-[1.95]"
: "font-[family-name:var(--flux)] leading-[1.75]";
function renderSeg(seg: Seg, i: number, lang: Lang) {
if (seg.t === "reveal") {
return (
<Reveal key={i} label={seg[lang]}>
{seg.id === "roots" ? (
<RootsDetail lang={lang} />
) : (
<AugmentedDetail lang={lang} />
)}
</Reveal>
);
}
return <span key={i}>{seg[lang]}</span>;
}
export default function RevealChips() {
const [lang, setLang] = useState<Lang>("en");
return (
<div
className="fixed inset-0 flex flex-col items-center justify-center overflow-y-auto px-8 py-10"
style={{ background: "var(--paper)", color: "var(--ink)" }}
>
{/* lab-keep: the chips are <button>s carrying the effect, so the preview
recorder's chrome-hiding rule has to leave them alone (lab.css) */}
<div className={`lab-keep max-w-[58ch] ${face(lang)}`}>
<p className="text-[clamp(15px,1.5vw,19px)]">
{intro.segs.map((seg, i) => renderSeg(seg, i, lang))}
</p>
<p className="mt-6 text-[clamp(12px,1.15vw,14px)] opacity-70">
{practice.segs.map((seg, i) => renderSeg(seg, i, lang))}
</p>
</div>
<div className="mt-9 flex items-center gap-3 text-[11px] tracking-[0.1em] uppercase">
{(["en", "jp"] as const).map((l) => (
<button
key={l}
onClick={() => setLang(l)}
style={{
color: lang === l ? "var(--ink)" : "var(--faint)",
fontFamily: "var(--flux)",
fontVariationSettings: '"wght" 400, "SRIF" 100',
}}
>
{l === "en" ? "EN" : "日本語"}
</button>
))}
<span
className="ml-4 normal-case tracking-[0.04em]"
style={{ color: "var(--faint)", fontFamily: "var(--flux)" }}
>
hover a circled word, click to pin
</span>
</div>
<div className="fixed bottom-6 left-6 z-10">
<ScrambleLink
from="REVEAL CHIPS"
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>
);
}