Advanced interactionsCC0-style snippets
Hover lens
Move a soft lens across a visual card to reveal depth without loading a WebGL layer.
[data-sc-effect="hover-lens"] { --sc-x: 50%; --sc-y: 50%; position: relative; overflow: hidden; }
[data-sc-effect="hover-lens"]::after { content: ""; position: absolute; inset: 0; background: radial-gradient(100px circle at var(--sc-x) var(--sc-y), rgba(255,255,255,.7), transparent 64%); mix-blend-mode: screen; pointer-events: none; }
@media (prefers-reduced-motion: reduce) { [data-sc-effect="hover-lens"]::after { display: none; } }
document.querySelectorAll('[data-sc-effect="hover-lens"]').forEach((card) => card.addEventListener('pointermove', (event) => { const box = card.getBoundingClientRect(); card.style.setProperty('--sc-x', `${event.clientX - box.left}px`); card.style.setProperty('--sc-y', `${event.clientY - box.top}px`); }));
Advanced interactionsCC0-style snippets
Modal dialog
A focus-friendly native dialog pattern with a real close button and escape behavior.
[data-sc-effect="modal-dialog"] dialog { max-width: min(90vw, 32rem); border: 1px solid #dce3ee; border-radius: 12px; padding: 1.25rem; box-shadow: 0 24px 80px rgba(15,23,42,.2); }
[data-sc-effect="modal-dialog"] dialog::backdrop { background: rgba(15,23,42,.45); }
@media (prefers-reduced-motion: reduce) { [data-sc-effect="modal-dialog"] dialog { scroll-behavior: auto; } }
<div data-sc-effect="modal-dialog"><button type="button" data-dialog-open aria-haspopup="dialog">Open details</button><dialog data-dialog><p>Short supporting content.</p><button type="button" data-dialog-close>Close</button></dialog></div>
<script>const modalRoot = document.querySelector('[data-sc-effect="modal-dialog"]'); const dialog = modalRoot.querySelector('[data-dialog]'); modalRoot.querySelector('[data-dialog-open]').addEventListener('click', () => dialog.showModal()); modalRoot.querySelector('[data-dialog-close]').addEventListener('click', () => dialog.close());</script>
Advanced interactionsCC0-style snippets
Command palette
Expose a small keyboard-friendly command surface for power users without adding a framework.
[data-sc-effect="command-palette"] [role="dialog"] { border: 1px solid #dce3ee; border-radius: 12px; background: #fff; box-shadow: 0 24px 70px rgba(15,23,42,.18); }
[data-sc-effect="command-palette"] [aria-selected="true"] { background: #edf4ff; color: #155eef; }
@media (prefers-reduced-motion: reduce) { [data-sc-effect="command-palette"] [role="dialog"] { transition: none; } }
const palette = document.querySelector('[data-sc-effect="command-palette"]'); const paletteDialog = palette.querySelector('[role="dialog"]'); const paletteButton = palette.querySelector('[data-palette-open]'); paletteButton.addEventListener('click', () => paletteDialog.hidden = false); palette.addEventListener('keydown', (event) => { if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') { event.preventDefault(); paletteDialog.hidden = false; paletteDialog.querySelector('input')?.focus(); } if (event.key === 'Escape') paletteDialog.hidden = true; });
Advanced interactionsCC0-style snippets
Theme switcher
Switch a component between light and dark visual tokens while keeping the state explicit and keyboard accessible.
[data-sc-effect="theme-switcher"] { --sc-surface: #fff; --sc-ink: #172033; padding: 1rem; border-radius: 10px; background: var(--sc-surface); color: var(--sc-ink); transition: background 180ms ease, color 180ms ease; }
[data-sc-effect="theme-switcher"].is-dark { --sc-surface: #172033; --sc-ink: #fff; }
@media (prefers-reduced-motion: reduce) { [data-sc-effect="theme-switcher"] { transition: none; } }
const themeRoot = document.querySelector('[data-sc-effect="theme-switcher"]'); themeRoot.querySelector('button').addEventListener('click', () => { const dark = themeRoot.classList.toggle('is-dark'); themeRoot.querySelector('[data-theme-label]').textContent = dark ? 'Dark' : 'Light'; });
Advanced interactionsCC0-style snippets
Cinematic object drift
Gently floats up to eight direct child objects on a selected section with varied timing and depth.
/* CSS */
[data-sc-effect="cinematic-float"] > [data-sc-float-object] { animation: sc-object-drift var(--sc-motion-duration, var(--sc-float-duration, 8s)) ease-in-out var(--sc-float-delay, 0s) infinite var(--sc-motion-direction, alternate); transform: translate3d(0, 0, 0); }
[data-sc-effect="cinematic-float"] > [data-sc-float-object][data-sc-motion-type="spin"] { animation-name: sc-object-spin; }
[data-sc-effect="cinematic-float"] > [data-sc-float-object][data-sc-motion-type="pulse"] { animation-name: sc-object-pulse; }
[data-sc-effect="cinematic-float"] > [data-sc-float-object][data-sc-motion-type="slide"] { animation-name: sc-object-slide; }
@keyframes sc-object-drift { to { transform: translate3d(var(--sc-float-x, 0px), var(--sc-float-y, -14px), 0) rotate(var(--sc-float-rotate, 1deg)); } }
@keyframes sc-object-spin { to { transform: translate3d(0, var(--sc-float-y, -14px), 0) rotate(var(--sc-float-spin, 16deg)); } }
@keyframes sc-object-pulse { to { transform: translate3d(0, var(--sc-float-y, -14px), 0) scale(var(--sc-float-scale, 1.035)); } }
@keyframes sc-object-slide { to { transform: translate3d(var(--sc-float-x, 8px), 0, 0); } }
@media (prefers-reduced-motion: reduce) { [data-sc-effect="cinematic-float"] > [data-sc-float-object] { animation: none; transform: none; } }
/* JS */
document.querySelectorAll('[data-sc-effect="cinematic-float"]').forEach((scene) => {
const mode = getComputedStyle(scene).getPropertyValue('--sc-object-mode').replace(/["']/g, '').trim() || 'all';
const objects = Array.from(scene.children).filter((item) => mode === 'media' ? item.matches('img,video,picture,svg,canvas') : mode === 'text-media' ? item.matches('h1,h2,h3,h4,p,img,video,picture,svg,canvas') : !item.matches('button,input,select,textarea,a,[contenteditable="true"]'));
const intensity = Math.max(.5, Math.min(1.5, parseFloat(getComputedStyle(scene).getPropertyValue('--sc-motion-intensity')) || 1));
const choreography = getComputedStyle(scene).getPropertyValue('--sc-object-motion').replace(/["']/g, '').trim() || 'float';
const stagger = Math.max(0, Math.min(800, parseFloat(getComputedStyle(scene).getPropertyValue('--sc-motion-stagger')) || 180)) / 1000;
const motionTypes = ['float', 'spin', 'pulse', 'slide'];
objects.slice(0, 12).forEach((item, index) => {
item.setAttribute('data-sc-float-object', '');
item.setAttribute('data-sc-motion-type', choreography === 'mixed' ? motionTypes[index % motionTypes.length] : choreography);
item.style.setProperty('--sc-float-duration', `${7 + (index % 4) * 1.4}s`);
item.style.setProperty('--sc-float-delay', `${index * stagger}s`);
item.style.setProperty('--sc-float-x', `${(index % 2 ? -8 : 8) * intensity}px`);
item.style.setProperty('--sc-float-y', `${(-10 - (index % 3) * 5) * intensity}px`);
item.style.setProperty('--sc-float-rotate', `${(index % 2 ? -1.2 : 1.2) * intensity}deg`);
item.style.setProperty('--sc-float-spin', `${(index % 2 ? -14 : 14) * intensity}deg`);
item.style.setProperty('--sc-float-scale', String(1 + .035 * intensity));
});
});