Ben-S's picture
Make sure all the interactive components actually make changes and provide live updates. The grid scaling works but shadows/scale/colors,fonts don't update
6ba7037 verified
Raw
History Blame Contribute Delete
28.5 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TokenCraft - Design System Studio</title>
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
<script src="https://cdn.tailwindcss.com">
</script>
<style>
:root {
--grid-size: 8px;
--base-font: 16px;
--spacing-scale: 1;
--shadow-opacity: 0.075;
--font-family: system-ui, -apple-system, sans-serif;
}
body {
font-family: var(--font-family);
font-size: var(--base-font);
}
.preview-card {
margin-bottom: calc(var(--grid-size) * var(--spacing-scale) * 2);
padding: calc(var(--grid-size) * var(--spacing-scale) * 2);
box-shadow: 0 calc(var(--grid-size) * 0.5) calc(var(--grid-size) * 1) rgba(0,0,0,var(--shadow-opacity));
}
button, input, select {
padding: calc(var(--grid-size) * var(--spacing-scale)) calc(var(--grid-size) * var(--spacing-scale) * 1.5);
margin-bottom: calc(var(--grid-size) * var(--spacing-scale));
}
.color-primary {
background-color: var(--color-primary-600);
border-color: var(--color-primary-500);
color: var(--color-primary-600);
}
</style>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.net.min.js"></script>
<style>
.token-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: #4f46e5;
cursor: pointer;
}
.preview-card {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.grid-dot {
background-image: radial-gradient(currentColor 1px, transparent 1px);
background-size: var(--grid-size) var(--grid-size);
}
</style>
</head>
<body class="bg-gray-50 font-sans antialiased">
<!-- Vanta.js Background -->
<div id="vanta-bg" class="fixed inset-0 -z-10 opacity-10"></div>
<div class="min-h-screen">
<!-- Header -->
<header class="bg-white/80 backdrop-blur-md border-b border-gray-200 sticky top-0 z-50">
<div class="container mx-auto px-6 py-4">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-2">
<i data-feather="box" class="text-indigo-600 w-6 h-6"></i>
<h1 class="text-2xl font-bold text-gray-800">TokenCraft</h1>
</div>
<nav class="hidden md:flex space-x-8">
<a href="#" class="text-indigo-600 font-medium">Design Tokens</a>
<a href="#" class="text-gray-600 hover:text-indigo-600 transition">Components</a>
<a href="#" class="text-gray-600 hover:text-indigo-600 transition">Typography</a>
<a href="#" class="text-gray-600 hover:text-indigo-600 transition">Export</a>
</nav>
<button class="md:hidden text-gray-600">
<i data-feather="menu"></i>
</button>
</div>
</div>
</header>
<main class="container mx-auto px-6 py-8 grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Token Controls Panel -->
<div class="lg:col-span-1 bg-white rounded-xl shadow-md p-6 sticky top-24 h-fit">
<div class="flex items-center justify-between mb-6">
<h2 class="text-xl font-bold text-gray-800">Design Tokens</h2>
<button class="text-indigo-600 hover:text-indigo-800 transition">
<i data-feather="save" class="w-5 h-5"></i>
</button>
</div>
<!-- Grid System Control -->
<div class="mb-8">
<div class="flex items-center justify-between mb-3">
<h3 class="font-medium text-gray-700">Grid System</h3>
<span class="text-sm font-mono bg-gray-100 px-2 py-1 rounded" id="grid-value">8px</span>
</div>
<input type="range" min="4" max="16" step="2" value="8" class="w-full token-slider" id="grid-slider">
<div class="flex justify-between text-xs text-gray-500 mt-1">
<span>4px</span>
<span>8px</span>
<span>12px</span>
<span>16px</span>
</div>
</div>
<!-- Color Palette -->
<div class="mb-8">
<h3 class="font-medium text-gray-700 mb-3">Color Palette</h3>
<div class="grid grid-cols-5 gap-2">
<div class="flex flex-col items-center">
<div class="w-8 h-8 rounded-full bg-indigo-500 mb-1 cursor-pointer hover:scale-110 transition" data-color="indigo"></div>
<span class="text-xs">Primary</span>
</div>
<div class="flex flex-col items-center">
<div class="w-8 h-8 rounded-full bg-emerald-500 mb-1 cursor-pointer hover:scale-110 transition" data-color="emerald"></div>
<span class="text-xs">Success</span>
</div>
<div class="flex flex-col items-center">
<div class="w-8 h-8 rounded-full bg-amber-500 mb-1 cursor-pointer hover:scale-110 transition" data-color="amber"></div>
<span class="text-xs">Warning</span>
</div>
<div class="flex flex-col items-center">
<div class="w-8 h-8 rounded-full bg-rose-500 mb-1 cursor-pointer hover:scale-110 transition" data-color="rose"></div>
<span class="text-xs">Danger</span>
</div>
<div class="flex flex-col items-center">
<div class="w-8 h-8 rounded-full bg-gray-500 mb-1 cursor-pointer hover:scale-110 transition" data-color="gray"></div>
<span class="text-xs">Neutral</span>
</div>
</div>
</div>
<!-- Spacing Scale -->
<div class="mb-8">
<h3 class="font-medium text-gray-700 mb-3">Spacing Scale</h3>
<div class="space-y-4">
<div>
<label class="block text-sm text-gray-600 mb-1">Base Unit</label>
<div class="flex items-center space-x-2">
<input type="range" min="0.5" max="2" step="0.25" value="1" class="w-full token-slider" id="spacing-slider">
<span class="text-sm font-mono w-12 text-center" id="spacing-value">1x</span>
</div>
</div>
<div>
<label class="block text-sm text-gray-600 mb-1">Vertical Rhythm</label>
<select class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm">
<option>8-point grid</option>
<option selected>4-point grid</option>
<option>Custom</option>
</select>
</div>
</div>
</div>
<!-- Typography Scale -->
<div class="mb-8">
<h3 class="font-medium text-gray-700 mb-3">Typography</h3>
<div class="space-y-4">
<div>
<label class="block text-sm text-gray-600 mb-1">Base Size</label>
<div class="flex items-center space-x-2">
<input type="range" min="12" max="20" step="1" value="16" class="w-full token-slider" id="font-slider">
<span class="text-sm font-mono w-12 text-center" id="font-value">16px</span>
</div>
</div>
<div>
<label class="block text-sm text-gray-600 mb-1">Type Scale</label>
<select class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm">
<option>Major Third (1.25)</option>
<option selected>Perfect Fourth (1.333)</option>
<option>Golden Ratio (1.618)</option>
</select>
</div>
<div>
<label class="block text-sm text-gray-600 mb-1">Font Family</label>
<select class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm">
<option>Inter (Sans-serif)</option>
<option selected>System UI</option>
<option>Georgia (Serif)</option>
</select>
</div>
</div>
</div>
<!-- Shadow System -->
<div>
<h3 class="font-medium text-gray-700 mb-3">Shadows</h3>
<div class="space-y-3">
<div class="flex items-center space-x-2">
<input type="checkbox" id="shadow-toggle" class="rounded text-indigo-600 focus:ring-indigo-500" checked>
<label for="shadow-toggle" class="text-sm text-gray-600">Enable shadows</label>
</div>
<div>
<label class="block text-sm text-gray-600 mb-1">Shadow Intensity</label>
<input type="range" min="0" max="100" step="10" value="50" class="w-full token-slider" id="shadow-slider">
</div>
</div>
</div>
</div>
<!-- Preview Panel -->
<div class="lg:col-span-2 space-y-8">
<!-- Grid Visualization -->
<div class="bg-white rounded-xl shadow-md p-6">
<div class="flex items-center justify-between mb-4">
<h2 class="text-lg font-medium text-gray-800">Grid System</h2>
<div class="flex items-center space-x-2">
<button class="text-gray-500 hover:text-indigo-600 transition" id="grid-toggle">
<i data-feather="eye" class="w-4 h-4"></i>
</button>
<span class="text-xs font-mono bg-gray-100 px-2 py-1 rounded">8px baseline</span>
</div>
</div>
<div class="grid-dot h-32 w-full border border-gray-200 rounded-md" style="--grid-size: 8px; color: rgba(0,0,0,0.05)"></div>
</div>
<!-- UI Components Preview -->
<div class="bg-white rounded-xl shadow-md p-6">
<h2 class="text-lg font-medium text-gray-800 mb-6">Component Preview</h2>
<!-- Buttons -->
<div class="mb-8">
<h3 class="text-sm font-medium text-gray-700 mb-3">Buttons</h3>
<div class="flex flex-wrap gap-3">
<button class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 transition">Primary</button>
<button class="px-4 py-2 bg-white border border-gray-300 text-gray-700 rounded-md hover:bg-gray-50 transition">Secondary</button>
<button class="px-4 py-2 bg-emerald-500 text-white rounded-md hover:bg-emerald-600 transition">Success</button>
<button class="px-4 py-2 bg-amber-500 text-white rounded-md hover:bg-amber-600 transition">Warning</button>
<button class="px-4 py-2 bg-rose-500 text-white rounded-md hover:bg-rose-600 transition">Danger</button>
</div>
</div>
<!-- Cards -->
<div class="mb-8">
<h3 class="text-sm font-medium text-gray-700 mb-3">Cards</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="preview-card border border-gray-200 rounded-lg overflow-hidden hover:shadow-md">
<div class="h-32 bg-indigo-100"></div>
<div class="p-4">
<h4 class="font-medium mb-2">Default Card</h4>
<p class="text-sm text-gray-600">This card demonstrates the current spacing and shadow settings.</p>
</div>
</div>
<div class="preview-card border border-gray-200 rounded-lg overflow-hidden hover:shadow-md">
<div class="h-32 bg-emerald-100"></div>
<div class="p-4">
<h4 class="font-medium mb-2">Another Card</h4>
<p class="text-sm text-gray-600">All components update live as you adjust the design tokens.</p>
</div>
</div>
</div>
</div>
<!-- Form Elements -->
<div>
<h3 class="text-sm font-medium text-gray-700 mb-3">Form Elements</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Text Input</label>
<input type="text" class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="Type something...">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Select Menu</label>
<select class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</select>
</div>
<div class="flex items-center space-x-3">
<div class="flex items-center">
<input id="push-everything" name="push-notifications" type="radio" class="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-500">
<label for="push-everything" class="ml-2 block text-sm text-gray-700">Radio 1</label>
</div>
<div class="flex items-center">
<input id="push-email" name="push-notifications" type="radio" class="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-500">
<label for="push-email" class="ml-2 block text-sm text-gray-700">Radio 2</label>
</div>
</div>
<div class="flex items-center">
<input id="comments" name="comments" type="checkbox" class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500">
<label for="comments" class="ml-2 block text-sm text-gray-700">Checkbox option</label>
</div>
</div>
</div>
</div>
<!-- Typography Preview -->
<div class="bg-white rounded-xl shadow-md p-6">
<h2 class="text-lg font-medium text-gray-800 mb-6">Typography</h2>
<div class="space-y-4">
<h1 class="text-4xl font-bold">Heading 1</h1>
<h2 class="text-3xl font-bold">Heading 2</h2>
<h3 class="text-2xl font-bold">Heading 3</h3>
<h4 class="text-xl font-bold">Heading 4</h4>
<h5 class="text-lg font-bold">Heading 5</h5>
<p class="text-base">Regular paragraph text. The quick brown fox jumps over the lazy dog.</p>
<p class="text-sm">Smaller text for captions and secondary information.</p>
<p class="text-xs">Very small text for meta information or labels.</p>
<div class="pt-4 border-t border-gray-200">
<p class="font-mono text-sm bg-gray-50 p-2 rounded">Code: font-family: system-ui, -apple-system, sans-serif;</p>
</div>
</div>
</div>
</div>
</main>
</div>
<script>
// Initialize Vanta.js background
const vantaEffect = VANTA.NET({
el: "#vanta-bg",
mouseControls: true,
touchControls: true,
gyroControls: false,
minHeight: 200.00,
minWidth: 200.00,
scale: 1.00,
scaleMobile: 1.00,
color: 0x7c3aed,
backgroundColor: 0xf9fafb,
points: 10.00,
maxDistance: 20.00
});
// Initialize Feather Icons
feather.replace();
// Design Token Store
const designTokens = {
grid: 8,
color: 'indigo',
spacing: 1,
font: 16,
shadow: true,
shadowIntensity: 50,
typeScale: 'Perfect Fourth (1.333)',
fontFamily: 'System UI'
};
// Utility function to update CSS variables
function updateCSSVariables() {
const root = document.documentElement;
root.style.setProperty('--grid-size', `${designTokens.grid}px`);
root.style.setProperty('--base-font', `${designTokens.font}px`);
root.style.setProperty('--spacing-scale', designTokens.spacing);
// Update shadows
const shadowOpacity = designTokens.shadow ? (designTokens.shadowIntensity / 100) * 0.15 : 0;
root.style.setProperty('--shadow-opacity', shadowOpacity);
// Update color scheme
document.querySelectorAll('.color-primary').forEach(el => {
el.classList.remove(`bg-${designTokens.color}-600`, `border-${designTokens.color}-500`, `text-${designTokens.color}-600`);
el.classList.add(`bg-${designTokens.color}-600`, `border-${designTokens.color}-500`, `text-${designTokens.color}-600`);
});
// Update font family
if (designTokens.fontFamily === 'System UI') {
root.style.setProperty('--font-family', 'system-ui, -apple-system, sans-serif');
} else if (designTokens.fontFamily === 'Inter (Sans-serif)') {
root.style.setProperty('--font-family', '"Inter", sans-serif');
if (!document.querySelector('#inter-font')) {
const link = document.createElement('link');
link.id = 'inter-font';
link.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap';
link.rel = 'stylesheet';
document.head.appendChild(link);
}
} else if (designTokens.fontFamily === 'Georgia (Serif)') {
root.style.setProperty('--font-family', 'Georgia, serif');
}
}
// Grid System Controls
const gridSlider = document.getElementById('grid-slider');
const gridValue = document.getElementById('grid-value');
const gridToggle = document.getElementById('grid-toggle');
const gridDot = document.querySelector('.grid-dot');
gridSlider.addEventListener('input', function() {
designTokens.grid = parseInt(this.value);
gridValue.textContent = `${designTokens.grid}px`;
updateCSSVariables();
});
gridToggle.addEventListener('click', function() {
const currentColor = gridDot.style.color;
gridDot.style.color = currentColor === 'rgba(0,0,0,0.05)' ? 'transparent' : 'rgba(0,0,0,0.05)';
const icon = this.querySelector('i');
if (icon.dataset.feather === 'eye') {
icon.dataset.feather = 'eye-off';
} else {
icon.dataset.feather = 'eye';
}
feather.replace();
});
// Color Palette Selection
document.querySelectorAll('[data-color]').forEach(swatch => {
swatch.addEventListener('click', function() {
designTokens.color = this.dataset.color;
updateCSSVariables();
// Update all elements with color classes
document.querySelectorAll('[class*="bg-"], [class*="text-"], [class*="border-"]').forEach(el => {
const classes = Array.from(el.classList);
classes.forEach(cls => {
if (cls.includes('bg-') || cls.includes('text-') || cls.includes('border-')) {
if (cls.includes('indigo') || cls.includes('emerald') || cls.includes('amber') || cls.includes('rose') || cls.includes('gray')) {
const newClass = cls.replace(/(indigo|emerald|amber|rose|gray)/g, designTokens.color);
el.classList.remove(cls);
el.classList.add(newClass);
}
}
});
});
});
});
// Spacing Controls
const spacingSlider = document.getElementById('spacing-slider');
const spacingValue = document.getElementById('spacing-value');
spacingSlider.addEventListener('input', function() {
designTokens.spacing = parseFloat(this.value);
spacingValue.textContent = `${designTokens.spacing}x`;
updateCSSVariables();
// Update preview cards and other components with spacing
document.querySelectorAll('.preview-card, button, input, select').forEach(el => {
const basePadding = 8 * designTokens.spacing;
el.style.padding = `${basePadding}px ${basePadding * 1.5}px`;
el.style.marginBottom = `${basePadding}px`;
});
});
// Font Controls
const fontSlider = document.getElementById('font-slider');
const fontValue = document.getElementById('font-value');
fontSlider.addEventListener('input', function() {
designTokens.font = parseInt(this.value);
fontValue.textContent = `${designTokens.font}px`;
updateCSSVariables();
// Update all text elements with new font size
document.querySelectorAll('h1, h2, h3, h4, h5, p, button, input, select').forEach(el => {
if (el.tagName.startsWith('H')) {
const level = parseInt(el.tagName[1]);
const scale = Math.pow(1.333, 5 - level); // Perfect Fourth scale
el.style.fontSize = `${designTokens.font * scale}px`;
} else if (el.tagName === 'P') {
el.style.fontSize = `${designTokens.font}px`;
}
});
});
// Type Scale Control
document.querySelectorAll('select')[0].addEventListener('change', function() {
designTokens.typeScale = this.value;
updateCSSVariables();
// Trigger font size update
fontSlider.dispatchEvent(new Event('input'));
});
// Font Family Control
document.querySelectorAll('select')[1].addEventListener('change', function() {
designTokens.fontFamily = this.value;
updateCSSVariables();
// Update all text elements with new font family
document.querySelectorAll('h1, h2, h3, h4, h5, p, button, input, select').forEach(el => {
if (designTokens.fontFamily === 'System UI') {
el.style.fontFamily = 'system-ui, -apple-system, sans-serif';
} else if (designTokens.fontFamily === 'Inter (Sans-serif)') {
el.style.fontFamily = '"Inter", sans-serif';
} else if (designTokens.fontFamily === 'Georgia (Serif)') {
el.style.fontFamily = 'Georgia, serif';
}
});
});
// Shadow Controls
const shadowSlider = document.getElementById('shadow-slider');
const shadowToggle = document.getElementById('shadow-toggle');
shadowSlider.addEventListener('input', function() {
designTokens.shadowIntensity = parseInt(this.value);
updateCSSVariables();
// Update shadow on all elements
const shadowOpacity = designTokens.shadow ? (designTokens.shadowIntensity / 100) * 0.15 : 0;
document.querySelectorAll('.preview-card, button, .rounded-xl').forEach(el => {
if (designTokens.shadow) {
el.style.boxShadow = `0 ${designTokens.grid * 0.5}px ${designTokens.grid * 1}px rgba(0,0,0,${shadowOpacity})`;
} else {
el.style.boxShadow = 'none';
}
});
});
shadowToggle.addEventListener('change', function() {
designTokens.shadow = this.checked;
updateCSSVariables();
// Trigger shadow update
shadowSlider.dispatchEvent(new Event('input'));
});
// Save Button
document.querySelector('[data-feather="save"]').parentElement.addEventListener('click', function() {
const dataStr = JSON.stringify(designTokens, null, 2);
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
const exportName = `tokencraft-${new Date().toISOString().slice(0,10)}.json`;
const linkElement = document.createElement('a');
linkElement.setAttribute('href', dataUri);
linkElement.setAttribute('download', exportName);
linkElement.click();
// Show temporary feedback
const icon = this.querySelector('i');
const originalIcon = icon.dataset.feather;
icon.dataset.feather = 'check';
feather.replace();
setTimeout(() => {
icon.dataset.feather = originalIcon;
feather.replace();
}, 2000);
});
// Initialize with current values
updateCSSVariables();
// Add initial event triggers to set up the UI correctly
setTimeout(() => {
fontSlider.dispatchEvent(new Event('input'));
spacingSlider.dispatchEvent(new Event('input'));
shadowSlider.dispatchEvent(new Event('input'));
}, 100);
</script>
</body>
</html>