Codrops 원본
Players Club: A Free Astro Template for Showcasing Music Artists
섹션 · MIT
갤러리 안에서는 화면을 벗어나거나 움직임을 멈추면 예제도 닫힙니다. 다시 재생할 때는 처음부터 시작해요.
SOURCE FILES
원본 코드 읽기
수집한 원본 소스와 실행 안내를 함께 제공합니다.
astro.config.mjs
// @ts-check
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
// https://astro.build/config
export default defineConfig({
devToolbar: {
enabled: false,
},
prefetch: true,
site: 'https://playersclub.crnacura.workers.dev/',
integrations: [sitemap()],
experimental: {
svg: true,
},
});
함께 쓰는 파일 39개 보기
src/content.config.ts
import { defineCollection, reference, z } from 'astro:content';
import { glob } from 'astro/loaders';
const artists = defineCollection({
loader: glob({ pattern: '**/*.md', base: "./src/data/artists" }),
schema: z.object({
name: z.string(),
stage_name: z.string(),
genre: z.string(),
image: z.object({
src: z.string(),
alt: z.string(),
}),
}),
});
const albums = defineCollection({
loader: glob({ pattern: '**/*.md', base: "./src/data/albums" }),
schema: z.object({
name: z.string(),
image: z.object({
src: z.string(),
alt: z.string(),
}),
publishDate: z.date(), // e.g. 2024-09-17
tracks: z.array(z.string()),
artist: reference('artists'),
}),
});
// Export all collections
export const collections = {artists, albums};src/pages/robots.txt.ts
import type { APIRoute } from 'astro';
const getRobotsTxt = (sitemapURL: URL) => `
User-agent: *
Allow: /
Sitemap: ${sitemapURL.href}
`;
export const GET: APIRoute = ({ site }) => {
const sitemapURL = new URL('sitemap-index.xml', site);
return new Response(getRobotsTxt(sitemapURL));
};src/scripts/gridActions.js
// Initialize variables to store DOM elements and states
let gridContainer;
let gridItems;
let shuffleButton;
let sortButton;
let searchButton;
let searchClearButton;
let searchContent;
let searchContentOriginal;
let searchDialog;
let searchInput;
let closeDialog;
/* Event handler functions */
// Shuffle grid: trigger grid shuffling.
const handleShuffleClick = () => shuffleGrid();
// Sort grid: trigger grid sorting.
const handleSortClick = () => sortGrid();
// Open search dialog: show the dialog and blur the page.
const handleSearchClick = () => {
searchDialog.showModal();
toggleDialogPageBlur(true);
};
// Close search dialog: hide the dialog and remove the blur.
const handleCloseClick = () => {
searchDialog.close();
toggleDialogPageBlur(false);
};
// Clear search: reset the filter and clear the input.
const handleSearchClearClick = () => {
filterGrid('');
toggleClearButton();
searchContent.innerHTML = searchContentOriginal;
searchInput.value = '';
searchButton.classList.remove('search--active');
};
// Filter grid: update grid items based on the search input.
const handleSearchInput = (e) => {
const searchTerm = e.target.value;
filterGrid(searchTerm);
searchContent.innerHTML = searchTerm === '' ? searchContentOriginal : searchTerm;
toggleClearButton(searchTerm);
searchButton.classList.toggle('search--active', searchTerm !== '');
};
/* Initialize DOM elements and states */
const initializeVariables = () => {
gridContainer = document.querySelector('[data-grid]');
gridItems = Array.from(gridContainer?.children || []);
shuffleButton = document.querySelector('[data-shuffle]');
sortButton = document.querySelector('[data-sort]');
searchButton = document.querySelector('[data-search]');
searchClearButton = document.querySelector('[data-clear]');
searchContent = searchButton?.querySelector('.oh__inner');
searchContentOriginal = searchContent?.innerHTML || '';
searchDialog = document.getElementById('search-dialog');
searchInput = document.getElementById('search-input');
closeDialog = document.getElementById('close-dialog');
};
/* Shuffle grid items randomly and update the container */
const shuffleGrid = () => {
const shuffledItems = gridItems.sort(() => Math.random() - 0.5);
if (gridContainer) {
gridContainer.innerHTML = '';
shuffledItems.forEach((item) => gridContainer.appendChild(item));
}
};
/* Sort grid items alphabetically by 'data-stagename' */
const sortGrid = () => {
const sortedItems = gridItems.sort((a, b) => {
const nameA = a.getAttribute('data-stagename').toLowerCase();
const nameB = b.getAttribute('data-stagename').toLowerCase();
return nameA.localeCompare(nameB);
});
if (gridContainer) {
gridContainer.innerHTML = '';
sortedItems.forEach((item) => gridContainer.appendChild(item));
}
};
/* Filter grid items based on the search input */
const filterGrid = (searchValue) => {
const lowerCaseSearch = searchValue.toLowerCase();
gridItems.forEach((item) => {
const name = item.getAttribute('data-name').toLowerCase();
const stagename = item.getAttribute('data-stagename').toLowerCase();
item.style.display =
name.includes(lowerCaseSearch) || stagename.includes(lowerCaseSearch)
? ''
: 'none';
});
};
/* Toggle page blur when the search dialog is open or closed */
const toggleDialogPageBlur = (toggle) => {
if (toggle) {
document.body.classList.add('blurred');
} else {
document.body.classList.remove('blurred');
}
};
/* Show or hide the clear button based on search input */
const toggleClearButton = (searchTerm = '') => {
const isHidden = searchClearButton?.classList.contains('hidden');
if (searchTerm === '' && !isHidden) {
searchClearButton.classList.add('hidden');
} else if (searchTerm !== '' && isHidden) {
searchClearButton.classList.remove('hidden');
}
};
/* Initialize event listeners and states */
const init = () => {
initializeVariables();
shuffleButton?.addEventListener('click', handleShuffleClick);
sortButton?.addEventListener('click', handleSortClick);
searchButton?.addEventListener('click', handleSearchClick);
closeDialog?.addEventListener('click', handleCloseClick);
searchClearButton?.addEventListener('click', handleSearchClearClick);
searchInput?.addEventListener('input', handleSearchInput);
searchDialog?.addEventListener('close', () => toggleDialogPageBlur(false));
};
/* Cleanup event listeners and reset variables */
const cleanup = () => {
shuffleButton?.removeEventListener('click', handleShuffleClick);
sortButton?.removeEventListener('click', handleSortClick);
searchButton?.removeEventListener('click', handleSearchClick);
closeDialog?.removeEventListener('click', handleCloseClick);
searchClearButton?.removeEventListener('click', handleSearchClearClick);
searchInput?.removeEventListener('input', handleSearchInput);
gridContainer = null;
gridItems = [];
shuffleButton = null;
sortButton = null;
searchButton = null;
searchClearButton = null;
searchContent = null;
searchContentOriginal = '';
searchDialog = null;
searchInput = null;
closeDialog = null;
};
/* Handle Astro page events on the home page */
const handlePageEvent = (type) => {
const page = document.documentElement.getAttribute('data-page');
if (page !== 'home') return;
if (type === 'load') {
init();
} else if (type === 'before-swap') {
cleanup();
}
};
// Listen for Astro's lifecycle events
document.addEventListener('astro:page-load', () => handlePageEvent('load'));
document.addEventListener('astro:before-swap', () => handlePageEvent('before-swap'));
src/scripts/imageFade.js
const init = () => {
// Retrieve all images with the 'fade-in' class.
const images = document.querySelectorAll('img.fade-in');
// Set up load handlers to add a 'loaded' class when images finish loading.
images.forEach((img) => {
img.onload = function () {
img.classList.add('loaded'); // Mark the image as loaded.
};
// If the image is already cached, trigger the load handler immediately.
if (img.complete) {
img.onload();
}
});
};
// Initialize fade-in effects on Astro page load.
document.addEventListener('astro:page-load', init);src/scripts/index.js
import gsap from 'gsap';
// DOM elements and animation-related variables
let lines;
let textSliders;
let gridContainer;
let gridItems;
let hasPreloaderComponent;
let animationTimeline; // GSAP timeline instance
// Initialize DOM elements used in the animations.
const initializeVariables = () => {
lines = document.querySelectorAll('hr');
textSliders = document.querySelectorAll('header .oh > .oh__inner');
gridContainer = document.querySelector('[data-grid]');
gridItems = gridContainer ? Array.from(gridContainer.children) : [];
hasPreloaderComponent = document.querySelector('.loading');
};
// Animate the homepage elements using a GSAP timeline.
const animateHomepageElements = () => {
if (!gridContainer || !gridItems.length) return;
// Hide the grid container before starting the animation.
animationTimeline = gsap.set(gridContainer, { autoAlpha: 0 });
gsap.timeline({
defaults: {
duration: 1.4,
ease: 'power4',
},
onComplete: () => {
// Dispatch a custom event after all animations complete.
const event = new CustomEvent('gridRendered');
document.dispatchEvent(event);
},
})
.fromTo(
lines,
{ transformOrigin: '0% 50%', scaleX: 0 },
{ duration: 1.6, ease: 'power2', stagger: 0.9, scaleX: 1 }
)
.from(textSliders, { yPercent: 100, stagger: 0.1 }, 0.2)
.set(gridContainer, { autoAlpha: 1 }, '<+=1')
.from(gridItems, { yPercent: 100, stagger: 0.08 }, '<')
.from(gridItems, { ease: 'sine', autoAlpha: 0, stagger: 0.08 }, '<');
};
// Clean up animations and DOM references to prevent memory leaks.
const cleanup = () => {
if (animationTimeline) {
animationTimeline.kill(); // Stop the timeline
animationTimeline = null;
}
lines = null;
textSliders = null;
gridContainer = null;
gridItems = null;
hasPreloaderComponent = null;
};
// Initialize the page: set variables, manage scroll behavior, and trigger animations.
const init = () => {
initializeVariables();
// Disable scroll restoration on browser back navigation.
if ('scrollRestoration' in history) {
history.scrollRestoration = 'manual';
}
// Scroll to the top of the page.
window.scrollTo(0, 0);
// Wait for assets to load if a preloader is present.
if (hasPreloaderComponent && sessionStorage.getItem('preloadComplete') !== 'true') {
document.addEventListener('assetsLoaded', animateHomepageElements, { once: true });
} else {
animateHomepageElements();
}
};
// Run a callback only if the current page is the home page.
const handlePageEvent = (event, callback) => {
const page = document.documentElement.getAttribute('data-page');
if (page === 'home') callback();
};
// Astro lifecycle hook: initialize animations on page load.
document.addEventListener('astro:page-load', () => {
handlePageEvent('page-load', init);
});
// Astro lifecycle hook: clean up before swapping pages.
document.addEventListener('astro:before-swap', () => {
handlePageEvent('before-swap', cleanup);
});src/scripts/preloader.js
import imagesLoaded from 'imagesloaded';
// Preloader element reference
let loading;
// Initialize the preloader element.
const initializeElements = () => {
loading = document.querySelector('.loading');
};
// Load all images and background images on the page.
// Resolves when all assets are loaded, or rejects if any fail.
const loadImages = () => {
return new Promise((resolve, reject) => {
// Collect all <img> elements.
const imgElements = document.querySelectorAll('img');
// Collect elements with background images.
const bgElements = [...document.querySelectorAll('*')].filter((el) => {
const style = window.getComputedStyle(el);
return style.backgroundImage !== 'none';
});
// Combine both sets of elements.
const allElements = [...imgElements, ...bgElements];
// Use imagesLoaded to track asset loading.
const imgLoad = imagesLoaded(allElements, { background: true });
imgLoad.on('done', resolve);
imgLoad.on('fail', () => {
reject(new Error('Failed to load some images or assets'));
});
});
};
// Load assets and dispatch a custom event when done.
const loadAssets = async () => {
try {
await loadImages();
const event = new CustomEvent('assetsLoaded');
document.dispatchEvent(event);
} catch (error) {
console.error('Failed to load assets:', error);
throw error;
}
};
// Show the preloader, load assets if needed, and then hide the preloader.
const toggleLoading = async () => {
if (sessionStorage.getItem('preloadComplete') === 'true') {
hide();
return;
}
show();
try {
await loadAssets();
sessionStorage.setItem('preloadComplete', 'true');
hide();
} catch (error) {
console.error('Failed to load assets or animate:', error);
}
};
// Display the preloader.
const show = () => {
loading.classList.remove('hidden');
};
// Hide the preloader.
const hide = () => {
loading.classList.add('hidden');
};
// Cleanup to reset references.
const cleanup = () => {
loading = null;
};
// Initialize the preloader logic.
const init = () => {
initializeElements();
toggleLoading();
};
// Execute a callback only if the current page is the home page.
const handlePageEvent = (event, callback) => {
const page = document.documentElement.getAttribute('data-page');
if (page === 'home') callback();
};
// Listen for Astro's lifecycle events.
document.addEventListener('astro:page-load', () => {
handlePageEvent('page-load', init);
});
document.addEventListener('astro:before-swap', () => {
handlePageEvent('before-swap', cleanup);
});
// Clear the preload flag before page unload to ensure the loader appears on refresh.
window.addEventListener('beforeunload', () => {
sessionStorage.removeItem('preloadComplete');
});
src/scripts/smoothscroll.js
import Lenis from 'lenis';
import { gsap } from "gsap";
// Initializes smooth scrolling with Lenis.
// Function to set up smooth scrolling.
export const initSmoothScrolling = () => {
// Initialize Lenis for smooth scroll effects. Lerp value controls the smoothness.
const lenis = new Lenis({ lerp: 0.15 });
// Ensure GSAP animations are in sync with Lenis' scroll frame updates.
gsap.ticker.add(time => {
lenis.raf(time * 1000); // Convert GSAP's time to milliseconds for Lenis.
});
// Turn off GSAP's default lag smoothing to avoid conflicts with Lenis.
gsap.ticker.lagSmoothing(0);
};
src/scripts/tooltip.js
import gsap from 'gsap';
class Tooltip {
constructor(gridEl) {
this.grid = gridEl;
this.artists = this.grid.children;
if (this.artists.length === 0) return;
this.tooltip = document.querySelector('.tooltip');
this.arrow = this.tooltip.querySelector('.tooltip__row--ra svg');
this.OFFSET_X = 20; // Distance from cursor to left edge of tooltip
this.OFFSET_Y = 0; // Distance from cursor to top edge of tooltip
this.animationConfig = {
// Configuration for the text animations (e.g., rows sliding in/out)
texts: {
duration: 0.7,
ease: 'expo',
},
// Configuration for the tooltip's scaling animations
tooltip: {
duration: 0.6,
ease: 'power4.inOut',
},
// Delay before starting text animations when showing the tooltip
textsDelay: 0.4, // Delay in seconds before the text animations start
// Overlap delay for hiding the tooltip and text animations
hideDelay: '-=0.7', // Overlaps the tooltip's scale-down with text sliding animations
};
// Animation directions for the rows
this.rowAnimationDirections = {
stagename: { in: { yPercent: -100 }, out: { yPercent: -100 } }, // In and out to/from the top
name: { in: { yPercent: 100 }, out: { yPercent: 100 } }, // In and out to/from the bottom
genre: { in: { yPercent: 100 }, out: { yPercent: 100 } }, // In and out to/from the bottom
arrow: { in: { yPercent: -100 }, out: { yPercent: -100 } }, // In and out to/from the top
};
this.hoverTarget = null; // Tracks the currently hovered `.artist`
this.isTooltipVisible = false; // Tracks tooltip visibility
this.scaleDownTimeout; // Tracks the scale-down timeout
this.scaleDownTimeline; // Stores the tooltip scale-down timeline
this.mouseLeaveTimeout; // Timeout for mouseleave handling
this.rowTimelines = {}; // Stores timelines for each row
this.arrowTimeline = null; // Stores the arrow animation timeline
this.windowWidth = window.innerWidth; // Cache window width
// Define smooth animations for moving the tooltip
// xTo and yTo control the tooltip's horizontal (x) and vertical (y) positions
// Using GSAP's quickTo for better performance
this.xTo = gsap.quickTo(this.tooltip, 'x', { duration: 0.6, ease: 'expo' });
this.yTo = gsap.quickTo(this.tooltip, 'y', { duration: 0.6, ease: 'expo' });
// Initialize row active states
this.tooltip.querySelectorAll('.tooltip__row').forEach(row => row.dataset.active = '0');
this.initializeEvents();
}
initializeEvents() {
this.grid.addEventListener('mousemove', this.handleMouseMove);
window.addEventListener('resize', this.handleResize);
[...this.artists].forEach(artist => {
artist.addEventListener('mouseenter', this.handleMouseEnter);
artist.addEventListener('mouseleave', this.handleMouseLeave);
});
}
handleMouseMove = (e) => {
if (!this.hoverTarget) return;
const tooltipWidth = this.tooltip.offsetWidth;
let tooltipX;
const tooltipY = e.clientY + this.OFFSET_Y + window.scrollY;
if (e.clientX + this.OFFSET_X + tooltipWidth > this.windowWidth) {
tooltipX = e.clientX - this.OFFSET_X - tooltipWidth + window.scrollX;
} else {
tooltipX = e.clientX + this.OFFSET_X + window.scrollX;
}
if (!this.isTooltipVisible) {
if (this.scaleDownTimeline) this.scaleDownTimeline.kill();
clearTimeout(this.scaleDownTimeout);
gsap.set(this.tooltip, { x: tooltipX, y: tooltipY });
gsap.fromTo(
this.tooltip,
{ scale: 0, opacity: 1, transformOrigin: '0% 100%' },
{ ...this.animationConfig.tooltip, scale: 1 }
);
this.isTooltipVisible = true;
} else {
this.xTo(tooltipX);
this.yTo(tooltipY);
}
clearTimeout(this.scaleDownTimeout);
this.scaleDownTimeout = setTimeout(() => {
if (!this.hoverTarget) {
this.scaleDownTimeline = gsap.timeline();
this.updateTooltip({ stagename: '', name: '', genre: '' }, this.scaleDownTimeline, 'out');
this.scaleDownTimeline.to(
this.tooltip,
{ ...this.animationConfig.tooltip, scale: 0 },
this.animationConfig.hideDelay
);
this.isTooltipVisible = false;
}
}, 50);
};
handleMouseEnter = (e) => {
clearTimeout(this.mouseLeaveTimeout);
this.hoverTarget = e.currentTarget;
if (this.scaleDownTimeline) this.scaleDownTimeline.kill();
clearTimeout(this.scaleDownTimeout);
const stageName = this.hoverTarget.dataset.stagename;
const name = this.hoverTarget.dataset.name;
const genre = this.hoverTarget.dataset.genre;
const updateTimeline = gsap.timeline();
this.updateTooltip({ stagename: stageName, name, genre }, updateTimeline, this.isTooltipVisible ? 'none' : 'in');
};
handleMouseLeave = () => {
this.hoverTarget = null;
this.mouseLeaveTimeout = setTimeout(() => {
if (!this.hoverTarget && this.isTooltipVisible) {
gsap.set(this.tooltip, { scale: 0, opacity: 0 });
this.isTooltipVisible = false;
}
}, 50);
};
handleResize = () => {
this.windowWidth = window.innerWidth;
};
initializeEvents() {
this.grid.addEventListener('mousemove', this.handleMouseMove);
window.addEventListener('resize', this.handleResize);
[...this.artists].forEach(artist => {
artist.addEventListener('mouseenter', this.handleMouseEnter);
artist.addEventListener('mouseleave', this.handleMouseLeave);
});
}
destroy() {
if (this.arrowTimeline) this.arrowTimeline.kill();
if (this.scaleDownTimeline) this.scaleDownTimeline.kill();
Object.values(this.rowTimelines).forEach(timeline => timeline && timeline.kill());
clearTimeout(this.scaleDownTimeout);
clearTimeout(this.mouseLeaveTimeout);
this.grid.removeEventListener('mousemove', this.handleMouseMove);
window.removeEventListener('resize', this.handleResize);
[...this.artists].forEach(artist => {
artist.removeEventListener('mouseenter', this.handleMouseEnter);
artist.removeEventListener('mouseleave', this.handleMouseLeave);
});
}
// Function to update all rows dynamically
updateTooltip(values, timeline, direction) {
Object.entries(values).forEach(([field, newValue]) => {
const rowSelector = `[data-field="${field}"]`;
this.updateTextSlider(rowSelector, newValue, timeline, direction);
});
// Animate the arrow only when tooltip appears/disappears
if ((direction === 'in' && !this.isTooltipVisible) || (direction === 'out' && this.isTooltipVisible)) {
this.animateArrow(timeline, direction);
}
}
// Function to update a single row with sliding animation and add to a timeline
updateTextSlider(rowSelector, newValue, timeline, direction) {
const row = this.tooltip.querySelector(rowSelector);
const textSliders = row.querySelectorAll('.oh__inner');
if (textSliders.length < 2) return; // No animations needed
const activeIndex = row.dataset.active === '0' ? 0 : 1;
const inactiveIndex = activeIndex === 0 ? 1 : 0;
const currentSlider = textSliders[activeIndex];
const nextSlider = textSliders[inactiveIndex];
// Determine animation direction
const rowField = rowSelector.replace('[data-field="', '').replace('"]', '');
const animationDirection = this.rowAnimationDirections[rowField] || this.rowAnimationDirections['name'];
// Clone animation directions to prevent GSAP mutation
const clonedOutDirection = { ...animationDirection.out };
const clonedInDirection = { ...animationDirection.in };
// Kill and reset existing row animation
if (this.rowTimelines[rowSelector] && direction !== 'out') {
this.rowTimelines[rowSelector].kill();
}
this.rowTimelines[rowSelector] = gsap.timeline();
if (direction === 'in') {
// Reset both sliders to their "out" positions
gsap.set(currentSlider, clonedOutDirection);
gsap.set(nextSlider, clonedInDirection); // Ensure the next slider is positioned off-screen for the next animation
// Slide the current text out (tooltip appearing)
this.rowTimelines[rowSelector].to(currentSlider, {
...this.animationConfig.texts,
...clonedOutDirection, // Slide out to the correct direction
}, this.animationConfig.textsDelay);
// Slide the next text in
gsap.set(nextSlider, clonedInDirection); // Position off-screen
this.rowTimelines[rowSelector].to(nextSlider, {
...this.animationConfig.texts,
yPercent: 0, // Slide into place
onStart: () => {
nextSlider.textContent = newValue; // Update content
},
}, this.animationConfig.textsDelay); // Start after delay
}
else if (direction === 'none') {
// Transition between images
const transitionOutDirection = {
stagename: { yPercent: 100 }, // Slide down for stagename
name: { yPercent: -100 }, // Slide up for name
genre: { yPercent: -100 }, // Slide up for genre
}[rowField] || { yPercent: 0 };
this.rowTimelines[rowSelector].to(currentSlider, {
...this.animationConfig.texts,
...transitionOutDirection, // Correct "out" animation for transitions
}, 0);
// Slide the next text in
gsap.set(nextSlider, clonedInDirection); // Position off-screen
this.rowTimelines[rowSelector].to(nextSlider, {
...this.animationConfig.texts,
yPercent: 0, // Slide into place
onStart: () => {
nextSlider.textContent = newValue; // Update content
},
}, 0); // Start simultaneously
}
else if (direction === 'out') {
// Tooltip disappearing
this.rowTimelines[rowSelector].to(currentSlider, {
...clonedOutDirection, // Slide out to the correct direction
...this.animationConfig.texts,
}, 0);
}
// Update active state for the row
row.dataset.active = inactiveIndex.toString();
// Add row animations to the main timeline
timeline.add(this.rowTimelines[rowSelector], 0);
}
// Animate the arrow component
animateArrow(timeline, direction = 'none') {
if (!this.arrow) return;
// Kill and reset existing arrow animation
if (this.arrowTimeline) {
this.arrowTimeline.kill();
}
this.arrowTimeline = gsap.timeline();
// Determine animation direction for the arrow
const animationDirection = this.rowAnimationDirections['arrow'];
if (direction === 'in') {
this.arrowTimeline.fromTo(this.arrow, {
...animationDirection.in,
}, {
...this.animationConfig.texts,
yPercent: 0,
}, this.animationConfig.textsDelay);
} else if (direction === 'out') {
this.arrowTimeline.to(this.arrow, {
...this.animationConfig.texts,
...animationDirection.out,
}, 0);
}
// Add arrow animation to the main timeline
timeline.add(this.arrowTimeline, 0);
}
}
let tooltip;
// Page event handler
const handlePageEvent = (type) => {
const page = document.documentElement.getAttribute('data-page');
if (page !== 'home') return;
if (type === 'load') {
tooltip = new Tooltip(document.querySelector('[data-grid]'));
} else if (type === 'before-swap') {
tooltip.destroy();
}
};
// Listen for Astro's lifecycle events
document.addEventListener('astro:page-load', () => handlePageEvent('load'));
document.addEventListener('astro:before-swap', () => handlePageEvent('before-swap'));
src/styles/global.css
* {
box-sizing: border-box;
&::after,
&::before {
box-sizing: inherit;
}
}
:root {
font-size: 16px;
--color-text: #000;
--color-text-alt: #6a6a6a;
--color-faded: #c9c9c9;
--color-bg: #fff;
--border-color: #ccc;
--color-link: #000;
--color-link-hover: #999;
--color-placeholder: #f8f8f8;
--font-size-s: 0.85rem;
--font-size-l: clamp(1.25rem, 3vw, 1.75rem);
--font-size-xl: clamp(1.5rem, 5vw, 2.75rem);
}
body {
margin: 0;
padding: 0 1rem;
color: var(--color-text);
background-color: var(--color-bg);
line-height: 1;
font-family: 'Instrument Sans Variable', serif;
font-variant-ligatures: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
&.blurred {
filter: blur(5px);
pointer-events: none;
}
}
h2 {
font-size: var(--font-size-xl);
margin: 0;
line-height: 0.85;
}
h3 {
font-size: 1rem;
margin-bottom: 2.5rem;
}
p+h3 {
margin-top: 4rem;
}
a {
text-decoration: none;
color: var(--color-link);
outline: none;
cursor: pointer;
transition: color 0.3s;
&:hover {
color: var(--color-link-hover);
outline: none;
}
&:focus {
outline: none;
background-color: lightgrey;
&:not(:focus-visible) {
background-color: transparent;
}
&:focus-visible {
opacity: 0.5;
background-color: transparent;
}
}
}
img {
display: block;
}
.fade-in {
opacity: 0;
transition: opacity 0.2s ease-out;
}
.fade-in.loaded {
opacity: 1;
}
button {
background: none;
display: flex;
padding: 0;
border: 0;
cursor: pointer;
font-size: inherit;
font-weight: inherit;
color: inherit;
fill: currentColor;
&:hover {
color: var(--color-link-hover);
}
&:focus {
outline: none;
background-color: lightgrey;
&:not(:focus-visible) {
background-color: transparent;
}
&:focus-visible {
background-color: transparent;
color: var(--color-link-hover);
}
}
}
img {
max-width: 100%;
}
.hidden {
opacity: 0;
pointer-events: none;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.faded {
color: var(--color-faded);
}
.inline {
display: flex;
gap: 1rem;
}
.content-wrap {
display: grid;
gap: 2rem;
padding: 3rem 0 10vh;
grid-template-columns: 100%;
grid-auto-columns: auto;
grid-template-areas: 'img' 'content';
}
@media screen and (min-width: 44em) {
.content-wrap {
grid-template-columns: 35vw 1fr;
grid-template-areas: 'img content';
}
}
.content {
grid-area: content;
max-width: 500px;
}
.content--page {
max-width: none;
min-height: calc(100vh - 10rem);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
@media screen and (min-width: 50em) {
margin: 0 20vw;
padding: 3rem 1rem;
}
}
.content p,
.content ol {
line-height: 1.4;
font-weight: 500;
}
.content ol {
list-style-type: decimal-leading-zero;
padding: 0 0 0 1.8em;
margin-bottom: 2rem;
}
dl {
display: grid;
grid-template-columns: auto 1fr;
gap: 1rem;
margin-top: 2.5rem;
}
dt {
font-weight: bold;
grid-column: 1;
}
dd {
grid-column: 2;
margin: 0;
}
.image {
background-color: var(--color-placeholder);
grid-area: img;
margin-top: 1rem;
}
.title-header {
position: relative;
display: grid;
grid-template-columns: 100%;
font-weight: 700;
gap: 1rem;
align-items: end;
padding: 2rem 0 1.5rem;
width: 100%;
}
.title-header--initial {
font-size: var(--font-size-l);
@media screen and (min-width: 50em) {
grid-template-columns: 20vw 1fr auto;
gap: 1rem;
}
}Original author attribution실행 안내·자료
Players Club: A Free Astro Template for Showcasing Music Artists
Original author: Manoela Ilic
Published by Codrops. Copyright (c) 2026 Codrops.
License: MIT, pursuant to the publisher's downloadable-demo grant.
Keep CODROPS-MIT-2026.txt and the original project notices with copies.
Third-party media exceptions and credits are recorded separately in the source inventory.
Media credits and license evidence실행 안내·자료
README.md
## Credits
- Design based on [Alex Tkachev's](https://alextkachev.com/) [Players Club Dribbble shot](https://dribbble.com/shots/25156320-Players-Club-UI-Animation).
- Images generated with [Midjourney](https://midjourney.com)
## License
[MIT](LICENSE)
Made with :blue_heart: by [Codrops](http://www.codrops.com)
.preview/third-party-notices/@fontsource-variable--instrument-sans/LICENSE실행 안내·자료
Copyright 2022 The Instrument Sans Project Authors (https://github.com/Instrument/instrument-sans)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
.preview/third-party-notices/astro/LICENSE실행 안내·자료
MIT License
Copyright (c) 2021 Fred K. Schott
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.
"""
This license applies to parts of the `packages/create-astro` and `packages/astro` subdirectories originating from the https://github.com/sveltejs/kit repository:
Copyright (c) 2020 [these people](https://github.com/sveltejs/kit/graphs/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.
"""
"""
This license applies to parts of the `packages/create-astro` and `packages/astro` subdirectories originating from the https://github.com/vitejs/vite repository:
MIT License
Copyright (c) 2019-present, Yuxi (Evan) You and Vite 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.
"""
.preview/third-party-notices/astro-seo/LICENSE실행 안내·자료
MIT License
Copyright (c) 2021 Jonas Schumacher
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.
.preview/third-party-notices/ev-emitter/LICENSE.md실행 안내·자료
Copyright (c) 2016-2021 [David DeSandro](https://desandro.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.
.preview/third-party-notices/imagesloaded/LICENSE.md실행 안내·자료
Copyright (c) 2011-2022 [David DeSandro](https://desandro.com) and [contributors](https://github.com/desandro/imagesloaded/graphs/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.
.preview/third-party-notices/lenis/LICENSE실행 안내·자료
The MIT License
Copyright (c) 2024 darkroom.engineering
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.Bundled dependency licenses실행 안내·자료
Isolated framework dependency inventory
{
"id": "codrops-e55ee0a4e942",
"packages": [
{
"package": "astro",
"version": "5.3.0",
"declaredLicense": "MIT",
"noticeFiles": [
".preview/third-party-notices/astro/LICENSE"
],
"noticeScope": "Original installed package notices; not a replacement license grant."
},
{
"package": "@fontsource-variable/instrument-sans",
"version": "5.1.0",
"declaredLicense": "OFL-1.1",
"noticeFiles": [
".preview/third-party-notices/@fontsource-variable--instrument-sans/LICENSE"
],
"noticeScope": "Original installed package notices; not a replacement license grant."
},
{
"package": "astro-seo",
"version": "0.8.4",
"declaredLicense": "MIT",
"noticeFiles": [
".preview/third-party-notices/astro-seo/LICENSE"
],
"noticeScope": "Original installed package notices; not a replacement license grant."
},
{
"package": "gsap",
"version": "3.12.5",
"declaredLicense": "Standard 'no charge' license: https://gsap.com/standard-license. Club GSAP members get more: https://gsap.com/licensing/. Why GreenSock doesn't employ an MIT license: https://gsap.com/why-license/",
"noticeFiles": [
".preview/third-party-notices/gsap/README.md",
".preview/third-party-notices/gsap/package.json",
".preview/third-party-notices/gsap/source-copyright-header.txt"
],
"noticeScope": "Original installed package notices; not a replacement license grant."
},
{
"package": "imagesloaded",
"version": "5.0.0",
"declaredLicense": "MIT",
"noticeFiles": [
".preview/third-party-notices/imagesloaded/LICENSE.md"
],
"noticeScope": "Original installed package notices; not a replacement license grant."
},
{
"package": "lenis",
"version": "1.1.18",
"declaredLicense": "MIT",
"noticeFiles": [
".preview/third-party-notices/lenis/LICENSE"
],
"noticeScope": "Original installed package notices; not a replacement license grant."
},
{
"package": "ev-emitter",
"version": "2.1.2",
"declaredLicense": "MIT",
"noticeFiles": [
".preview/third-party-notices/ev-emitter/LICENSE.md"
],
"noticeScope": "Original installed package notices; not a replacement license grant."
}
],
"publisherNotice": ".preview/CODROPS-MIT.txt",
"authorAttribution": ".preview/ATTRIBUTION.txt"
}
.preview/ATTRIBUTION.txt
Players Club: A Free Astro Template for Showcasing Music Artists
Original author: Manoela Ilic
Published by Codrops. Copyright (c) 2026 Codrops.
License: MIT, pursuant to the publisher's downloadable-demo grant.
Keep CODROPS-MIT-2026.txt and the original project notices with copies.
Third-party media exceptions and credits are recorded separately in the source inventory.
.preview/third-party-notices/@fontsource-variable--instrument-sans/LICENSE
Copyright 2022 The Instrument Sans Project Authors (https://github.com/Instrument/instrument-sans)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
.preview/third-party-notices/astro/LICENSE
MIT License
Copyright (c) 2021 Fred K. Schott
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.
"""
This license applies to parts of the `packages/create-astro` and `packages/astro` subdirectories originating from the https://github.com/sveltejs/kit repository:
Copyright (c) 2020 [these people](https://github.com/sveltejs/kit/graphs/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.
"""
"""
This license applies to parts of the `packages/create-astro` and `packages/astro` subdirectories originating from the https://github.com/vitejs/vite repository:
MIT License
Copyright (c) 2019-present, Yuxi (Evan) You and Vite 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.
"""
.preview/third-party-notices/astro-seo/LICENSE
MIT License
Copyright (c) 2021 Jonas Schumacher
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.
.preview/third-party-notices/ev-emitter/LICENSE.md
Copyright (c) 2016-2021 [David DeSandro](https://desandro.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.
.preview/third-party-notices/gsap/README.md
# GSAP (GreenSock Animation Platform)
[](http://gsap.com)
GSAP is a **framework-agnostic** JavaScript animation library that turns developers into animation superheroes. Build high-performance animations that work in **every** major browser. Animate CSS, SVG, canvas, React, Vue, WebGL, colors, strings, motion paths, generic objects...anything JavaScript can touch! GSAP's <a href="https://gsap.com/docs/v3/Plugins/ScrollTrigger/">ScrollTrigger</a> plugin delivers jaw-dropping scroll-based animations with minimal code. <a href="https://gsap.com/docs/v3/GSAP/gsap.matchMedia()">gsap.matchMedia()</a> makes building responsive, accessibility-friendly animations a breeze.
No other library delivers such advanced sequencing, reliability, and tight control while solving real-world problems on over 12 million sites. GSAP works around countless browser inconsistencies; your animations ***just work***. At its core, GSAP is a high-speed property manipulator, updating values over time with extreme accuracy. It's up to 20x faster than jQuery!
GSAP is completely flexible; sprinkle it wherever you want. **Zero dependencies.**
There are many optional <a href="https://gsap.com/docs/v3/Plugins">plugins</a> and <a href="https://gsap.com/docs/v3/Eases">easing</a> functions for achieving advanced effects easily like <a href="https://gsap.com/docs/v3/Plugins/ScrollTrigger/">scrolling</a>, <a href="https://gsap.com/docs/v3/Plugins/MorphSVGPlugin">morphing</a>, animating along a <a href="https://gsap.com/docs/v3/Plugins/MotionPathPlugin">motion path</a> or <a href="https://gsap.com/docs/v3/Plugins/Flip/">FLIP</a> animations. There's even a handy <a href="https://gsap.com/docs/v3/Plugins/Observer/">Observer</a> for normalizing event detection across browsers/devices.
### Get Started
[](http://gsap.com/get-started)
## Docs & Installation
View the <a href="https://gsap.com/docs">full documentation here</a>, including an <a href="https://gsap.com/install">installation guide</a>.
### CDN
```html
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12/dist/gsap.min.js"></script>
```
See <a href="https://www.jsdelivr.com/gsap">JSDelivr's dedicated GSAP page</a> for quick CDN links to the core files/plugins. There are more <a href="https://gsap.com/install">installation instructions</a> at gsap.com.
**Every major ad network excludes GSAP from file size calculations** and most have it on their own CDNs, so contact them for the appropriate URL(s).
### NPM
See the <a href="https://gsap.com/install">guide to using GSAP via NPM here</a>.
```javascript
npm install gsap
```
GSAP's core can animate almost anything including CSS and attributes, plus it includes all of the <a href="https://gsap.com/docs/v3/GSAP/UtilityMethods">utility methods</a> like <a href="https://gsap.com/docs/v3/GSAP/UtilityMethods/interpolate()">interpolate()</a>, <a href="https://gsap.com/docs/v3/GSAP/UtilityMethods/mapRange()">mapRange()</a>, most of the <a href="https://gsap.com/docs/v3/Eases">eases</a>, and it can do snapping and modifiers.
```javascript
// typical import
import gsap from "gsap";
// get other plugins:
import ScrollTrigger from "gsap/ScrollTrigger";
import Flip from "gsap/Flip";
import Draggable from "gsap/Draggable";
// or all tools are exported from the "all" file (excluding members-only plugins):
import { gsap, ScrollTrigger, Draggable, MotionPathPlugin } from "gsap/all";
// don't forget to register plugins
gsap.registerPlugin(ScrollTrigger, Draggable, Flip, MotionPathPlugin);
```
The NPM files are ES modules, but there's also a /dist/ directory with <a href="https://www.davidbcalhoun.com/2014/what-is-amd-commonjs-and-umd/">UMD</a> files for extra compatibility.
Download <a href="https://gsap.com/pricing/">Club GSAP</a> members-only plugins from your gsap.com account and then include them in your own JS payload. There's even a <a href="https://www.youtube.com/watch?v=znVi89_gazE">tarball file you can install with NPM/Yarn</a>. GSAP has a <a href="https://gsap.com/docs/v3/Installation#private">private NPM registry</a> for members too. Post questions in our <a href="https://gsap.com/community/">forums</a> and we'd be happy to help.
### ScrollTrigger & ScrollSmoother
If you're looking for scroll-driven animations, GSAP's <a href="https://gsap.com/docs/v3/Plugins/ScrollTrigger/">ScrollTrigger</a> plugin is the new standard. There's a companion <a href="https://gsap.com/docs/v3/Plugins/ScrollSmoother/">ScrollSmoother</a> as well.
[](https://gsap.com/docs/v3/Plugins/ScrollTrigger)
### Using React?
There's a <a href="https://www.npmjs.com/package/@gsap/react">@gsap/react</a> package that exposes a `useGSAP()` hook which is a drop-in replacement for `useEffect()`/`useLayoutEffect()`, automating cleanup tasks. Please read the <a href="https://gsap.com/react">React guide</a> for details.
### Resources
* <a href="https://gsap.com/">gsap.com</a>
* <a href="https://gsap.com/get-started/">Getting started guide</a>
* <a href="https://gsap.com/docs/">Docs</a>
* <a href="https://gsap.com/resources/demos">Demos & starter templates</a>
* <a href="https://gsap.com/community/">Community forums</a>
* <a href="https://gsap.com/docs/v3/Eases">Ease Visualizer</a>
* <a href="https://gsap.com/showcase">Showcase</a>
* <a href="https://www.youtube.com/@GreenSockLearning">YouTube Channel</a>
* <a href="https://gsap.com/cheatsheet">Cheat sheet</a>
* <a href="https://gsap.com/trial">Try bonus plugins for free</a>
* <a href="https://gsap.com/pricing/">Club GSAP</a> (get access to unrestricted bonus plugins that are not in this repository)
### What is Club GSAP?
There are 3 main reasons anyone signs up for <a href="https://gsap.com/pricing">Club GSAP</a>:
* To get access to snazzy <a href="https://gsap.com/pricing">members-only plugins</a> like MorphSVG, SplitText, ScrollSmoother, etc.
* To get the special <a href="https://gsap.com/licensing/">commercial license</a>.
* To support ongoing development efforts and **cheer us on**.
<a href="https://gsap.com/pricing/">Learn more</a>.
### Try all bonus plugins for free!
<a href="https://gsap.com/trial">https://gsap.com/trial</a>
### Need help?
Ask in the friendly <a href="https://gsap.com/community/">GSAP forums</a>. Or share your knowledge and help someone else - it's a great way to sharpen your skills! Report any bugs there too (or <a href="https://github.com/greensock/GSAP/issues">file an issue here</a> if you prefer).
### License
GreenSock's standard "no charge" license can be viewed at <a href="https://gsap.com/standard-license">https://gsap.com/standard-license</a>. <a href="https://gsap.com/pricing/">Club GSAP</a> members are granted additional rights. See <a href="https://gsap.com/licensing/">https://gsap.com/licensing/</a> for details. Why doesn't GSAP use an MIT (or similar) open source license, and why is that a **good** thing? This article explains it all: <a href="https://gsap.com/why-license/" target="_blank">https://gsap.com/why-license/</a>
Copyright (c) 2008-2023, GreenSock. All rights reserved.
.preview/third-party-notices/gsap/source-copyright-header.txt
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }
/*!
* GSAP 3.12.5
* https://gsap.com
*
* @license Copyright 2008-2024, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license or for
* Club GSAP members, the agreement issued with that membership.
* @author: Jack Doyle, jack@greensock.com
*/
.preview/third-party-notices/imagesloaded/LICENSE.md
Copyright (c) 2011-2022 [David DeSandro](https://desandro.com) and [contributors](https://github.com/desandro/imagesloaded/graphs/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.
.preview/third-party-notices/lenis/LICENSE
The MIT License
Copyright (c) 2024 darkroom.engineering
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.
BEHIND THE EXAMPLE
이 장면을 만드는 원리
검색 문자열을 필터와 표시 양쪽에 쓰기
시각적인 글자 분할과 사용자에게 한 글자로 보이는 단위를 구분합니다.
- 이 예제에서는
- handleSearchInput은 입력을 filterGrid로 넘기고 검색 버튼의 searchContent.innerHTML에도 직접 넣습니다. 필터는 data-name·data-stagename을 소문자로 비교합니다.
코드와 함께 확인하기
코드에서 찾기
const handleSearchInputgridActions.js같은 사용자 입력이 문자열 검색과 HTML 해석이라는 서로 다른 경로를 거칩니다.
직접 해보기
꺾쇠가 있는 평문과 조합 중인 한글을 입력하고 버튼 표시·필터 결과를 비교합니다.
살펴볼 변화검색어를 HTML로 해석하지 않고 입력한 문자로 표시해야 하며 조합 중 필터 변화와 최종 결과의 일관성을 확인해야 합니다.
