From 2d7823bcc9fc6aef6d22447412238a8d34731e5c Mon Sep 17 00:00:00 2001 From: Chronyyx Date: Sat, 16 May 2026 06:50:54 -0400 Subject: [PATCH] feat: added new shiny pill component and added delay on the spotlight follow --- package.json | 1 + src/components/glow-follow.ts | 55 ++++++++++++- src/components/shine-pill.tsx | 13 +++ src/index.ts | 1 + src/styles.css | 150 +++++++++++++++++++++++++++++++++- 5 files changed, 217 insertions(+), 3 deletions(-) create mode 100644 src/components/shine-pill.tsx diff --git a/package.json b/package.json index 420f036..819b0ce 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "./select": "./src/components/select.tsx", "./separator": "./src/components/separator.tsx", "./sheet": "./src/components/sheet.tsx", + "./shine-pill": "./src/components/shine-pill.tsx", "./skeleton": "./src/components/skeleton.tsx", "./slider": "./src/components/slider.tsx", "./sonner": "./src/components/sonner.tsx", diff --git a/src/components/glow-follow.ts b/src/components/glow-follow.ts index 291bd2c..5dc36ee 100644 --- a/src/components/glow-follow.ts +++ b/src/components/glow-follow.ts @@ -1,5 +1,17 @@ const GLOW_FOLLOW_KEY = "__kleffioGlowFollowInstalled"; const GLOW_FOLLOW_SELECTOR = ".overview-glass-card[data-frosted][data-spotlight=\"true\"]"; +const GLOW_FOLLOW_EASE = 0.055; +const GLOW_FOLLOW_SETTLE_DISTANCE = 0.1; + +type GlowFollowState = { + currentX: number; + currentY: number; + targetX: number; + targetY: number; + frame: number | null; +}; + +const glowFollowStates = new WeakMap(); declare global { interface Window { @@ -21,9 +33,48 @@ export function setGlowFollowPosition(element: HTMLElement, clientX: number, cli const rect = element.getBoundingClientRect(); const x = Math.max(0, Math.min(rect.width, clientX - rect.left)); const y = Math.max(0, Math.min(rect.height, clientY - rect.top)); + const state = glowFollowStates.get(element); + + if (!state) { + glowFollowStates.set(element, { + currentX: x, + currentY: y, + targetX: x, + targetY: y, + frame: null, + }); + + element.style.setProperty("--glow-follow-x", `${x}px`); + element.style.setProperty("--glow-follow-y", `${y}px`); + return; + } + + state.targetX = x; + state.targetY = y; + + if (state.frame === null) { + state.frame = window.requestAnimationFrame(() => animateGlowFollow(element, state)); + } +} + +function animateGlowFollow(element: HTMLElement, state: GlowFollowState) { + const nextX = state.currentX + (state.targetX - state.currentX) * GLOW_FOLLOW_EASE; + const nextY = state.currentY + (state.targetY - state.currentY) * GLOW_FOLLOW_EASE; + const distanceX = Math.abs(state.targetX - nextX); + const distanceY = Math.abs(state.targetY - nextY); + + state.currentX = distanceX < GLOW_FOLLOW_SETTLE_DISTANCE ? state.targetX : nextX; + state.currentY = distanceY < GLOW_FOLLOW_SETTLE_DISTANCE ? state.targetY : nextY; + + element.style.setProperty("--glow-follow-x", `${state.currentX}px`); + element.style.setProperty("--glow-follow-y", `${state.currentY}px`); + + if (state.currentX === state.targetX && state.currentY === state.targetY) { + state.frame = null; + return; + } - element.style.setProperty("--glow-follow-x", `${x}px`); - element.style.setProperty("--glow-follow-y", `${y}px`); + state.frame = window.requestAnimationFrame(() => animateGlowFollow(element, state)); } export function clearGlowFollowPosition(element: HTMLElement) { diff --git a/src/components/shine-pill.tsx b/src/components/shine-pill.tsx new file mode 100644 index 0000000..f18bcbe --- /dev/null +++ b/src/components/shine-pill.tsx @@ -0,0 +1,13 @@ +import * as React from "react"; + +import { cn } from "../utils"; + +function ShinePill({ className, children, ...props }: React.ComponentProps<"span">) { + return ( + + {children} + + ); +} + +export { ShinePill }; diff --git a/src/index.ts b/src/index.ts index c9566d7..c58f905 100644 --- a/src/index.ts +++ b/src/index.ts @@ -48,6 +48,7 @@ export * from "./components/scroll-area"; export * from "./components/select"; export * from "./components/separator"; export * from "./components/sheet"; +export * from "./components/shine-pill"; export * from "./components/skeleton"; export * from "./components/slider"; export * from "./components/spinner"; diff --git a/src/styles.css b/src/styles.css index 2f5502e..0d6dec9 100644 --- a/src/styles.css +++ b/src/styles.css @@ -316,7 +316,7 @@ z-index: 0; pointer-events: none; background: radial-gradient( - 15rem circle at var(--glow-follow-x, 50%) var(--glow-follow-y, 50%), + 15rem circle at var(--glow-follow-display-x, var(--glow-follow-x, 50%)) var(--glow-follow-display-y, var(--glow-follow-y, 50%)), rgba(245, 181, 23, 0.12), rgba(245, 181, 23, 0.045) 34%, transparent 68% @@ -558,6 +558,153 @@ -webkit-backdrop-filter: blur(14px); } + .ui-shine-pill { + position: relative; + display: inline-flex; + width: fit-content; + max-width: 100%; + align-items: center; + justify-content: center; + overflow: hidden; + border: 1px solid rgba(245, 181, 23, 0.24); + border-radius: 9999px; + background: rgba(245, 181, 23, 0.18); + padding: 0.45rem 1.05rem; + color: rgba(255, 244, 214, 0.9); + font-size: 0.78rem; + font-weight: 650; + line-height: 1.2; + text-align: center; + box-shadow: + 0 0 0 1px rgba(255, 255, 255, 0.055) inset, + 0 1px 0 rgba(255, 255, 255, 0.12) inset, + 0 0 28px rgba(245, 181, 23, 0.16); + backdrop-filter: blur(16px) saturate(1.18); + -webkit-backdrop-filter: blur(16px) saturate(1.18); + isolation: isolate; + } + + .ui-shine-pill::after { + content: ""; + position: absolute; + inset-block: -80%; + left: -45%; + width: 38%; + border-radius: inherit; + background: linear-gradient( + 100deg, + transparent 0%, + rgba(255, 255, 255, 0.08) 24%, + rgba(255, 255, 255, 0.78) 48%, + rgba(250, 215, 130, 0.42) 58%, + transparent 100% + ); + filter: blur(1px); + opacity: 0.86; + transform: translate3d(-120%, 0, 0) rotate(10deg); + animation: ui-shine-pill-sweep 3.8s cubic-bezier(0.22, 1, 0.36, 1) infinite; + pointer-events: none; + } + + .ui-shine-pill__content { + position: relative; + z-index: 1; + white-space: nowrap; + } + + .ui-shine-chip { + position: relative; + display: inline-flex; + max-width: 100%; + align-items: center; + justify-content: center; + overflow: hidden; + border: 1px solid rgba(245, 181, 23, 0.24); + border-radius: 9999px; + background: rgba(245, 181, 23, 0.12); + padding: 0.125rem 0.45rem; + color: rgba(255, 236, 185, 0.8); + font-size: 0.625rem; + font-weight: 650; + line-height: 1rem; + box-shadow: + 0 0 0 1px rgba(255, 255, 255, 0.04) inset, + 0 0 14px rgba(245, 181, 23, 0.08); + backdrop-filter: blur(12px) saturate(1.1); + -webkit-backdrop-filter: blur(12px) saturate(1.1); + } + + .ui-shine-chip::after { + content: ""; + position: absolute; + inset-block: -110%; + left: -55%; + width: 42%; + background: linear-gradient( + 100deg, + transparent 0%, + rgba(255, 255, 255, 0.06) 24%, + rgba(255, 255, 255, 0.62) 48%, + rgba(250, 215, 130, 0.34) 58%, + transparent 100% + ); + filter: blur(1px); + opacity: 0.74; + transform: translate3d(-125%, 0, 0) rotate(10deg); + animation: ui-shine-chip-sweep 4.2s cubic-bezier(0.22, 1, 0.36, 1) infinite; + pointer-events: none; + } + + .ui-shine-chip__content { + position: relative; + z-index: 1; + white-space: nowrap; + } + + @media (max-width: 380px) { + .ui-shine-pill__content { + white-space: normal; + } + } + + @media (prefers-reduced-motion: reduce) { + .ui-shine-pill::after { + animation: none; + transform: translate3d(130%, 0, 0) rotate(10deg); + } + + .ui-shine-chip::after { + animation: none; + transform: translate3d(130%, 0, 0) rotate(10deg); + } + } + + @keyframes ui-shine-pill-sweep { + + 0%, + 38% { + transform: translate3d(-130%, 0, 0) rotate(10deg); + } + + 70%, + 100% { + transform: translate3d(520%, 0, 0) rotate(10deg); + } + } + + @keyframes ui-shine-chip-sweep { + + 0%, + 45% { + transform: translate3d(-125%, 0, 0) rotate(10deg); + } + + 76%, + 100% { + transform: translate3d(470%, 0, 0) rotate(10deg); + } + } + .component-showcase-stats { display: grid; grid-template-columns: 1fr; @@ -633,6 +780,7 @@ position: relative; display: flex; flex-direction: column; + padding: 1.25rem; } .component-showcase-section-title {