This component no longer exists. It was in ns-ui’s manifest when we first indexed it and is not there now — the registry served its inventory on 2026-08-07 and this item was not on it. The install command below will fail. It is kept here because a tutorial somewhere still tells people to run it.
Badge Unread Tarnish
ns-ui/badge-unread-tarnishRemovedComponent
Unread badge that tarnishes like brass: solid when fresh, an outline within a day, a muted ring after a week — new activity instantly re-polishes it with a small flare.
Live preview
live · rendered by the registry
Rendered by ns-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://design.helpmarq.com/r/badge-unread-tarnish.jsonSource
1"use client";23import { useEffect, useId, useRef, useState } from "react";45// ---------------------------------------------------------------------------6// PatinaPip — an unread badge that ages like brass instead of just counting.7// Its data-stage is derived from the newest item's timestamp, not the count:8// fresh (< 1 day) is a solid --foreground fill with background-token digits,9// waning (< 1 week) thins to a transparent fill with a 1.5px --foreground10// ring, dormant (>= 1 week) settles into a 1px --muted ring with --muted11// digits — fill, outline weight and text tone all move together so the three12// stages read apart under monochrome viewing, never by hue alone. Ordinary13// aging (the interval tick, or a prop simply reflecting more elapsed time)14// crossfades those properties over 400ms via a plain CSS transition; new15// activity is different in kind, not degree, so it's fast-pathed around that16// transition (a one-frame transitionDuration:0 clamp) straight to "fresh"17// and topped with a 200ms spring-eased scale(1 → 1.12 → 1) flare via the Web18// Animations API, so re-polish always reads as instantaneous, not eased.19// The pip itself is decorative (role="img", not a control) — the accessible20// name lives on it as an aria-label ("3 unread, newest 2 days ago") for a21// focusable nav item to pull in via aria-describedby, and a visually-hidden22// aria-live region separately announces new arrivals as they happen. Pure23// DOM + CSS, zero dependencies, no canvas.24// ---------------------------------------------------------------------------2526export type PatinaPipStage = "fresh" | "waning" | "dormant";2728export interface PatinaPipProps {29 /** unread item count. Incidental to the component's point — see newestTimestamp. */30 count: number;31 /** timestamp of the newest unread item; the sole driver of the tarnish stage. */32 newestTimestamp: number | Date;33 /** id placed on the pip itself, e.g. so a nav item can aria-describedby it. Defaults to an internal useId. */34 id?: string;35 className?: string;36}3738const MINUTE_MS = 60_000;39const HOUR_MS = 60 * MINUTE_MS;40const DAY_MS = 24 * HOUR_MS;41const WEEK_MS = 7 * DAY_MS;4243// how often the pip re-checks its own age while mounted, so a badge left44// open on screen tarnishes on its own without any new prop ever arriving.45const TICK_MS = MINUTE_MS;4647const FLARE_KEYFRAMES: Keyframe[] = [48 { transform: "scale(1)" },49 { transform: "scale(1.12)", offset: 0.5 },50 { transform: "scale(1)" },51];52// … truncated
