An easter egg for blogs using Sanity and Portable Text: every em dash (—) becomes clickable, revealing whether it was typed by a human or placed by AI.
Human-certified dashes show playful messages like "Certified organic em dash" or "Artisanal punctuation." Uncertified dashes (assumed AI) show messages like "Machine-generated punctuation" or "Algorithmically optimized dash."
The feature has four layers:
A pure function that walks Portable Text blocks at render time, finds em dashes in span text, and splits them into separate spans with a synthetic emDash mark. This mark only exists at render time — nothing is persisted. Spans already marked with humanCertifiedEmDash (from the Studio) are left alone.
A 'use client' leaf component that wraps each em dash in an invisible button. Click it and a tooltip appears with a random message based on the mode ("verified" or "ai"). The button inherits all surrounding text styles so it's visually indistinguishable from normal text — only a faint dotted underline hints at interactivity.
A custom annotation type in the Portable Text schema. Authors can select an em dash in the Studio and mark it as "Human Certified Em Dash" with an optional note. This is stored in the document and survives round-trips.
A one-shot Sanity migration that certifies all existing em dashes as human-typed — because they were written before the feature existed.
Dependencies: @portabletext/react, next-sanity (or any Portable Text renderer), React 19, Sanity Studio v3.
- Add
em-dashes.tsto your lib/utils - Add
HumanVerifiedEmDash.tsxto your components - Add the CSS from
em-dash-styles.cssto your global styles (theanimate-fade-in-upclass + keyframes may already exist in your project) - Wire up the mark renderers in your Portable Text component (see
portable-text-usage.tsx) - Add the
humanCertifiedEmDashannotation to your Sanity block schema - (Optional) Run the migration to certify existing em dashes as human-typed
- No server cost:
annotateEmDashesis a pure function on already-fetched data — no extra queries - No data mutation: The
emDashmark is synthesized client-side only, never persisted to Sanity - Leaf component:
HumanVerifiedEmDashis'use client'but isolated as a leaf — doesn't cause parent re-renders - Allocation-aware: Returns the original array reference when no em dashes exist (avoids unnecessary GC on posts without dashes)
- Accessible: Differentiated
aria-labelby mode,role="status"+aria-live="polite"on tooltips
Built for knut.fyi. MIT License.