21st.dev 원본

Progressive Blur Slider · Motion Primitives

데이터 · MIT

데이터 더 보기
ORIGINAL PREVIEW
Progressive Blur Slider · Motion Primitives 정적 미리보기

갤러리 안에서는 화면을 벗어나거나 움직임을 멈추면 예제도 닫힙니다. 다시 재생할 때는 처음부터 시작해요.

큰 화면으로 보기 새 탭실행 HTML 저장

저장한 HTML에서는 갤러리의 재생 설정이 적용되지 않아요.

SOURCE FILES

원본 코드 읽기

22개 파일

수집한 원본 소스와 실행 안내를 함께 제공합니다. 실행 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.