21st.dev 원본
Progressive Blur Slider · Motion Primitives
데이터 · MIT
갤러리 안에서는 화면을 벗어나거나 움직임을 멈추면 예제도 닫힙니다. 다시 재생할 때는 처음부터 시작해요.
저장한 HTML에서는 갤러리의 재생 설정이 적용되지 않아요.
SOURCE FILES
원본 코드 읽기
수집한 원본 소스와 실행 안내를 함께 제공합니다. 실행 HTML에는 미리보기를 위한 실행 환경이 들어 있습니다.
baseline-example.tsx
import { InfiniteSlider } from '@/components/ui/infinite-slider';
import { ProgressiveBlur } from '@/components/ui/progressive-blur';
const logos = [
{
id: "logo-2",
description: "Figma",
image: "https://cdn.21st.dev/assets/mirror/25/25a0515c472578f5dfcfeb765be5383f6893cda1f0f1560f69d3bcabdda71693.svg",
className: "h-7 w-auto",
},
{
id: "logo-3",
description: "Next.js",
image: "https://cdn.21st.dev/assets/mirror/8f/8f4aec13fdd1bf3b6b5566a6c4f5feade1c5e5b492fa81ba32f62f3aa155220e.svg",
className: "h-7 w-auto",
},
{
id: "logo-6",
description: "Supabase",
image: "https://cdn.21st.dev/assets/mirror/54/5414744d341683260469fbaa9a518e0a4b3000ddd714ce5d0525f8e2cebcb064.svg",
className: "h-7 w-auto",
},
{
id: "logo-8",
description: "Vercel",
image: "https://www.shadcnblocks.com/images/block/logos/vercel.svg",
className: "h-7 w-auto",
},
];
export function LogosSlider() {
return (
<div className='relative h-[100px] w-full overflow-hidden'>
<InfiniteSlider
className='flex h-full w-full items-center'
duration={30}
gap={48}
>
{logos.map((logo) => (
<div
key={logo.id}
className='flex w-32 items-center justify-center'
>
<img
src={logo.image}
alt={logo.description}
className={logo.className}
/>
</div>
))}
</InfiniteSlider>
<ProgressiveBlur
className='pointer-events-none absolute top-0 left-0 h-full w-[200px]'
direction='left'
blurIntensity={1}
/>
<ProgressiveBlur
className='pointer-events-none absolute top-0 right-0 h-full w-[200px]'
direction='right'
blurIntensity={1}
/>
</div>
);
}LICENSE
MIT License
Copyright (c) 2024 ibelick
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
app/page.tsx
함께 쓰는 파일 20개 보기
upstream/app/docs/progressive-blur/progressive-blur-slider.tsx
import { InfiniteSlider } from '@/components/core/infinite-slider';
import { ProgressiveBlur } from '@/components/core/progressive-blur';
export function ProgressiveBlurSlider() {
return (
<div className='relative h-[350px] w-full overflow-hidden'>
<InfiniteSlider className='flex h-full w-full items-center'>
<div className='w-32 text-center text-4xl font-[450] text-black dark:text-white'>
1
</div>
<div className='w-32 text-center text-4xl font-[450] text-black dark:text-white'>
2
</div>
<div className='w-32 text-center text-4xl font-[450] text-black dark:text-white'>
3
</div>
<div className='w-32 text-center text-4xl font-[450] text-black dark:text-white'>
4
</div>
<div className='w-32 text-center text-4xl font-[450] text-black dark:text-white'>
5
</div>
<div className='w-32 text-center text-4xl font-[450] text-black dark:text-white'>
6
</div>
<div className='w-32 text-center text-4xl font-[450] text-black dark:text-white'>
7
</div>
<div className='w-32 text-center text-4xl font-[450] text-black dark:text-white'>
8
</div>
<div className='w-32 text-center text-4xl font-[450] text-black dark:text-white'>
9
</div>
</InfiniteSlider>
<ProgressiveBlur
className='pointer-events-none absolute top-0 left-0 h-full w-[200px]'
direction='left'
blurIntensity={1}
/>
<ProgressiveBlur
className='pointer-events-none absolute top-0 right-0 h-full w-[200px]'
direction='right'
blurIntensity={1}
/>
</div>
);
}
author/components/core/infinite-slider.tsx
'use client';
import { cn } from '@/lib/utils';
import { useMotionValue, animate, motion } from 'motion/react';
import { useState, useEffect } from 'react';
import useMeasure from 'react-use-measure';
export type InfiniteSliderProps = {
children: React.ReactNode;
gap?: number;
duration?: number;
durationOnHover?: number;
direction?: 'horizontal' | 'vertical';
reverse?: boolean;
className?: string;
};
export function InfiniteSlider({
children,
gap = 16,
duration = 25,
durationOnHover,
direction = 'horizontal',
reverse = false,
className,
}: InfiniteSliderProps) {
const [currentDuration, setCurrentDuration] = useState(duration);
const [ref, { width, height }] = useMeasure();
const translation = useMotionValue(0);
const [isTransitioning, setIsTransitioning] = useState(false);
const [key, setKey] = useState(0);
useEffect(() => {
let controls;
const size = direction === 'horizontal' ? width : height;
const contentSize = size + gap;
const from = reverse ? -contentSize / 2 : 0;
const to = reverse ? 0 : -contentSize / 2;
if (isTransitioning) {
controls = animate(translation, [translation.get(), to], {
ease: 'linear',
duration:
currentDuration * Math.abs((translation.get() - to) / contentSize),
onComplete: () => {
setIsTransitioning(false);
setKey((prevKey) => prevKey + 1);
},
});
} else {
controls = animate(translation, [from, to], {
ease: 'linear',
duration: currentDuration,
repeat: Infinity,
repeatType: 'loop',
repeatDelay: 0,
onRepeat: () => {
translation.set(from);
},
});
}
return controls?.stop;
}, [
key,
translation,
currentDuration,
width,
height,
gap,
isTransitioning,
direction,
reverse,
]);
const hoverProps = durationOnHover
? {
onHoverStart: () => {
setIsTransitioning(true);
setCurrentDuration(durationOnHover);
},
onHoverEnd: () => {
setIsTransitioning(true);
setCurrentDuration(duration);
},
}
: {};
return (
<div className={cn('overflow-hidden', className)}>
<motion.div
className='flex w-max'
style={{
...(direction === 'horizontal'
? { x: translation }
: { y: translation }),
gap: `${gap}px`,
flexDirection: direction === 'horizontal' ? 'row' : 'column',
}}
ref={ref}
{...hoverProps}
>
{children}
{children}
</motion.div>
</div>
);
}
author/components/core/progressive-blur.tsx
'use client';
import { cn } from '@/lib/utils';
import { HTMLMotionProps, motion } from 'motion/react';
export const GRADIENT_ANGLES = {
top: 0,
right: 90,
bottom: 180,
left: 270,
};
export type ProgressiveBlurProps = {
direction?: keyof typeof GRADIENT_ANGLES;
blurLayers?: number;
className?: string;
blurIntensity?: number;
} & HTMLMotionProps<'div'>;
export function ProgressiveBlur({
direction = 'bottom',
blurLayers = 8,
className,
blurIntensity = 0.25,
...props
}: ProgressiveBlurProps) {
const layers = Math.max(blurLayers, 2);
const segmentSize = 1 / (blurLayers + 1);
return (
<div className={cn('relative', className)}>
{Array.from({ length: layers }).map((_, index) => {
const angle = GRADIENT_ANGLES[direction];
const gradientStops = [
index * segmentSize,
(index + 1) * segmentSize,
(index + 2) * segmentSize,
(index + 3) * segmentSize,
].map(
(pos, posIndex) =>
`rgba(255, 255, 255, ${posIndex === 1 || posIndex === 2 ? 1 : 0}) ${pos * 100}%`
);
const gradient = `linear-gradient(${angle}deg, ${gradientStops.join(
', '
)})`;
return (
<motion.div
key={index}
className='pointer-events-none absolute inset-0 rounded-[inherit]'
style={{
maskImage: gradient,
WebkitMaskImage: gradient,
backdropFilter: `blur(${index * blurIntensity}px)`,
}}
{...props}
/>
);
})}
</div>
);
}
author/lib/utils.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
author/app/globals.css
@import 'tailwindcss';
@plugin "@tailwindcss/typography";
@plugin 'tailwindcss-animate';
@custom-variant dark (&:is(.dark *));
@theme {
/* connect the Tailwind mono font to the Geist variable that's being injected */
--default-mono-font-family: '__GeistMono_c1e5c9', ui-monospace, SFMono-Regular, Roboto Mono, Menlo, Monaco, Liberation Mono, DejaVu Sans Mono, Courier New, monospace;
--font-mono: '__GeistMono_c1e5c9', ui-monospace, SFMono-Regular, Roboto Mono, Menlo, Monaco, Liberation Mono, DejaVu Sans Mono, Courier New, monospace;
--default-mono-font-feature-settings: normal;
--default-mono-font-variation-settings: normal;
}
/*
The default border color has changed to `currentColor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
@utility step {
counter-increment: step;
&:before {
@apply absolute inline-flex h-9 w-9 items-center justify-center rounded-full border border-zinc-200 bg-white text-center -indent-px font-mono text-base font-medium dark:border-zinc-800 dark:bg-zinc-950;
@apply ml-[-50px] mt-[-4px];
content: counter(step);
}
}
@layer utilities {
:root {
--scrollbar-color: rgba(0, 0, 0, 0.3);
/* shiki theme noir */
--shiki-foreground: #ffffff;
--shiki-token-constant: #a7a7a7;
--shiki-token-string: #a7a7a7;
--shiki-token-comment: #666666;
--shiki-token-keyword: #a7a7a7;
--shiki-token-parameter: #a7a7a7;
--shiki-token-function: #ffffff;
--shiki-token-string-expression: #a7a7a7;
--shiki-token-punctuation: #a7a7a7;
--shiki-token-link: #a7a7a7;
--shiki-token-number: #ffffff;
--shiki-token-property: #a7a7a7;
}
:root.dark {
--scrollbar-color: rgba(255, 255, 255, 0.3);
}
html {
-webkit-tap-highlight-color: transparent;
scrollbar-gutter: stable;
scrollbar-color: var(--scrollbar-color) transparent;
scrollbar-width: thin;
}
/* fix radix dropdown-menu layout shift */
html body[data-scroll-locked] {
margin-right: 0 !important;
}
code {
font-family: var(--font-geist-mono);
font-feature-settings: var(--default-mono-font-feature-settings);
font-variation-settings: var(--default-mono-font-variation-settings);
}
}
:root {
--background: hsl(0 0% 100%);
--foreground: hsl(240 10% 3.9%);
--card: hsl(0 0% 100%);
--card-foreground: hsl(240 10% 3.9%);
--popover: hsl(0 0% 100%);
--popover-foreground: hsl(240 10% 3.9%);
--primary: hsl(240 5.9% 10%);
--primary-foreground: hsl(0 0% 98%);
--secondary: hsl(240 4.8% 95.9%);
--secondary-foreground: hsl(240 5.9% 10%);
--muted: hsl(240 4.8% 95.9%);
--muted-foreground: hsl(240 3.8% 46.1%);
--accent: hsl(240 4.8% 95.9%);
--accent-foreground: hsl(240 5.9% 10%);
--destructive: hsl(0 84.2% 60.2%);
--destructive-foreground: hsl(0 0% 98%);
--border: hsl(240 5.9% 90%);
--input: hsl(240 5.9% 90%);
--ring: hsl(240 10% 3.9%);
--chart-1: hsl(12 76% 61%);
--chart-2: hsl(173 58% 39%);
--chart-3: hsl(197 37% 24%);
--chart-4: hsl(43 74% 66%);
--chart-5: hsl(27 87% 67%);
--radius: 0.6rem;
}
.dark {
--background: hsl(240 10% 3.9%);
--foreground: hsl(0 0% 98%);
--card: hsl(240 10% 3.9%);
--card-foreground: hsl(0 0% 98%);
--popover: hsl(240 10% 3.9%);
--popover-foreground: hsl(0 0% 98%);
--primary: hsl(0 0% 98%);
--primary-foreground: hsl(240 5.9% 10%);
--secondary: hsl(240 3.7% 15.9%);
--secondary-foreground: hsl(0 0% 98%);
--muted: hsl(240 3.7% 15.9%);
--muted-foreground: hsl(240 5% 64.9%);
--accent: hsl(240 3.7% 15.9%);
--accent-foreground: hsl(0 0% 98%);
--destructive: hsl(0 62.8% 30.6%);
--destructive-foreground: hsl(0 0% 98%);
--border: hsl(240 3.7% 15.9%);
--input: hsl(240 3.7% 15.9%);
--ring: hsl(240 4.9% 83.9%);
--chart-1: hsl(220 70% 50%);
--chart-2: hsl(160 60% 45%);
--chart-3: hsl(30 80% 55%);
--chart-4: hsl(280 65% 60%);
--chart-5: hsl(340 75% 55%);
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
author/package.json
{
"name": "motion-primitives",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"build:registry": "tsx scripts/registry-build.ts"
},
"dependencies": {
"@code-hike/mdx": "^0.9.0",
"@mdx-js/loader": "^3.0.1",
"@mdx-js/react": "^3.0.1",
"@next/mdx": "^14.2.4",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.0",
"@types/mdx": "^2.0.13",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"geist": "^1.3.1",
"lucide-react": "^0.400.0",
"motion": "^11.12.0",
"next": "^14.2.14",
"next-themes": "^0.3.0",
"react": "^18",
"react-dom": "^18",
"react-use-measure": "^2.1.1",
"remark-gfm": "^4.0.0",
"shiki": "^1.10.1",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.0.0",
"@tailwindcss/typography": "^0.5.16",
"@types/node": "^20.17.9",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.4",
"postcss": "^8",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.0.0-insiders.6d3fa07",
"tailwindcss": "^4.0.0",
"tsx": "^4.19.2",
"typescript": "^5"
}
}
assets/25a0515c472578f5dfcfeb765be5383f6893cda1f0f1560f69d3bcabdda71693.svg
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 50 50">
<path d="M 18.5 2 C 13.81752 2 10 5.8175204 10 10.5 C 10 13.800292 11.949927 16.592855 14.708984 18 C 11.949927 19.407145 10 22.199708 10 25.5 C 10 28.800292 11.949927 31.592855 14.708984 33 C 11.949927 34.407145 10 37.199708 10 40.5 C 10 45.18248 13.81752 49 18.5 49 C 23.18248 49 27 45.18248 27 40.5 L 27 33 L 27 29.65625 C 28.341367 32.201717 30.932066 34 34 34 C 38.406436 34 42 30.406432 42 26 C 42 22.378246 39.526173 19.428685 36.214844 18.449219 C 39.553558 17.301707 42 14.220224 42 10.5 C 42 5.8175204 38.18248 2 33.5 2 L 26 2 L 18.5 2 z M 18.5 4 L 25 4 L 25 17 L 18.5 17 C 14.89848 17 12 14.10152 12 10.5 C 12 6.8984796 14.89848 4 18.5 4 z M 27 4 L 33.5 4 C 37.10152 4 40 6.8984796 40 10.5 C 40 14.10152 37.10152 17 33.5 17 L 27 17 L 27 4 z M 18.5 19 L 25 19 L 25 32 L 18.5 32 C 14.89848 32 12 29.10152 12 25.5 C 12 21.89848 14.89848 19 18.5 19 z M 27 19 L 30.34375 19 C 28.908839 19.756146 27.756146 20.908838 27 22.34375 L 27 19 z M 34 20 C 37.325556 20 40 22.674446 40 26 C 40 29.325554 37.325556 32 34 32 C 30.674444 32 28 29.325554 28 26 C 28 22.674446 30.674444 20 34 20 z M 18.5 34 L 25 34 L 25 40.5 C 25 44.10152 22.10152 47 18.5 47 C 14.89848 47 12 44.10152 12 40.5 C 12 36.89848 14.89848 34 18.5 34 z"></path>
</svg>assets/8f4aec13fdd1bf3b6b5566a6c4f5feade1c5e5b492fa81ba32f62f3aa155220e.svg
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg"><mask id="nextjs_icon_dark__mask0_408_139" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="180" height="180"><circle cx="90" cy="90" r="90" fill="black"/></mask><g mask="url(#nextjs_icon_dark__mask0_408_139)"><circle cx="90" cy="90" r="87" fill="black" stroke="white" stroke-width="6"/><path d="M149.508 157.52L69.142 54H54V125.97H66.1136V69.3836L139.999 164.845C143.333 162.614 146.509 160.165 149.508 157.52Z" fill="url(#nextjs_icon_dark__paint0_linear_408_139)"/><rect x="115" y="54" width="12" height="72" fill="url(#nextjs_icon_dark__paint1_linear_408_139)"/></g><defs><linearGradient id="nextjs_icon_dark__paint0_linear_408_139" x1="109" y1="116.5" x2="144.5" y2="160.5" gradientUnits="userSpaceOnUse"><stop stop-color="white"/><stop offset="1" stop-color="white" stop-opacity="0"/></linearGradient><linearGradient id="nextjs_icon_dark__paint1_linear_408_139" x1="121" y1="54" x2="120.799" y2="106.875" gradientUnits="userSpaceOnUse"><stop stop-color="white"/><stop offset="1" stop-color="white" stop-opacity="0"/></linearGradient></defs></svg>assets/5414744d341683260469fbaa9a518e0a4b3000ddd714ce5d0525f8e2cebcb064.svg
<svg viewBox="0 0 109 113" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0627L99.1935 40.0627C107.384 40.0627 111.952 49.5228 106.859 55.9374L63.7076 110.284Z" fill="url(#supabase__paint0_linear)"/><path d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0627L99.1935 40.0627C107.384 40.0627 111.952 49.5228 106.859 55.9374L63.7076 110.284Z" fill="url(#supabase__paint1_linear)" fill-opacity="0.2"/><path d="M45.317 2.07103C48.1765 -1.53037 53.9745 0.442937 54.0434 5.041L54.4849 72.2922H9.83113C1.64038 72.2922 -2.92775 62.8321 2.1655 56.4175L45.317 2.07103Z" fill="#3ECF8E"/><defs><linearGradient id="supabase__paint0_linear" x1="53.9738" y1="54.974" x2="94.1635" y2="71.8295" gradientUnits="userSpaceOnUse"><stop stop-color="#249361"/><stop offset="1" stop-color="#3ECF8E"/></linearGradient><linearGradient id="supabase__paint1_linear" x1="36.1558" y1="30.578" x2="54.4844" y2="65.0806" gradientUnits="userSpaceOnUse"><stop/><stop offset="1" stop-opacity="0"/></linearGradient></defs></svg>Usage.tsx실행 안내·자료
// Local host for the unchanged exact baseline demonstration.
import {LogosSlider as OriginalDemo} from "./baseline-example.tsx";
export default function Demo() { return <><OriginalDemo /></>; }
runtime/author-mount.tsx실행 안내·자료
/** Local sandbox host. Ready is a mount observation, never a verification result. */
import React, { Component, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
declare global {
interface Window {
__STYLEGALLERY_PREVIEW__: { id: string; status: string; errors: string[] };
}
}
export function mount(Demo: React.ComponentType, id: string) {
const state = window.__STYLEGALLERY_PREVIEW__ = { id, status: 'loading', errors: [] as string[] };
const send = (message: object) => parent.postMessage({ ...message, id }, '*');
const report = (error: unknown) => {
const message = error instanceof Error ? error.message : String(error);
if (!state.errors.includes(message)) state.errors.push(message);
state.status = 'error';
document.body.dataset.previewStatus = 'error';
send({ type: 'sg-preview-error', message });
};
window.addEventListener('error', (event) => report(event.error ?? event.message));
window.addEventListener('unhandledrejection', (event) => report(event.reason));
window.addEventListener('securitypolicyviolation', (event) => report(`Blocked by preview policy: ${event.violatedDirective}`));
const theme = new URLSearchParams(location.search).get('theme') === 'dark' ? 'dark' : 'light';
document.documentElement.classList.toggle('dark', theme === 'dark');
document.documentElement.dataset.theme = theme;
// Keep original source bytes while ensuring imported attribution/navigation remains local text.
const removeDestinations = () => document.querySelectorAll('a[href]').forEach((link) => {
link.removeAttribute('href');
link.removeAttribute('target');
});
document.addEventListener('click', (event) => {
if ((event.target as Element)?.closest?.('a')) event.preventDefault();
}, true);
document.addEventListener('submit', (event) => event.preventDefault());
new MutationObserver(removeDestinations).observe(document.getElementById('root')!, { childList: true, subtree: true });
const observe = () => {
const root = document.getElementById('root')!;
const elements = Array.from(root.querySelectorAll('*'));
const rect = root.getBoundingClientRect();
const diagnostics = {
textLength: (root.textContent ?? '').trim().length,
elementCount: elements.length,
visibleElementCount: elements.filter((element) => {
const bounds = element.getBoundingClientRect();
const style = getComputedStyle(element);
return bounds.width > 0 && bounds.height > 0 && style.display !== 'none' && style.visibility !== 'hidden' && Number(style.opacity) !== 0;
}).length,
images: [],
canvases: [],
rootRect: { width: rect.width, height: rect.height },
documentWidth: document.documentElement.scrollWidth,
viewportWidth: innerWidth,
};
send({ type: 'sg-preview-observation', diagnostics });
};
class Boundary extends Component<{ children: React.ReactNode }, { error: string | null }> {
state = { error: null as string | null };
static getDerivedStateFromError(error: Error) { return { error: error.message }; }
componentDidCatch(error: Error) { report(error); }
render() { return this.state.error ? <p role="alert">This preview could not render: {this.state.error}</p> : this.props.children; }
}
function Ready() {
useEffect(() => {
const preference = matchMedia('(prefers-reduced-motion: reduce)');
const syncSVG = () => document.querySelectorAll('svg').forEach((svg) => {
if (preference.matches) svg.pauseAnimations?.();
else svg.unpauseAnimations?.();
});
syncSVG();
preference.addEventListener('change', syncSVG);
let second = 0;
let delayed = 0;
const first = requestAnimationFrame(() => { second = requestAnimationFrame(() => {
if (state.status !== 'error') {
state.status = 'mounted';
document.body.dataset.previewStatus = 'mounted';
send({ type: 'sg-preview-ready' });
observe();
delayed = window.setTimeout(observe, 700);
}
}); });
return () => {
cancelAnimationFrame(first);
cancelAnimationFrame(second);
clearTimeout(delayed);
preference.removeEventListener('change', syncSVG);
};
}, []);
return <Demo />;
}
createRoot(document.getElementById('root')!).render(<Boundary><Ready /></Boundary>);
}
runtime/author-styles.css실행 안내·자료
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--background);
--color-card-foreground: var(--foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--muted);
--color-secondary-foreground: var(--foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--muted);
--color-accent-foreground: var(--foreground);
--color-popover: var(--background);
--color-popover-foreground: var(--foreground);
--color-destructive: #dc2626;
--color-destructive-foreground: #ffffff;
--color-input: var(--border);
--color-border: var(--border);
--color-ring: var(--foreground);
--radius-lg: 0.5rem;
--radius-md: 0.375rem;
--radius-sm: 0.25rem;
}
:root {
--background: #ffffff;
--border: #d4d4d8;
--foreground: #18181b;
--muted: #f4f4f5;
--muted-foreground: #71717a;
--primary: #18181b;
--primary-foreground: #fafafa;
background: var(--background);
color: var(--foreground);
color-scheme: light;
font-family: Arial, sans-serif;
}
.dark {
--background: #09090b;
--border: #3f3f46;
--foreground: #fafafa;
--muted: #27272a;
--muted-foreground: #a1a1aa;
--primary: #fafafa;
--primary-foreground: #18181b;
color-scheme: dark;
}
body {
margin: 0;
min-height: 100vh;
}
#root {
align-items: center;
box-sizing: border-box;
display: flex;
justify-content: center;
min-height: 100vh;
padding: 24px;
width: 100%;
}
#root > * {
max-width: 100%;
}
noscript {
display: block;
padding: 24px;
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-play-state: paused !important;
}
}
runtime/applied-theme.css실행 안내·자료
:root {
--background: hsl(0 0% 100%);
--foreground: hsl(240 10% 3.9%);
--card: hsl(0 0% 100%);
--card-foreground: hsl(240 10% 3.9%);
--popover: hsl(0 0% 100%);
--popover-foreground: hsl(240 10% 3.9%);
--primary: hsl(240 5.9% 10%);
--primary-foreground: hsl(0 0% 98%);
--secondary: hsl(240 4.8% 95.9%);
--secondary-foreground: hsl(240 5.9% 10%);
--muted: hsl(240 4.8% 95.9%);
--muted-foreground: hsl(240 3.8% 46.1%);
--accent: hsl(240 4.8% 95.9%);
--accent-foreground: hsl(240 5.9% 10%);
--destructive: hsl(0 84.2% 60.2%);
--destructive-foreground: hsl(0 0% 98%);
--border: hsl(240 5.9% 90%);
--input: hsl(240 5.9% 90%);
--ring: hsl(240 10% 3.9%);
--chart-1: hsl(12 76% 61%);
--chart-2: hsl(173 58% 39%);
--chart-3: hsl(197 37% 24%);
--chart-4: hsl(43 74% 66%);
--chart-5: hsl(27 87% 67%);
--radius: 0.6rem;
}
.dark {
--background: hsl(240 10% 3.9%);
--foreground: hsl(0 0% 98%);
--card: hsl(240 10% 3.9%);
--card-foreground: hsl(0 0% 98%);
--popover: hsl(240 10% 3.9%);
--popover-foreground: hsl(0 0% 98%);
--primary: hsl(0 0% 98%);
--primary-foreground: hsl(240 5.9% 10%);
--secondary: hsl(240 3.7% 15.9%);
--secondary-foreground: hsl(0 0% 98%);
--muted: hsl(240 3.7% 15.9%);
--muted-foreground: hsl(240 5% 64.9%);
--accent: hsl(240 3.7% 15.9%);
--accent-foreground: hsl(0 0% 98%);
--destructive: hsl(0 62.8% 30.6%);
--destructive-foreground: hsl(0 0% 98%);
--border: hsl(240 3.7% 15.9%);
--input: hsl(240 3.7% 15.9%);
--ring: hsl(240 4.9% 83.9%);
--chart-1: hsl(220 70% 50%);
--chart-2: hsl(160 60% 45%);
--chart-3: hsl(30 80% 55%);
--chart-4: hsl(280 65% 60%);
--chart-5: hsl(340 75% 55%);
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
THIRD-PARTY-LICENSES.txt실행 안내·자료
clsx 2.1.1 — license
MIT License
Copyright (c) Luke Edwards <luke.edwards05@gmail.com> (lukeed.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
========================================================================
tailwind-merge 3.3.1 — LICENSE.md
MIT License
Copyright (c) 2021 Dany Castillo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
========================================================================
motion-v11 11.12.0 — LICENSE.md
The MIT License (MIT)
Copyright (c) 2018 [Framer](https://www.framer.com?utm_source=motion-license) B.V.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
========================================================================
react-v18 18.3.1 — LICENSE
MIT License
Copyright (c) Facebook, Inc. and its affiliates.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
========================================================================
@emotion/memoize 0.9.0 — LICENSE
MIT License
Copyright (c) Emotion team and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
========================================================================
@emotion/is-prop-valid 1.4.0 — LICENSE
MIT License
Copyright (c) Emotion team and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
========================================================================
debounce 1.2.1 — LICENSE
MIT License
Copyright (c) 2012-2018 The Debounce Contributors. See CONTRIBUTORS.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
========================================================================
react-use-measure 2.1.1 — LICENSE
MIT License
Copyright (c) 2019 Paul Henschel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
========================================================================
scheduler 0.23.2 — LICENSE
MIT License
Copyright (c) Facebook, Inc. and its affiliates.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
========================================================================
react-dom-v18 18.3.1 — LICENSE
MIT License
Copyright (c) Facebook, Inc. and its affiliates.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
========================================================================
tailwindcss 4.1.13 — LICENSE
MIT License
Copyright (c) Tailwind Labs, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
runtime/asset-adapter.json실행 안내·자료
{
"operation": "replace exact declared asset URL with its acquired bytes as a data URL only while bundling; original source files unchanged",
"assets": [
{
"source_url": "https://cdn.21st.dev/assets/mirror/25/25a0515c472578f5dfcfeb765be5383f6893cda1f0f1560f69d3bcabdda71693.svg",
"response": "artifacts/full-import/21st-author-sources/responses/asset-90ec011fb104a3691f4e.json.gz",
"http_status": 200,
"sha256": "25a0515c472578f5dfcfeb765be5383f6893cda1f0f1560f69d3bcabdda71693",
"content_type": "image/svg+xml",
"license_basis": "공개 데모 레코드의 MIT 선언; 이미지별 별도 라이선스 문구는 제공되지 않음",
"file": "assets/25a0515c472578f5dfcfeb765be5383f6893cda1f0f1560f69d3bcabdda71693.svg"
},
{
"source_url": "https://cdn.21st.dev/assets/mirror/8f/8f4aec13fdd1bf3b6b5566a6c4f5feade1c5e5b492fa81ba32f62f3aa155220e.svg",
"response": "artifacts/full-import/21st-author-sources/responses/asset-6b9f4f91f17d836248fd.json.gz",
"http_status": 200,
"sha256": "8f4aec13fdd1bf3b6b5566a6c4f5feade1c5e5b492fa81ba32f62f3aa155220e",
"content_type": "image/svg+xml",
"license_basis": "공개 데모 레코드의 MIT 선언; 이미지별 별도 라이선스 문구는 제공되지 않음",
"file": "assets/8f4aec13fdd1bf3b6b5566a6c4f5feade1c5e5b492fa81ba32f62f3aa155220e.svg"
},
{
"source_url": "https://cdn.21st.dev/assets/mirror/54/5414744d341683260469fbaa9a518e0a4b3000ddd714ce5d0525f8e2cebcb064.svg",
"response": "artifacts/full-import/21st-author-sources/responses/asset-63e46837d03b86790da6.json.gz",
"http_status": 200,
"sha256": "5414744d341683260469fbaa9a518e0a4b3000ddd714ce5d0525f8e2cebcb064",
"content_type": "image/svg+xml",
"license_basis": "공개 데모 레코드의 MIT 선언; 이미지별 별도 라이선스 문구는 제공되지 않음",
"file": "assets/5414744d341683260469fbaa9a518e0a4b3000ddd714ce5d0525f8e2cebcb064.svg"
},
{
"source_url": "https://www.shadcnblocks.com/images/block/logos/vercel.svg",
"response": "artifacts/full-import/21st-author-sources/responses/asset-92b0cc93b0fdd0f7895e.json.gz",
"http_status": 403,
"sha256": "0e2a1c7b58c4fac2b853e30a2ee2be5e184e3230ee66c78b65d7c534e2bedb8d",
"content_type": "application/xml",
"license_basis": "공개 데모 레코드의 MIT 선언; 이미지별 별도 라이선스 문구는 제공되지 않음"
}
]
}
runtime/assets/design-engineering-loading-and-feedback.webp실행 안내·자료
큰 소스 파일과 이미지·모델 등의 자료는 파일을 저장해 확인할 수 있어요.
runtime/media-provenance.json실행 안내·자료
{
"assets": [
{
"sourceUrl": "https://www.shadcnblocks.com/images/block/logos/vercel.svg",
"file": "runtime/assets/design-engineering-loading-and-feedback.webp",
"sha256": "2c1dfe0fe9fcccf7ee293d860f73a8c3141a82e7fea4063d2c94fada93c55997",
"mediaType": "image/webp",
"origin": "StyleGallery self-generated illustration, previously requested by the user",
"generationRecord": "research/2026-09-21-research-previews.json",
"reason": "Original Vercel SVG returned HTTP 403; this one preview image is explicitly adapted, not claimed to match the original.",
"originalHttpStatus": 403,
"originalSourceUnchanged": true
}
]
}
runtime/source-adapters.json실행 안내·자료
{
"operation": "literal host-only integration while bundling; acquired source files remain byte-identical",
"replacements": [
{
"file": "baseline-example.tsx",
"from": "description: \"Vercel\"",
"to": "description: \"StyleGallery loading illustration\"",
"reason": "The adapted preview image must not retain the original logo alternative text."
}
]
}
provenance.json실행 안내·자료
{
"id": "21st-018f1b504565",
"sourceRevision": "c78bac5bbc7f1005ba5e6f8e659e9711b38db911",
"demoIdentity": {
"file": "baseline-example.tsx",
"export": "LogosSlider",
"source": "exact-public-demo"
},
"fidelity": {
"preserved": "21st 공개 discovery의 고유 데모 ID와 미리보기 이미지가 가리키는 원래 데모 바이트 전체",
"dependency_revision": "c78bac5bbc7f1005ba5e6f8e659e9711b38db911",
"observed_differences": [
"현재 최신 InfiniteSlider는 speed API로 바뀌어 원래 duration을 무시한다. 같은 작성자의 공개 Git history에서 실제 duration API를 가진 이전 revision을 고정했다.",
"공식 현재 데모의 다른 로고 목록으로 바꾸지 않고 원래 CDN의 Figma/Next.js/Supabase/Vercel 네 개 URL과 className을 보존한다."
],
"mapping": []
},
"acquisitionLimitations": [
"CDN 데모는 SHA256으로, 작성자 원본과 의존 파일은 Git 커밋 및 blob SHA로 고정했다.",
"현재 작성자 revision의 의존 파일이 과거 21st 내부 구현과 바이트 단위로 같다는 주장은 하지 않는다.",
"다운로드한 코드를 실행하지 않았으며 브라우저·상호작용 검증은 별도 단계다."
],
"files": [
{
"file": "baseline-example.tsx",
"kind": "implementation",
"sha256": "6528f92d0fbb88b68bffef1660998869971fd8b844595ebf9b440f71a47e3307",
"sourceRevision": "sha256:6528f92d0fbb88b68bffef1660998869971fd8b844595ebf9b440f71a47e3307",
"license": "MIT"
},
{
"file": "LICENSE",
"kind": "license",
"sha256": "f668f5ef3635eb906f10b1eea9a32e449eb6e1a183ab6879ef6d56c0980dd2f3",
"sourceRevision": "c78bac5bbc7f1005ba5e6f8e659e9711b38db911",
"license": "MIT"
},
{
"file": "upstream/app/docs/progressive-blur/progressive-blur-slider.tsx",
"kind": "upstream-implementation",
"sha256": "d19a01e3132b1717a612dfed492582d4fcdb14e66d0d46a85caf718eba1c1973",
"sourceRevision": "c78bac5bbc7f1005ba5e6f8e659e9711b38db911",
"license": "MIT"
},
{
"file": "author/components/core/infinite-slider.tsx",
"kind": "implementation-dependency",
"sha256": "754a459205e3cfccdd95be83b366b1cbd4dad8aa1529999116ef962357f3563b",
"sourceRevision": "c78bac5bbc7f1005ba5e6f8e659e9711b38db911",
"license": "MIT"
},
{
"file": "author/components/core/progressive-blur.tsx",
"kind": "implementation-dependency",
"sha256": "6b20da98cfa4db4e2e57bd649212f13d42fe7046ba727db2af27316dbc820502",
"sourceRevision": "c78bac5bbc7f1005ba5e6f8e659e9711b38db911",
"license": "MIT"
},
{
"file": "author/lib/utils.ts",
"kind": "implementation-dependency",
"sha256": "9304a861c8673bee09e0f12de31773abbde503b02e59dfd74763ddec2e37cf05",
"sourceRevision": "c78bac5bbc7f1005ba5e6f8e659e9711b38db911",
"license": "MIT"
},
{
"file": "author/app/globals.css",
"kind": "theme-source",
"sha256": "f950f824931eba81d32b879d1cb38a8a093a5c74d3775bdd3de39a46c36d3fe5",
"sourceRevision": "c78bac5bbc7f1005ba5e6f8e659e9711b38db911",
"license": "MIT"
},
{
"file": "author/package.json",
"kind": "dependency-manifest",
"sha256": "f931b8f5eeaa793a817f4477041c6339e4964fd6247c30c84ce06315530dff29",
"sourceRevision": "c78bac5bbc7f1005ba5e6f8e659e9711b38db911",
"license": "MIT"
},
{
"file": "assets/25a0515c472578f5dfcfeb765be5383f6893cda1f0f1560f69d3bcabdda71693.svg",
"kind": "declared-demo-asset",
"sha256": "25a0515c472578f5dfcfeb765be5383f6893cda1f0f1560f69d3bcabdda71693",
"sourceRevision": "sha256:25a0515c472578f5dfcfeb765be5383f6893cda1f0f1560f69d3bcabdda71693",
"license": "publisher-declaration-only; asset-specific-license-unspecified"
},
{
"file": "assets/8f4aec13fdd1bf3b6b5566a6c4f5feade1c5e5b492fa81ba32f62f3aa155220e.svg",
"kind": "declared-demo-asset",
"sha256": "8f4aec13fdd1bf3b6b5566a6c4f5feade1c5e5b492fa81ba32f62f3aa155220e",
"sourceRevision": "sha256:8f4aec13fdd1bf3b6b5566a6c4f5feade1c5e5b492fa81ba32f62f3aa155220e",
"license": "publisher-declaration-only; asset-specific-license-unspecified"
},
{
"file": "assets/5414744d341683260469fbaa9a518e0a4b3000ddd714ce5d0525f8e2cebcb064.svg",
"kind": "declared-demo-asset",
"sha256": "5414744d341683260469fbaa9a518e0a4b3000ddd714ce5d0525f8e2cebcb064",
"sourceRevision": "sha256:5414744d341683260469fbaa9a518e0a4b3000ddd714ce5d0525f8e2cebcb064",
"license": "publisher-declaration-only; asset-specific-license-unspecified"
}
],
"importMap": {
"@/components/ui/infinite-slider": "author/components/core/infinite-slider.tsx",
"@/components/ui/progressive-blur": "author/components/core/progressive-blur.tsx",
"@/lib/utils": "author/lib/utils.ts"
},
"declaredDependencies": {
"clsx": "^2.1.1",
"motion": "^11.12.0",
"react": "^18",
"react-use-measure": "^2.1.1",
"tailwind-merge": "^2.6.0"
},
"runtimeDependencies": {
"clsx": "2.1.1",
"tailwind-merge": "3.3.1",
"motion-v11": "11.12.0",
"react-v18": "18.3.1",
"@emotion/memoize": "0.9.0",
"@emotion/is-prop-valid": "1.4.0",
"debounce": "1.2.1",
"react-use-measure": "2.1.1",
"scheduler": "0.23.2",
"react-dom-v18": "18.3.1",
"tailwindcss": "4.1.13"
},
"packageAliases": {
"motion": "motion-v11",
"react": "react-v18",
"react-dom": "react-dom-v18"
},
"assets": [
{
"source_url": "https://cdn.21st.dev/assets/mirror/25/25a0515c472578f5dfcfeb765be5383f6893cda1f0f1560f69d3bcabdda71693.svg",
"response": "artifacts/full-import/21st-author-sources/responses/asset-90ec011fb104a3691f4e.json.gz",
"http_status": 200,
"sha256": "25a0515c472578f5dfcfeb765be5383f6893cda1f0f1560f69d3bcabdda71693",
"content_type": "image/svg+xml",
"license_basis": "공개 데모 레코드의 MIT 선언; 이미지별 별도 라이선스 문구는 제공되지 않음",
"file": "assets/25a0515c472578f5dfcfeb765be5383f6893cda1f0f1560f69d3bcabdda71693.svg"
},
{
"source_url": "https://cdn.21st.dev/assets/mirror/8f/8f4aec13fdd1bf3b6b5566a6c4f5feade1c5e5b492fa81ba32f62f3aa155220e.svg",
"response": "artifacts/full-import/21st-author-sources/responses/asset-6b9f4f91f17d836248fd.json.gz",
"http_status": 200,
"sha256": "8f4aec13fdd1bf3b6b5566a6c4f5feade1c5e5b492fa81ba32f62f3aa155220e",
"content_type": "image/svg+xml",
"license_basis": "공개 데모 레코드의 MIT 선언; 이미지별 별도 라이선스 문구는 제공되지 않음",
"file": "assets/8f4aec13fdd1bf3b6b5566a6c4f5feade1c5e5b492fa81ba32f62f3aa155220e.svg"
},
{
"source_url": "https://cdn.21st.dev/assets/mirror/54/5414744d341683260469fbaa9a518e0a4b3000ddd714ce5d0525f8e2cebcb064.svg",
"response": "artifacts/full-import/21st-author-sources/responses/asset-63e46837d03b86790da6.json.gz",
"http_status": 200,
"sha256": "5414744d341683260469fbaa9a518e0a4b3000ddd714ce5d0525f8e2cebcb064",
"content_type": "image/svg+xml",
"license_basis": "공개 데모 레코드의 MIT 선언; 이미지별 별도 라이선스 문구는 제공되지 않음",
"file": "assets/5414744d341683260469fbaa9a518e0a4b3000ddd714ce5d0525f8e2cebcb064.svg"
},
{
"source_url": "https://www.shadcnblocks.com/images/block/logos/vercel.svg",
"response": "artifacts/full-import/21st-author-sources/responses/asset-92b0cc93b0fdd0f7895e.json.gz",
"http_status": 403,
"sha256": "0e2a1c7b58c4fac2b853e30a2ee2be5e184e3230ee66c78b65d7c534e2bedb8d",
"content_type": "application/xml",
"license_basis": "공개 데모 레코드의 MIT 선언; 이미지별 별도 라이선스 문구는 제공되지 않음"
}
],
"assetAdaptations": [
{
"sourceUrl": "https://www.shadcnblocks.com/images/block/logos/vercel.svg",
"file": "runtime/assets/design-engineering-loading-and-feedback.webp",
"sha256": "2c1dfe0fe9fcccf7ee293d860f73a8c3141a82e7fea4063d2c94fada93c55997",
"mediaType": "image/webp",
"origin": "StyleGallery self-generated illustration, previously requested by the user",
"generationRecord": "research/2026-09-21-research-previews.json",
"reason": "Original Vercel SVG returned HTTP 403; this one preview image is explicitly adapted, not claimed to match the original.",
"originalHttpStatus": 403,
"originalSourceUnchanged": true
}
],
"adaptations": [
"The exact baseline demo and the author revision that supports duration={30} are retained. Newer speed-based source is not substituted.",
"Figma, Next.js, and Supabase asset bytes are preserved. Only the unavailable Vercel SVG slot uses the existing StyleGallery loading illustration in the bundled preview, with corresponding alternative text. Original source files remain byte-identical.",
"Source image declaration and the separately generated host image are distinguished in media provenance. Both are embedded locally; no network request is needed."
],
"runtime_verified": false
}
README.md실행 안내·자료
# Progressive Blur Slider · Motion Primitives
The acquired original files are unchanged. Source revision: c78bac5bbc7f1005ba5e6f8e659e9711b38db911. Each exact file hash and any demo content revision is recorded in provenance.json. Keep all included license notices.
- The exact baseline demo and the author revision that supports duration={30} are retained. Newer speed-based source is not substituted.
- Figma, Next.js, and Supabase asset bytes are preserved. Only the unavailable Vercel SVG slot uses the existing StyleGallery loading illustration in the bundled preview, with corresponding alternative text. Original source files remain byte-identical.
- Source image declaration and the separately generated host image are distinguished in media provenance. Both are embedded locally; no network request is needed.
- Host spacing and fallback tokens come from runtime/author-styles.css. runtime/applied-theme.css contains the selected original author theme and overrides fallback tokens.
Open the standalone HTML to run with all JavaScript and CSS bundled locally. ?theme=light and ?theme=dark select the host theme. The parent gallery must use an opaque allow-scripts sandbox. No external requests are required.
For source reuse, start with Usage.tsx and the unchanged baseline demonstration. Resolve @/ imports to their included matching author files and install the exact package versions in provenance.json. The host mount and styles are supplied for reference.
Build success and the ready message do not prove runtime or accessibility behavior. Browser validation is recorded separately.
BEHIND THE EXAMPLE
이 장면을 만드는 원리
같은 콘텐츠 두 벌로 연결하는 반복 이동
반복 이동의 거리·시간·중단 소유자를 구분하고 움직임 감소에서도 같은 정보를 제공해야 합니다.
- 이 예제에서는
- InfiniteSlider는 children을 두 번 렌더링하고 측정한 전체 길이에 gap을 더한 값의 절반만큼 이동합니다. 원래 데모의 gap은 48px, duration은 30초입니다. durationOnHover를 넘기지 않아 호버 감속은 활성화되지 않으며 원본에 움직임 감소 분기도 없습니다. 호스트의 CSS 정지만으로 이 JavaScript 이동이 멈췄다고 볼 수 없습니다.
코드와 함께 확인하기
코드에서 찾기
const contentSize = size + gap;infinite-slider.tsx두 벌의 측정 길이와 연결 간격을 이동 거리 계산에 사용합니다.
repeat: Infinityinfinite-slider.tsxMotion 값이 무한 반복으로 이동합니다.
duration={30}baseline-example.tsx과거 duration API에 맞는 공식 원본을 연결했습니다.
직접 해보기
반복 경계를 지나도록 재생하고 부모 폭을 변경합니다.
살펴볼 변화이미지 사이 간격과 반복 경계의 점프를 측정합니다. 리사이즈 뒤 측정값에 맞춰 재시작하는지 확인해야 합니다.
재생 중 움직임 감소를 켜고 키보드만으로 같은 이미지 정보를 확인합니다.
살펴볼 변화원본의 자동 이동을 멈추거나 정적인 전체 목록으로 제공할 제품 경로가 필요합니다. CSS 정지 규칙만으로 JavaScript 이동까지 검증했다고 보고하지 않습니다.
양쪽 200px 안에 겹치는 흐림 레이어
효과의 영역과 콘텐츠 가시성을 실제 부모 크기에서 함께 확인합니다.
- 이 예제에서는
- 각 ProgressiveBlur는 기본 8개 레이어를 만들고 index × blurIntensity만큼 backdrop-filter를 적용합니다. 이 데모는 강도 1이라 0~7px을 사용하며 왼쪽 270도·오른쪽 90도 마스크를 200px씩 배치합니다. 부모 폭이 400px보다 작으면 두 효과 영역이 겹칠 수 있습니다. 레이어는 pointer-events-none이고 이미지를 직접 수정하지 않습니다.
코드와 함께 확인하기
코드에서 찾기
blurLayers = 8progressive-blur.tsx레이어 기본 개수입니다.
backdropFilter: `blur(${index * blurIntensity}px)`progressive-blur.tsx뒤에 그려진 콘텐츠에 레이어별 흐림을 적용합니다.
h-full w-[200px]baseline-example.tsx양쪽 효과의 폭은 고정 길이입니다.
직접 해보기
640px과 320px 부모에서 이미지가 중앙을 통과하는 모습을 비교합니다.
살펴볼 변화좁은 부모에서 효과가 겹쳐 주요 이미지가 읽히지 않는지 확인합니다. backdrop-filter를 지원하지 않을 때도 콘텐츠가 사라지지 않아야 합니다.
원본 소스와 교체된 한 이미지의 구분
자료의 입력 자산과 로컬 데모 지원 코드를 구분하고 동일성의 범위를 공개합니다.
- 이 예제에서는
- 원래 배열은 Figma·Next.js·Supabase·Vercel 네 이미지입니다. 앞의 세 SVG는 확보한 바이트를 사용하고, 공개 서버가 403을 반환한 Vercel 한 자리에는 자체 제작 로딩 일러스트를 사용합니다. 번들에만 새 이미지와 대체 텍스트를 적용하며 다운로드 원본 배열과 컴포넌트는 수정하지 않았습니다.
코드와 함께 확인하기
코드에서 찾기
description: "Vercel"baseline-example.tsx다운로드하는 원본 배열의 네 번째 이미지 의미입니다.
originalSourceUnchangedmedia-provenance.json자체 생성 이미지의 근거와 원본 미취득 사실을 구분해 기록합니다.
직접 해보기
네트워크를 끊은 상태에서 단독 HTML을 열고 이미지 네 자리와 대체 텍스트를 확인합니다.
살펴볼 변화세 원본 로고와 한 자체 제작 일러스트가 표시되어야 합니다. 교체 이미지를 Vercel 원본이나 동일한 제작 자산으로 소개하지 않습니다.
