"use client"; import { useId } from "react"; /* * Tab Overload – a small card illustration, in a single self-contained file. * Three overlapping browser windows; the front one is active with colored dots, an outline and a glow. * Hover a window behind it and the highlight moves over to that one. * The artwork is drawn on a 360 × 240 viewBox and fades out towards the right and bottom edge. */ export type TabOverloadProps = { className?: string; /** Accessible description of the scene */ label?: string; }; /** Offsets of the three windows (top-left corner of each frame) */ const windows = [ { n: 1, x: 30, y: 68 }, { n: 2, x: 125, y: 56 }, { n: 3, x: 220, y: 44 }, ] as const; export function TabOverload({ className, label = "Three overlapping browser windows full of placeholder content", }: TabOverloadProps) { const id = "ui-to" + useId().replace(/[^a-zA-Z0-9_-]/g, ""); return (
{/* React 19 hoists this into once, no matter how many instances render */}
); } /* ---------- Styles ---------- */ const css = /* css */ ` .ui-to { position: relative; width: 100%; aspect-ratio: 3 / 2; overflow: hidden; /* Fade out towards the right and bottom edge */ -webkit-mask-image: linear-gradient(to right, #000 70%, transparent 100%), linear-gradient(to bottom, #000 70%, transparent 100%); -webkit-mask-composite: source-in; mask-image: linear-gradient(to right, #000 70%, transparent 100%), linear-gradient(to bottom, #000 70%, transparent 100%); mask-composite: intersect; } .ui-to-art { position: absolute; inset: 0; display: block; width: 100%; height: 100%; } /* Placeholder blocks shimmer softly */ .ui-to-bar { animation: ui-to-shimmer 2.4s ease-in-out infinite; } @keyframes ui-to-shimmer { 0%, 100% { fill: #EFF1F4; } 50% { fill: #DDE1E5; } } /* The highlight follows the hovered window; the front window is active by default */ .ui-to-blobs, .ui-to-front { opacity: 0; pointer-events: none; transition: opacity 0.3s ease; } .ui-to-content { opacity: 0.2; pointer-events: none; transition: opacity 0.3s ease; } .ui-to-blobs-3, .ui-to-front-3, .ui-to-content-3 { opacity: 1; } .ui-to-art:has(.ui-to-tab-1:hover) .ui-to-blobs-1, .ui-to-art:has(.ui-to-tab-1:hover) .ui-to-front-1, .ui-to-art:has(.ui-to-tab-1:hover) .ui-to-content-1, .ui-to-art:has(.ui-to-tab-2:hover) .ui-to-blobs-2, .ui-to-art:has(.ui-to-tab-2:hover) .ui-to-front-2, .ui-to-art:has(.ui-to-tab-2:hover) .ui-to-content-2 { opacity: 1; } .ui-to-art:has(.ui-to-tab-1:hover, .ui-to-tab-2:hover) .ui-to-blobs-3, .ui-to-art:has(.ui-to-tab-1:hover, .ui-to-tab-2:hover) .ui-to-front-3 { opacity: 0; } .ui-to-art:has(.ui-to-tab-1:hover, .ui-to-tab-2:hover) .ui-to-content-3 { opacity: 0.2; } @media (prefers-reduced-motion: reduce) { .ui-to-bar { animation: none; } .ui-to-blobs, .ui-to-front, .ui-to-content { transition: none; } } /* Dark mode: follows a .dark class or data-theme="dark" on any ancestor, e.g. */ :where(.dark, [data-theme="dark"]) .ui-to-frame { fill: #1b1b1d; stroke: rgba(255, 255, 255, 0.1); } :where(.dark, [data-theme="dark"]) .ui-to-dot { fill: #3a3a3f; } :where(.dark, [data-theme="dark"]) .ui-to-page { fill: #232326; stroke: rgba(255, 255, 255, 0.08); } :where(.dark, [data-theme="dark"]) .ui-to-bar { fill: #2e2e32; } @media (prefers-reduced-motion: no-preference) { :where(.dark, [data-theme="dark"]) .ui-to-bar { animation-name: ui-to-shimmer-dark; } } @keyframes ui-to-shimmer-dark { 0%, 100% { fill: #2e2e32; } 50% { fill: #3a3a3f; } } `;