# ThrillWiki Design System - Component Library ## Overview Comprehensive component library for ThrillWiki's frontend redesign, providing reusable UI patterns that implement the design token system consistently across all templates. ## Component Categories ### 1. Layout Components #### Container ```html
``` ```css .container { width: 100%; margin-left: auto; margin-right: auto; padding-left: var(--spacing-container-sm); padding-right: var(--spacing-container-sm); } @media (min-width: 640px) { .container { padding-left: var(--spacing-container-md); padding-right: var(--spacing-container-md); } } @media (min-width: 1024px) { .container { padding-left: var(--spacing-container-lg); padding-right: var(--spacing-container-lg); } } .container-constrained { max-width: 1280px; } .container-fluid { width: 100%; padding-left: var(--spacing-container-xs); padding-right: var(--spacing-container-xs); } ``` #### Grid System ```html
Item 1
Item 2
Item 3
Item 1
Item 2
Item 3
``` ```css .grid-auto-fit { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--spacing-layout-md); } .grid-item { min-width: 0; /* Prevent grid blowout */ } ``` ### 2. Navigation Components #### Primary Navigation ```html ``` ```css .nav-primary { background: var(--bg-elevated); border-bottom: 1px solid var(--border-primary); position: sticky; top: 0; z-index: var(--z-sticky); backdrop-filter: blur(8px); } .nav-container { display: flex; align-items: center; justify-content: space-between; padding: var(--spacing-component-md) var(--spacing-container-md); max-width: 1280px; margin: 0 auto; } .nav-brand { flex-shrink: 0; } .nav-logo-img { height: 2rem; width: auto; } .nav-links { display: flex; align-items: center; gap: var(--spacing-component-lg); } .nav-link { color: var(--text-secondary); text-decoration: none; font-weight: 500; padding: var(--spacing-component-sm) var(--spacing-component-md); border-radius: var(--radius-md); transition: all var(--transition-fast); } .nav-link:hover { color: var(--text-primary); background: var(--interactive-secondary); } .nav-link.active { color: var(--text-brand); background: var(--color-primary-50); } .dark .nav-link.active { background: var(--color-primary-900); } .nav-actions { display: flex; align-items: center; gap: var(--spacing-component-sm); } .nav-mobile { border-top: 1px solid var(--border-primary); background: var(--bg-elevated); } .nav-mobile-links { padding: var(--spacing-component-md); display: flex; flex-direction: column; gap: var(--spacing-component-xs); } .nav-mobile-link { color: var(--text-secondary); text-decoration: none; padding: var(--spacing-component-md); border-radius: var(--radius-md); transition: all var(--transition-fast); } .nav-mobile-link:hover { color: var(--text-primary); background: var(--interactive-secondary); } ``` ### 3. Button Components #### Button Variants ```html ``` ```css .btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--spacing-component-xs); padding: var(--spacing-component-sm) var(--spacing-component-md); border: 1px solid transparent; border-radius: var(--radius-button); font-size: var(--text-sm); font-weight: 500; line-height: 1.5; text-decoration: none; cursor: pointer; transition: all var(--transition-fast); user-select: none; white-space: nowrap; } .btn:disabled { opacity: 0.6; cursor: not-allowed; } .btn-primary { background: var(--interactive-primary); color: var(--text-inverse); box-shadow: var(--shadow-button); } .btn-primary:hover:not(:disabled) { background: var(--interactive-primary-hover); box-shadow: var(--shadow-button-hover); } .btn-primary:active { background: var(--interactive-primary-active); } .btn-secondary { background: var(--interactive-secondary); color: var(--text-primary); border-color: var(--border-primary); } .btn-secondary:hover:not(:disabled) { background: var(--interactive-secondary-hover); border-color: var(--border-secondary); } .btn-outline { background: transparent; color: var(--interactive-primary); border-color: var(--interactive-primary); } .btn-outline:hover:not(:disabled) { background: var(--interactive-primary); color: var(--text-inverse); } .btn-ghost { background: transparent; color: var(--text-secondary); } .btn-ghost:hover:not(:disabled) { background: var(--interactive-secondary); color: var(--text-primary); } .btn-icon { display: inline-flex; align-items: center; justify-content: center; width: 2.5rem; height: 2.5rem; padding: 0; border: none; border-radius: var(--radius-button); background: transparent; color: var(--text-secondary); cursor: pointer; transition: all var(--transition-fast); } .btn-icon:hover { background: var(--interactive-secondary); color: var(--text-primary); } /* Button Sizes */ .btn-sm { padding: var(--spacing-component-xs) var(--spacing-component-sm); font-size: var(--text-xs); } .btn-lg { padding: var(--spacing-component-md) var(--spacing-component-lg); font-size: var(--text-base); } .btn-xl { padding: var(--spacing-component-lg) var(--spacing-component-xl); font-size: var(--text-lg); } ``` ### 4. Form Components #### Input Fields ```html
Park Type
``` ```css .form-group { margin-bottom: var(--spacing-component-lg); } .form-label { display: block; font-size: var(--text-sm); font-weight: 500; color: var(--text-primary); margin-bottom: var(--spacing-component-xs); } .form-input, .form-textarea, .form-select { width: 100%; padding: var(--spacing-component-sm) var(--spacing-component-md); border: 1px solid var(--border-primary); border-radius: var(--radius-input); background: var(--bg-elevated); color: var(--text-primary); font-size: var(--text-sm); line-height: 1.5; transition: all var(--transition-fast); } .form-input:focus, .form-textarea:focus, .form-select:focus { outline: none; border-color: var(--border-focus); box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); } .form-input.error, .form-textarea.error, .form-select.error { border-color: var(--border-error); } .form-error { margin-top: var(--spacing-component-xs); font-size: var(--text-xs); color: var(--color-error-600); } .form-textarea { resize: vertical; min-height: 100px; } .form-checkbox, .form-radio { display: flex; align-items: flex-start; gap: var(--spacing-component-sm); cursor: pointer; } .form-checkbox-input, .form-radio-input { margin: 0; width: 1rem; height: 1rem; flex-shrink: 0; } .form-checkbox-label, .form-radio-label { font-size: var(--text-sm); color: var(--text-primary); line-height: 1.5; } .form-fieldset { border: none; padding: 0; margin: 0; } .form-legend { font-size: var(--text-sm); font-weight: 500; color: var(--text-primary); margin-bottom: var(--spacing-component-sm); } .form-radio-group { display: flex; flex-direction: column; gap: var(--spacing-component-sm); } @media (min-width: 640px) { .form-radio-group { flex-direction: row; gap: var(--spacing-component-lg); } } ``` ### 5. Card Components #### Basic Card ```html

Card Title

Card subtitle

Card content goes here.

{{ park.name }}
{{ park.get_status_display }}

{{ park.name }}

{{ park.description|truncatewords:20 }}

{{ park.location }} {{ park.rides.count }} rides
``` ```css .card { background: var(--bg-elevated); border: 1px solid var(--border-primary); border-radius: var(--radius-card); box-shadow: var(--shadow-card); overflow: hidden; transition: all var(--transition-fast); } .card-hover:hover { box-shadow: var(--shadow-card-hover); transform: translateY(-2px); } .card-header { padding: var(--spacing-component-lg); border-bottom: 1px solid var(--border-primary); } .card-title { font-size: var(--text-lg); font-weight: 600; color: var(--text-primary); margin: 0 0 var(--spacing-component-xs) 0; } .card-subtitle { font-size: var(--text-sm); color: var(--text-secondary); margin: 0; } .card-title-link { color: inherit; text-decoration: none; transition: color var(--transition-fast); } .card-title-link:hover { color: var(--text-brand); } .card-body { padding: var(--spacing-component-lg); } .card-text { color: var(--text-secondary); line-height: var(--leading-relaxed); margin: 0 0 var(--spacing-component-md) 0; } .card-footer { padding: var(--spacing-component-lg); border-top: 1px solid var(--border-primary); background: var(--bg-secondary); } .card-image { position: relative; aspect-ratio: 16 / 9; overflow: hidden; } .card-image-img { width: 100%; height: 100%; object-fit: cover; transition: transform var(--transition-slow); } .card-hover:hover .card-image-img { transform: scale(1.05); } .card-image-overlay { position: absolute; top: var(--spacing-component-sm); right: var(--spacing-component-sm); } .card-meta { display: flex; flex-wrap: wrap; gap: var(--spacing-component-md); margin-top: var(--spacing-component-md); } .card-meta-item { display: flex; align-items: center; gap: var(--spacing-component-xs); font-size: var(--text-xs); color: var(--text-tertiary); } ``` ### 6. Modal Components #### Modal Structure ```html ``` ```css .modal-overlay { position: fixed; inset: 0; background: var(--bg-overlay); z-index: var(--z-modal); display: flex; align-items: center; justify-content: center; padding: var(--spacing-component-md); } .modal-container { width: 100%; max-width: 32rem; max-height: 90vh; overflow-y: auto; } .modal-content { background: var(--bg-elevated); border-radius: var(--radius-modal); box-shadow: var(--shadow-modal); overflow: hidden; } .modal-header { display: flex; align-items: center; justify-content: space-between; padding: var(--spacing-component-lg); border-bottom: 1px solid var(--border-primary); } .modal-title { font-size: var(--text-xl); font-weight: 600; color: var(--text-primary); margin: 0; } .modal-body { padding: var(--spacing-component-lg); } .modal-footer { display: flex; justify-content: flex-end; gap: var(--spacing-component-sm); padding: var(--spacing-component-lg); border-top: 1px solid var(--border-primary); background: var(--bg-secondary); } ``` ### 7. Utility Components #### Icons ```html ``` ```css .icon-xs { width: 0.75rem; height: 0.75rem; } .icon-sm { width: 1rem; height: 1rem; } .icon-md { width: 1.25rem; height: 1.25rem; } .icon-lg { width: 1.5rem; height: 1.5rem; } .icon-xl { width: 2rem; height: 2rem; } ``` #### Badges ```html Operating Seasonal Closed Under Construction Small Default Large ``` ```css .badge { display: inline-flex; align-items: center; padding: var(--spacing-component-xs) var(--spacing-component-sm); font-size: var(--text-xs); font-weight: 500; border-radius: var(--radius-badge); text-transform: uppercase; letter-spacing: var(--tracking-wide); } .badge-sm { padding: 0.125rem var(--spacing-component-xs); font-size: 0.625rem; } .badge-lg { padding: var(--spacing-component-sm) var(--spacing-component-md); font-size: var(--text-sm); } .badge-primary { background: var(--color-primary-100); color: var(--color-primary-800); } .badge-success { background: var(--color-success-100); color: var(--color-success-800); } .badge-warning { background: var(--color-warning-100); color: var(--color-warning-800); } .badge-error { background: var(--color-error-100); color: var(--color-error-800); } .badge-info { background: var(--color-info-100); color: var(--color-info-800); } .dark .badge-primary { background: var(--color-primary-900); color: var(--color-primary-200); } .dark .badge-success { background: var(--color-success-900); color: var(--color-success-200); } .dark .badge-warning { background: var(--color-warning-900); color: var(--color-warning-200); } .dark .badge-error { background: var(--color-error-900); color: var(--color-error-200); } .dark .badge-info { background: var(--color-info-900); color: var(--color-info-200); } ``` #### Avatars ```html User User User User User
JD
``` ```css .avatar-xs, .avatar-sm, .avatar-md, .avatar-lg, .avatar-xl { border-radius: var(--radius-full); object-fit: cover; flex-shrink: 0; } .avatar-xs { width: 1.5rem; height: 1.5rem; } .avatar-sm { width: 2rem; height: 2rem; } .avatar-md { width: 2.5rem; height: 2.5rem; } .avatar-lg { width: 3rem; height: 3rem; } .avatar-xl { width: 4rem; height: 4rem; } .avatar-fallback { display: flex; align-items: center; justify-content: center; background: var(--color-primary-100); color: var(--color-primary-700); font-weight: 500; font-size: 0.875rem; } .dark .avatar-fallback { background: var(--color-primary-800); color: var(--color-primary-200); } ``` ## Alpine.js Component Patterns ### Theme Toggle Component ```javascript Alpine.data('themeToggle', () => ({ isDark: localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches), init() { this.updateTheme(); }, toggle() { this.isDark = !this.isDark; this.updateTheme(); }, updateTheme() { if (this.isDark) { document.documentElement.classList.add('dark'); localStorage.setItem('theme', 'dark'); } else { document.documentElement.classList.remove('dark'); localStorage.setItem('theme', 'light'); } } })); ``` ### Modal Component ```javascript Alpine.data background: var(--color-warning-900); color: var(--color-warning-200); } .dark .badge-error { background: var(--color-error-900); color: var(--color-error-200); } .dark .badge-info { background: var(--color-info-900); color: var(--color-info-200); } ``` #### Avatars ```html User User User User User
JD
``` ```css .avatar-xs, .avatar-sm, .avatar-md, .avatar-lg, .avatar-xl { border-radius: var(--radius-full); object-fit: cover; flex-shrink: 0; } .avatar-xs { width: 1.5rem; height: 1.5rem; } .avatar-sm { width: 2rem; height: 2rem; } .avatar-md { width: 2.5rem; height: 2.5rem; } .avatar-lg { width: 3rem; height: 3rem; } .avatar-xl { width: 4rem; height: 4rem; } .avatar-fallback { display: flex; align-items: center; justify-content: center; background: var(--color-primary-100); color: var(--color-primary-700); font-weight: 500; font-size: 0.875rem; } .dark .avatar-fallback { background: var(--color-primary-800); color: var(--color-primary-200); } ``` ## Alpine.js Component Patterns ### Theme Toggle Component ```javascript Alpine.data('themeToggle', () => ({ isDark: localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches), init() { this.updateTheme(); }, toggle() { this.isDark = !this.isDark; this.updateTheme(); }, updateTheme() { if (this.isDark) { document.documentElement.classList.add('dark'); localStorage.setItem('theme', 'dark'); } else { document.documentElement.classList.remove('dark'); localStorage.setItem('theme', 'light'); } } })); ``` ### Modal Component ```javascript Alpine.data('modal', () => ({ isOpen: false, modalId: null, handleOpen(detail) { if (detail.id === this.modalId || !this.modalId) { this.isOpen = true; document.body.style.overflow = 'hidden'; } }, handleClose(detail) { if (!detail.id || detail.id === this.modalId) { this.close(); } }, close() { this.isOpen = false; document.body.style.overflow = ''; this.$dispatch('modal-closed', { id: this.modalId }); }, init() { this.modalId = this.$el.dataset.modalId || 'default'; } })); ``` ### Search Component ```javascript Alpine.data('searchComponent', () => ({ query: '', results: [], loading: false, debounceTimer: null, search() { clearTimeout(this.debounceTimer); this.debounceTimer = setTimeout(() => { if (this.query.length >= 2) { this.loading = true; // HTMX will handle the actual search request } else { this.results = []; } }, 300); }, selectResult(result) { this.$dispatch('result-selected', result); this.query = ''; this.results = []; }, clearSearch() { this.query = ''; this.results = []; } })); ``` ### Form Validation Component ```javascript Alpine.data('formValidation', () => ({ errors: {}, touched: {}, isSubmitting: false, validateField(fieldName, value, rules) { const fieldErrors = []; if (rules.required && (!value || value.trim() === '')) { fieldErrors.push('This field is required'); } if (rules.minLength && value && value.length < rules.minLength) { fieldErrors.push(`Must be at least ${rules.minLength} characters`); } if (rules.maxLength && value && value.length > rules.maxLength) { fieldErrors.push(`Must be no more than ${rules.maxLength} characters`); } if (rules.email && value && !this.isValidEmail(value)) { fieldErrors.push('Please enter a valid email address'); } this.errors[fieldName] = fieldErrors.length > 0 ? fieldErrors[0] : null; this.touched[fieldName] = true; return fieldErrors.length === 0; }, isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); }, hasError(fieldName) { return this.touched[fieldName] && this.errors[fieldName]; }, isValid() { return Object.values(this.errors).every(error => !error); } })); ``` ## Responsive Design Patterns ### Mobile-First Breakpoints ```css /* Mobile First Approach */ .responsive-grid { display: grid; grid-template-columns: 1fr; gap: var(--spacing-layout-sm); } @media (min-width: 640px) { .responsive-grid { grid-template-columns: repeat(2, 1fr); gap: var(--spacing-layout-md); } } @media (min-width: 1024px) { .responsive-grid { grid-template-columns: repeat(3, 1fr); gap: var(--spacing-layout-lg); } } @media (min-width: 1280px) { .responsive-grid { grid-template-columns: repeat(4, 1fr); } } ``` ### Container Queries (Future Enhancement) ```css /* Container Queries for Component-Level Responsiveness */ @container (min-width: 300px) { .card { display: flex; flex-direction: row; } .card-image { flex: 0 0 40%; } .card-body { flex: 1; } } ``` ## Accessibility Patterns ### Focus Management ```css /* Enhanced Focus Styles */ .focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; } /* Skip Links */ .skip-link { position: absolute; top: -40px; left: 6px; background: var(--bg-elevated); color: var(--text-primary); padding: 8px; text-decoration: none; border-radius: var(--radius-md); z-index: 1000; } .skip-link:focus { top: 6px; } ``` ### ARIA Patterns ```html
``` ## Animation Patterns ### Micro-Interactions ```css /* Button Hover Effects */ .btn { position: relative; overflow: hidden; } .btn::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); transition: left var(--duration-300) var(--ease-out); } .btn:hover::before { left: 100%; } /* Card Hover Effects */ .card-hover { transition: all var(--transition-base); } .card-hover:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); } .card-hover:hover .card-image img { transform: scale(1.05); } ``` ### Loading States ```css /* Skeleton Loading */ .skeleton { background: linear-gradient(90deg, var(--color-neutral-200) 25%, var(--color-neutral-100) 50%, var(--color-neutral-200) 75%); background-size: 200% 100%; animation: skeleton-loading 1.5s infinite; } @keyframes skeleton-loading { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } .dark .skeleton { background: linear-gradient(90deg, var(--color-neutral-700) 25%, var(--color-neutral-600) 50%, var(--color-neutral-700) 75%); background-size: 200% 100%; } /* Spinner Animation */ .spinner { width: 1rem; height: 1rem; border: 2px solid var(--color-neutral-200); border-top: 2px solid var(--interactive-primary); border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } ``` ## Implementation Guidelines ### CSS Architecture 1. **Layer Structure**: Use CSS layers for better cascade management 2. **Component Isolation**: Each component should be self-contained 3. **Token Usage**: Always use design tokens instead of hardcoded values 4. **Progressive Enhancement**: Ensure components work without JavaScript ### JavaScript Integration 1. **Alpine.js Components**: Create reusable Alpine.js data functions 2. **HTMX Integration**: Design components to work seamlessly with HTMX 3. **Event Communication**: Use Alpine.js events for component communication 4. **Performance**: Minimize DOM queries and optimize reactivity ### Testing Strategy 1. **Visual Regression**: Test components across different themes and screen sizes 2. **Accessibility**: Automated and manual accessibility testing 3. **Performance**: Monitor component rendering performance 4. **Cross-browser**: Test in all supported browsers ### Documentation Standards 1. **Component Examples**: Provide usage examples for each component 2. **Props/Attributes**: Document all available options 3. **Accessibility**: Document ARIA patterns and keyboard interactions 4. **Browser Support**: Specify supported browsers and fallbacks This component library provides a solid foundation for the ThrillWiki frontend redesign, ensuring consistency, accessibility, and maintainability across all templates and user interfaces.