Complete design token system, 15+ accessible components, light/dark mode, 12 animations — zero dependencies. 1. Custom Properties — Complete Design Token System 2. Reset & Base 3. Light Theme Tokens 4. @supports — Graceful Degradation 5. prefers-reduced-motion 6. Animated Background Scene 7. Glass Utility Classes 8. Focus Visible 9. Component: Card 10. Component: Button 11. Component: Badge & Tag 12. Component: Input 13. Component: Checkbox & Radio 14. Component: Switch 15. Component: Navigation 16. Component: Tabs + Panels 17. Component: Accordion 18. Component: Modal / Dialog 19. Component: Toast / Snackbar 20. Component: Tooltip 21. Component: Dropdown / Select 22. Component: Chip 23. Component: Avatar Group 24. Component: Breadcrumb 25. Component: Skeleton Loader 26. Component: Stepper 27. Component: Data Table 28. 12 Liquid Glass Animations 29. Page Layout & Hero 30. Theme Toggle Button 31. Responsive
Last active
June 7, 2026 15:32
-
-
Save aldoyh/f241b8a2ec2f005da55906b65f1ffe1e to your computer and use it in GitHub Desktop.
Pure CSS Glassmorphism Liquid Glass UI kit
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <div class="scene" aria-hidden="true"> | |
| <div class="scene__blob scene__blob--1"></div> | |
| <div class="scene__blob scene__blob--2"></div> | |
| <div class="scene__blob scene__blob--3"></div> | |
| </div> | |
| <!-- Theme toggle --> | |
| <button class="glass glass-btn theme-toggle-btn" id="theme-toggle" aria-label="Toggle colour scheme" title="Toggle light / dark mode"> | |
| <span class="icon-dark" aria-hidden="true">☀️</span> | |
| <span class="icon-light" aria-hidden="true">🌙</span> | |
| </button> | |
| <!-- Toast region (ARIA live) --> | |
| <div class="glass-toast-region" id="toast-region" role="region" aria-label="Notifications" aria-live="polite"></div> | |
| <main class="page"> | |
| <!-- ─── HERO ─────────────────────────────────────────────────────────── --> | |
| <header class="hero"> | |
| <span class="hero__kicker">Pure CSS · Glassmorphism · Liquid Glass</span> | |
| <h1 class="hero__title">Liquid Glass<br>UI Kit</h1> | |
| <p class="hero__sub"> | |
| Complete design token system, 15+ accessible components, light/dark mode, 12 animations — zero dependencies. | |
| </p> | |
| <div class="hero__cta"> | |
| <a href="#components" class="glass glass-btn glass-btn--primary">Explore Components →</a> | |
| <a href="#animations" class="glass glass-btn glass-btn--ghost">View Animations</a> | |
| </div> | |
| <div class="container stats"> | |
| <div class="glass stats__item"> | |
| <div class="stats__num">15+</div> | |
| <div class="stats__desc">Components</div> | |
| </div> | |
| <div class="glass stats__item"> | |
| <div class="stats__num">12</div> | |
| <div class="stats__desc">Animations</div> | |
| </div> | |
| <div class="glass stats__item"> | |
| <div class="stats__num">a11y</div> | |
| <div class="stats__desc">WCAG Compliant</div> | |
| </div> | |
| </div> | |
| </header> | |
| <div class="container page" id="components"> | |
| <!-- ─── CARDS ──────────────────────────────────────────────────────── --> | |
| <section class="section"> | |
| <div class="section__header"> | |
| <h2 class="section__title">Glass Cards</h2> | |
| <span class="section__subtitle">Hover to interact</span> | |
| </div> | |
| <div class="card-grid"> | |
| <div class="glass glass-card"> | |
| <div class="glass-card__label">Component · 01</div> | |
| <h3 class="glass-card__title">Frosted Surface</h3> | |
| <p class="glass-card__body">Backdrop-filter blur creates depth without obscuring the vibrant background.</p> | |
| <div style="margin-top:20px"><span class="glass-badge glass-badge--aqua"><span class="glass-badge__dot"></span> Live</span></div> | |
| </div> | |
| <div class="glass glass--dark glass-card"> | |
| <div class="glass-card__label">Component · 02</div> | |
| <h3 class="glass-card__title">Dark Glass</h3> | |
| <p class="glass-card__body">Darker tint for contrast-rich surfaces. Ideal over bright backgrounds.</p> | |
| <div style="margin-top:20px"><span class="glass-badge glass-badge--violet"><span class="glass-badge__dot"></span> Premium</span></div> | |
| </div> | |
| <div class="glass glass-card profile-card"> | |
| <div class="profile-card__avatar">🌊</div> | |
| <div class="profile-card__name">Lyra Voss</div> | |
| <div class="profile-card__role">UI Designer · Berlin</div> | |
| <div class="progress-wrap" style="width:100%"> | |
| <div class="progress-label"><span>Projects</span><span>87%</span></div> | |
| <div class="progress-bar"> | |
| <div class="progress-bar__fill" style="width:87%"></div> | |
| </div> | |
| </div> | |
| <div class="progress-wrap" style="width:100%"> | |
| <div class="progress-label"><span>Clarity</span><span>94%</span></div> | |
| <div class="progress-bar"> | |
| <div class="progress-bar__fill" style="width:94%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <div class="divider"></div> | |
| <!-- ─── BUTTONS ────────────────────────────────────────────────────── --> | |
| <section class="section"> | |
| <div class="section__header"> | |
| <h2 class="section__title">Buttons</h2> | |
| <span class="section__subtitle">Glass · Ghost · Accent · Danger · Sizes</span> | |
| </div> | |
| <div style="display:flex;flex-direction:column;gap:16px"> | |
| <div class="btn-row"> | |
| <button class="glass glass-btn glass-btn--primary">Primary</button> | |
| <button class="glass glass-btn glass-btn--ghost">Ghost</button> | |
| <button class="glass glass-btn glass-btn--accent">Accent</button> | |
| <button class="glass glass-btn glass-btn--danger">Danger</button> | |
| <button class="glass glass-btn glass-btn--ghost" disabled>Disabled</button> | |
| </div> | |
| <div class="btn-row"> | |
| <button class="glass glass-btn glass-btn--primary glass-btn--sm">Small</button> | |
| <button class="glass glass-btn glass-btn--primary">Default</button> | |
| <button class="glass glass-btn glass-btn--primary glass-btn--lg">Large</button> | |
| </div> | |
| <div class="btn-row"> | |
| <div class="glass-tooltip-wrap"> | |
| <button class="glass glass-btn glass-btn--icon glass-btn--primary" aria-label="Favourite">✦</button> | |
| <div class="glass glass-tooltip" role="tooltip">Favourite</div> | |
| </div> | |
| <div class="glass-tooltip-wrap"> | |
| <button class="glass glass-btn glass-btn--icon glass-btn--ghost" aria-label="Refresh">⟳</button> | |
| <div class="glass glass-tooltip" role="tooltip">Refresh</div> | |
| </div> | |
| <div class="glass-tooltip-wrap"> | |
| <button class="glass glass-btn glass-btn--icon glass-btn--accent" aria-label="Upload">⬆</button> | |
| <div class="glass glass-tooltip" role="tooltip">Upload</div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <div class="divider"></div> | |
| <!-- ─── NAVIGATION + TABS ──────────────────────────────────────────── --> | |
| <section class="section"> | |
| <div class="section__header"> | |
| <h2 class="section__title">Navigation & Tabs</h2> | |
| <span class="section__subtitle">Pill nav · Tab panels · Toggle</span> | |
| </div> | |
| <div style="display:flex;flex-direction:column;gap:var(--space-xl)"> | |
| <nav class="glass glass-nav" aria-label="Main navigation"> | |
| <span class="glass-nav__item glass-nav__item--active" tabindex="0">Discover</span> | |
| <span class="glass-nav__item" tabindex="0">Trending</span> | |
| <span class="glass-nav__item" tabindex="0">Collections</span> | |
| <span class="glass-nav__item" tabindex="0">Profile</span> | |
| </nav> | |
| <!-- Tabs with real content panels --> | |
| <div class="glass-tabs" id="demo-tabs"> | |
| <div class="glass glass-tab-list" role="tablist" aria-label="Demo tabs"> | |
| <button class="glass-tab" role="tab" aria-selected="true" aria-controls="tab-panel-1" id="tab-1">Overview</button> | |
| <button class="glass-tab" role="tab" aria-selected="false" aria-controls="tab-panel-2" id="tab-2">Specs</button> | |
| <button class="glass-tab" role="tab" aria-selected="false" aria-controls="tab-panel-3" id="tab-3">Examples</button> | |
| </div> | |
| <div id="tab-panel-1" class="glass glass-tab-panel is-active glass-card" role="tabpanel" aria-labelledby="tab-1"> | |
| <h3 class="glass-card__title">Overview</h3> | |
| <p class="glass-card__body">Tab panels switch content without page navigation. Each panel is accessible via keyboard and ARIA roles, with an animated reveal on activation.</p> | |
| </div> | |
| <div id="tab-panel-2" class="glass glass-tab-panel glass-card" role="tabpanel" aria-labelledby="tab-2"> | |
| <h3 class="glass-card__title">Technical Specs</h3> | |
| <p class="glass-card__body">Built on CSS custom properties with no JavaScript dependencies for pure display. The JS layer handles ARIA state updates and keyboard navigation (Arrow keys, Home, End).</p> | |
| </div> | |
| <div id="tab-panel-3" class="glass glass-tab-panel glass-card" role="tabpanel" aria-labelledby="tab-3"> | |
| <h3 class="glass-card__title">Example Panels</h3> | |
| <p class="glass-card__body">Any content can live inside a tab panel — forms, data tables, cards, or rich media. The glass surface adapts to whatever's inside.</p> | |
| </div> | |
| </div> | |
| <div class="glass glass-toggle" role="group" aria-label="View toggle"> | |
| <span class="glass-toggle__opt is-on" tabindex="0">Grid</span> | |
| <span class="glass-toggle__opt" tabindex="0">List</span> | |
| <span class="glass-toggle__opt" tabindex="0">Map</span> | |
| </div> | |
| </div> | |
| </section> | |
| <div class="divider"></div> | |
| <!-- ─── DROPDOWN ──────────────────────────────────────────────────── --> | |
| <section class="section"> | |
| <div class="section__header"> | |
| <h2 class="section__title">Dropdown</h2> | |
| <span class="section__subtitle">Custom menu · Keyboard accessible</span> | |
| </div> | |
| <div style="display:flex;flex-wrap:wrap;gap:var(--space-md);align-items:flex-start"> | |
| <div class="glass-dropdown" id="dd-actions"> | |
| <button class="glass glass-btn glass-btn--ghost glass-dropdown-trigger" aria-haspopup="true" aria-expanded="false"> | |
| Actions <span class="glass-dropdown-trigger-arrow">▾</span> | |
| </button> | |
| <div class="glass glass-dropdown-menu" role="menu"> | |
| <button class="glass-dropdown-item" role="menuitem">✏️ Edit</button> | |
| <button class="glass-dropdown-item" role="menuitem">📋 Duplicate</button> | |
| <button class="glass-dropdown-item" role="menuitem">🔗 Share</button> | |
| <div class="glass-dropdown-divider"></div> | |
| <button class="glass-dropdown-item glass-dropdown-item--danger" role="menuitem">🗑 Delete</button> | |
| </div> | |
| </div> | |
| <div class="glass-dropdown" id="dd-sort"> | |
| <button class="glass glass-btn glass-btn--primary glass-dropdown-trigger" aria-haspopup="true" aria-expanded="false"> | |
| Sort by: Name <span class="glass-dropdown-trigger-arrow">▾</span> | |
| </button> | |
| <div class="glass glass-dropdown-menu" role="menu"> | |
| <button class="glass-dropdown-item is-active" role="menuitem">Name ↑</button> | |
| <button class="glass-dropdown-item" role="menuitem">Name ↓</button> | |
| <button class="glass-dropdown-item" role="menuitem">Date modified</button> | |
| <button class="glass-dropdown-item" role="menuitem">File size</button> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <div class="divider"></div> | |
| <!-- ─── FORM CONTROLS ─────────────────────────────────────────────── --> | |
| <section class="section"> | |
| <div class="section__header"> | |
| <h2 class="section__title">Form Controls</h2> | |
| <span class="section__subtitle">Checkbox · Radio · Switch · Select · Input</span> | |
| </div> | |
| <div class="glass glass-form"> | |
| <div class="glass-form__row"> | |
| <div> | |
| <label>First Name</label> | |
| <div class="glass-input-wrap"> | |
| <input class="glass-input" placeholder="Lyra" aria-label="First name"> | |
| </div> | |
| </div> | |
| <div> | |
| <label>Last Name</label> | |
| <div class="glass-input-wrap"> | |
| <input class="glass-input" placeholder="Voss" aria-label="Last name"> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="glass-form__row glass-form__row--full"> | |
| <div> | |
| <label>Email <span style="color:#f87171">*</span></label> | |
| <div class="glass-input-wrap"> | |
| <input class="glass-input" type="email" placeholder="lyra@cosmos.io" aria-label="Email" required> | |
| <span class="glass-input-icon">✉</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="glass-form__row glass-form__row--full"> | |
| <div> | |
| <label>Role</label> | |
| <select class="glass-select" aria-label="Select role"> | |
| <option value="">Choose a role…</option> | |
| <option value="design">UI Designer</option> | |
| <option value="eng">Frontend Engineer</option> | |
| <option value="pm">Product Manager</option> | |
| <option value="motion">Motion Designer</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="glass-form__row glass-form__row--full"> | |
| <div> | |
| <label>Message</label> | |
| <textarea class="glass-textarea" placeholder="Write something…" aria-label="Message"></textarea> | |
| </div> | |
| </div> | |
| <div class="glass-form__row"> | |
| <div> | |
| <label style="margin-bottom:var(--space-sm)">Interests</label> | |
| <div class="glass-check-group"> | |
| <label class="glass-check-label"> | |
| <input type="checkbox" checked aria-label="Design"> | |
| <span class="glass-check-box"></span> | |
| Design | |
| </label> | |
| <label class="glass-check-label"> | |
| <input type="checkbox" aria-label="Motion"> | |
| <span class="glass-check-box"></span> | |
| Motion | |
| </label> | |
| <label class="glass-check-label"> | |
| <input type="checkbox" checked aria-label="Frontend"> | |
| <span class="glass-check-box"></span> | |
| Frontend | |
| </label> | |
| </div> | |
| </div> | |
| <div> | |
| <label style="margin-bottom:var(--space-sm)">Preferred contact</label> | |
| <div class="glass-check-group"> | |
| <label class="glass-check-label"> | |
| <input type="radio" name="contact" value="email" checked aria-label="Email"> | |
| <span class="glass-radio-box"></span> | |
| </label> | |
| <label class="glass-check-label"> | |
| <input type="radio" name="contact" value="slack" aria-label="Slack"> | |
| <span class="glass-radio-box"></span> | |
| Slack | |
| </label> | |
| <label class="glass-check-label"> | |
| <input type="radio" name="contact" value="phone" aria-label="Phone"> | |
| <span class="glass-radio-box"></span> | |
| Phone | |
| </label> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="glass-form__row"> | |
| <div> | |
| <label style="margin-bottom:var(--space-sm)">Preferences</label> | |
| <div style="display:flex;flex-direction:column;gap:var(--space-sm)"> | |
| <label class="glass-switch-label"> | |
| <input type="checkbox" checked aria-label="Email notifications"> | |
| <span class="glass-switch-track"><span class="glass-switch-thumb"></span></span> | |
| Email notifications | |
| </label> | |
| <label class="glass-switch-label"> | |
| <input type="checkbox" aria-label="Dark mode"> | |
| <span class="glass-switch-track"><span class="glass-switch-thumb"></span></span> | |
| Dark mode | |
| </label> | |
| <label class="glass-switch-label"> | |
| <input type="checkbox" checked aria-label="Analytics"> | |
| <span class="glass-switch-track"><span class="glass-switch-thumb"></span></span> | |
| Analytics | |
| </label> | |
| </div> | |
| </div> | |
| <div> | |
| <label>Blur intensity <span id="blur-val">50</span>%</label> | |
| <input class="glass-range" type="range" min="0" max="100" value="50" aria-label="Blur intensity" oninput="document.getElementById('blur-val').textContent=this.value"> | |
| </div> | |
| </div> | |
| <button class="glass glass-btn glass-btn--primary" style="width:100%;justify-content:center" type="submit"> | |
| Submit Form | |
| </button> | |
| </div> | |
| </section> | |
| <div class="divider"></div> | |
| <!-- ─── ACCORDION ─────────────────────────────────────────────────── --> | |
| <section class="section"> | |
| <div class="section__header"> | |
| <h2 class="section__title">Accordion</h2> | |
| <span class="section__subtitle">Click to expand</span> | |
| </div> | |
| <div class="glass-accordion" id="accordion"> | |
| <div class="glass glass-accordion-item is-open"> | |
| <button class="glass-accordion-trigger" aria-expanded="true" aria-controls="acc-1"> | |
| What is Liquid Glass? | |
| <span class="glass-accordion-icon" aria-hidden="true">▾</span> | |
| </button> | |
| <div class="glass-accordion-body" id="acc-1" role="region"> | |
| <div class="glass-accordion-content"> | |
| Liquid Glass is a CSS design language built around backdrop-filter blur, translucent surfaces, and reflective gradients — creating the illusion of physical depth on digital screens. | |
| </div> | |
| </div> | |
| </div> | |
| <div class="glass glass-accordion-item"> | |
| <button class="glass-accordion-trigger" aria-expanded="false" aria-controls="acc-2"> | |
| Browser support? | |
| <span class="glass-accordion-icon" aria-hidden="true">▾</span> | |
| </button> | |
| <div class="glass-accordion-body" id="acc-2" role="region"> | |
| <div class="glass-accordion-content"> | |
| backdrop-filter is supported in all modern browsers. A <code style="opacity:.8">@supports</code> fallback is included for older Firefox, providing a semi-transparent solid surface instead of blur. | |
| </div> | |
| </div> | |
| </div> | |
| <div class="glass glass-accordion-item"> | |
| <button class="glass-accordion-trigger" aria-expanded="false" aria-controls="acc-3"> | |
| How do I theme it? | |
| <span class="glass-accordion-icon" aria-hidden="true">▾</span> | |
| </button> | |
| <div class="glass-accordion-body" id="acc-3" role="region"> | |
| <div class="glass-accordion-content"> | |
| All values are CSS custom properties. Toggle <code style="opacity:.8">data-theme="light"</code> on the <code style="opacity:.8"><html></code> element to switch the entire palette instantly. Override individual tokens in a local scope for component-level theming. | |
| </div> | |
| </div> | |
| </div> | |
| <div class="glass glass-accordion-item"> | |
| <button class="glass-accordion-trigger" aria-expanded="false" aria-controls="acc-4"> | |
| Is it accessible? | |
| <span class="glass-accordion-icon" aria-hidden="true">▾</span> | |
| </button> | |
| <div class="glass-accordion-body" id="acc-4" role="region"> | |
| <div class="glass-accordion-content"> | |
| Yes. All interactive elements have ARIA roles, keyboard support, and visible :focus-visible outlines. prefers-reduced-motion disables all animations. Colour contrast meets WCAG AA on both themes. | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <div class="divider"></div> | |
| <!-- ─── STEPPER ────────────────────────────────────────────────────── --> | |
| <section class="section"> | |
| <div class="section__header"> | |
| <h2 class="section__title">Stepper</h2> | |
| <span class="section__subtitle">Multi-step flow · Horizontal & vertical</span> | |
| </div> | |
| <div style="display:flex;flex-direction:column;gap:var(--space-2xl)"> | |
| <div> | |
| <p class="section__subtitle" style="margin-bottom:var(--space-md)">Horizontal stepper</p> | |
| <div class="glass-stepper" id="stepper-h"> | |
| <div class="glass-step is-complete"> | |
| <div class="glass-step__inner" style="display:flex;align-items:center;gap:8px"> | |
| <div class="glass-step__node" aria-label="Step 1: Account — complete">✓</div> | |
| <span class="glass-step__label">Account</span> | |
| </div> | |
| <div class="glass-step__connector"></div> | |
| </div> | |
| <div class="glass-step is-complete"> | |
| <div class="glass-step__inner" style="display:flex;align-items:center;gap:8px"> | |
| <div class="glass-step__node" aria-label="Step 2: Profile — complete">✓</div> | |
| <span class="glass-step__label">Profile</span> | |
| </div> | |
| <div class="glass-step__connector"></div> | |
| </div> | |
| <div class="glass-step is-active"> | |
| <div class="glass-step__inner" style="display:flex;align-items:center;gap:8px"> | |
| <div class="glass-step__node" aria-label="Step 3: Billing — current" aria-current="step">3</div> | |
| <span class="glass-step__label">Billing</span> | |
| </div> | |
| <div class="glass-step__connector"></div> | |
| </div> | |
| <div class="glass-step"> | |
| <div class="glass-step__inner" style="display:flex;align-items:center;gap:8px"> | |
| <div class="glass-step__node" aria-label="Step 4: Review — pending">4</div> | |
| <span class="glass-step__label">Review</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="btn-row" style="margin-top:var(--space-md)"> | |
| <button class="glass glass-btn glass-btn--ghost glass-btn--sm" id="stepper-prev">← Prev</button> | |
| <button class="glass glass-btn glass-btn--primary glass-btn--sm" id="stepper-next">Next →</button> | |
| </div> | |
| </div> | |
| <div> | |
| <p class="section__subtitle" style="margin-bottom:var(--space-md)">Vertical stepper</p> | |
| <div class="glass-stepper glass-stepper--vertical"> | |
| <div class="glass-step is-complete"> | |
| <div class="glass-step__inner"> | |
| <div class="glass-step__node" aria-label="Step 1 — complete">✓</div> | |
| <span class="glass-step__label">Create account</span> | |
| </div> | |
| <div class="glass-step__connector"></div> | |
| </div> | |
| <div class="glass-step is-active"> | |
| <div class="glass-step__inner"> | |
| <div class="glass-step__node" aria-label="Step 2 — current" aria-current="step">2</div> | |
| <span class="glass-step__label">Verify email</span> | |
| </div> | |
| <div class="glass-step__connector"></div> | |
| </div> | |
| <div class="glass-step"> | |
| <div class="glass-step__inner"> | |
| <div class="glass-step__node" aria-label="Step 3 — pending">3</div> | |
| <span class="glass-step__label">Set up workspace</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <div class="divider"></div> | |
| <!-- ─── DATA TABLE ─────────────────────────────────────────────────── --> | |
| <section class="section"> | |
| <div class="section__header"> | |
| <h2 class="section__title">Data Table</h2> | |
| <span class="section__subtitle">Sortable · Hover rows · Status badges</span> | |
| </div> | |
| <div class="glass glass-table-wrap" id="data-table-wrap"> | |
| <!-- Toolbar --> | |
| <div class="glass-table-toolbar"> | |
| <span class="glass-table-toolbar__title">Team Members</span> | |
| <span class="glass-table-toolbar__count" id="tbl-count">5 members</span> | |
| <div class="glass-table-search"> | |
| <span class="glass-table-search__icon">🔍</span> | |
| <input class="glass-table-search__input" type="search" placeholder="Search members…" id="tbl-search" aria-label="Search members"> | |
| </div> | |
| <button class="glass glass-btn glass-btn--ghost glass-btn--sm" id="tbl-add-btn" style="white-space:nowrap">+ Invite</button> | |
| </div> | |
| <!-- Bulk action bar --> | |
| <div class="glass-table-bulk" id="tbl-bulk" role="status" aria-live="polite"> | |
| <span class="glass-table-bulk__count" id="tbl-bulk-count">0 selected</span> | |
| <button class="glass glass-btn glass-btn--ghost glass-btn--sm">📋 Export</button> | |
| <button class="glass glass-btn glass-btn--danger glass-btn--sm">🗑 Remove</button> | |
| <button class="glass glass-btn glass-btn--ghost glass-btn--sm" id="tbl-deselect" style="margin-left:auto">✕ Clear</button> | |
| </div> | |
| <!-- Table --> | |
| <table class="glass-table" id="glass-table" aria-label="Team members"> | |
| <thead> | |
| <tr> | |
| <th class="col-check" scope="col"> | |
| <input type="checkbox" class="glass-table-check" id="tbl-check-all" aria-label="Select all rows"> | |
| </th> | |
| <th scope="col" data-col="name" class="is-sorted"> | |
| Member <span class="glass-table__sort-icon">↑</span> | |
| </th> | |
| <th scope="col" data-col="role">Role</th> | |
| <th scope="col" data-col="status">Status</th> | |
| <th scope="col" data-col="projects" class="glass-table__cell--num"> | |
| Projects <span class="glass-table__sort-icon">↕</span> | |
| </th> | |
| <th scope="col" data-col="score" class="glass-table__cell--num"> | |
| Score <span class="glass-table__sort-icon">↕</span> | |
| </th> | |
| <th scope="col" style="width:48px"></th> | |
| </tr> | |
| </thead> | |
| <tbody id="tbl-body"> | |
| <tr data-id="1"> | |
| <td class="col-check" data-label="Select"> | |
| <input type="checkbox" class="glass-table-check" aria-label="Select Lyra Voss"> | |
| </td> | |
| <td data-label="Member"> | |
| <div class="glass-table-member"> | |
| <div class="glass-table-avatar" style="background:linear-gradient(135deg,#e8a87c,#d4845a)">👩</div> | |
| <div class="glass-table-member__info"> | |
| <span class="glass-table-member__name">Lyra Voss</span> | |
| <span class="glass-table-member__email">lyra@cosmos.io</span> | |
| </div> | |
| </div> | |
| </td> | |
| <td data-label="Role">UI Designer</td> | |
| <td data-label="Status"><span class="glass-badge glass-badge--lime"><span class="glass-badge__dot"></span>Online</span></td> | |
| <td class="glass-table__cell--num" data-label="Projects">12</td> | |
| <td class="glass-table__cell--num" data-label="Score"> | |
| <div class="glass-table-score"> | |
| <div class="glass-table-score__bar"> | |
| <div class="glass-table-score__fill" style="width:97%"></div> | |
| </div> | |
| <span class="glass-table-score__num">97</span> | |
| </div> | |
| </td> | |
| <td> | |
| <button class="glass-table-action" aria-label="Actions for Lyra Voss">⋯</button> | |
| </td> | |
| </tr> | |
| <tr data-id="2"> | |
| <td class="col-check" data-label="Select"> | |
| <input type="checkbox" class="glass-table-check" aria-label="Select Marc Dubois"> | |
| </td> | |
| <td data-label="Member"> | |
| <div class="glass-table-member"> | |
| <div class="glass-table-avatar" style="background:linear-gradient(135deg,#6baed6,#3182bd)">👨💻</div> | |
| <div class="glass-table-member__info"> | |
| <span class="glass-table-member__name">Marc Dubois</span> | |
| <span class="glass-table-member__email">marc@cosmos.io</span> | |
| </div> | |
| </div> | |
| </td> | |
| <td data-label="Role">Frontend Eng</td> | |
| <td data-label="Status"><span class="glass-badge glass-badge--lime"><span class="glass-badge__dot"></span>Online</span></td> | |
| <td class="glass-table__cell--num" data-label="Projects">8</td> | |
| <td class="glass-table__cell--num" data-label="Score"> | |
| <div class="glass-table-score"> | |
| <div class="glass-table-score__bar"> | |
| <div class="glass-table-score__fill" style="width:89%"></div> | |
| </div> | |
| <span class="glass-table-score__num">89</span> | |
| </div> | |
| </td> | |
| <td> | |
| <button class="glass-table-action" aria-label="Actions for Marc Dubois">⋯</button> | |
| </td> | |
| </tr> | |
| <tr data-id="3"> | |
| <td class="col-check" data-label="Select"> | |
| <input type="checkbox" class="glass-table-check" aria-label="Select Sara Kim"> | |
| </td> | |
| <td data-label="Member"> | |
| <div class="glass-table-member"> | |
| <div class="glass-table-avatar" style="background:linear-gradient(135deg,#f7b7d3,#e06fa4)">👩🎨</div> | |
| <div class="glass-table-member__info"> | |
| <span class="glass-table-member__name">Sara Kim</span> | |
| <span class="glass-table-member__email">sara@cosmos.io</span> | |
| </div> | |
| </div> | |
| </td> | |
| <td data-label="Role">Motion</td> | |
| <td data-label="Status"><span class="glass-badge glass-badge--amber">Away</span></td> | |
| <td class="glass-table__cell--num" data-label="Projects">5</td> | |
| <td class="glass-table__cell--num" data-label="Score"> | |
| <div class="glass-table-score"> | |
| <div class="glass-table-score__bar"> | |
| <div class="glass-table-score__fill" style="width:84%"></div> | |
| </div> | |
| <span class="glass-table-score__num">84</span> | |
| </div> | |
| </td> | |
| <td> | |
| <button class="glass-table-action" aria-label="Actions for Sara Kim">⋯</button> | |
| </td> | |
| </tr> | |
| <tr data-id="4"> | |
| <td class="col-check" data-label="Select"> | |
| <input type="checkbox" class="glass-table-check" aria-label="Select Omar Reyes"> | |
| </td> | |
| <td data-label="Member"> | |
| <div class="glass-table-member"> | |
| <div class="glass-table-avatar" style="background:linear-gradient(135deg,#9ecb8a,#4c9e3f)">👨🔬</div> | |
| <div class="glass-table-member__info"> | |
| <span class="glass-table-member__name">Omar Reyes</span> | |
| <span class="glass-table-member__email">omar@cosmos.io</span> | |
| </div> | |
| </div> | |
| </td> | |
| <td data-label="Role">Backend Eng</td> | |
| <td data-label="Status"><span class="glass-badge glass-badge--violet">Offline</span></td> | |
| <td class="glass-table__cell--num" data-label="Projects">9</td> | |
| <td class="glass-table__cell--num" data-label="Score"> | |
| <div class="glass-table-score"> | |
| <div class="glass-table-score__bar"> | |
| <div class="glass-table-score__fill" style="width:91%"></div> | |
| </div> | |
| <span class="glass-table-score__num">91</span> | |
| </div> | |
| </td> | |
| <td> | |
| <button class="glass-table-action" aria-label="Actions for Omar Reyes">⋯</button> | |
| </td> | |
| </tr> | |
| <tr data-id="5"> | |
| <td class="col-check" data-label="Select"> | |
| <input type="checkbox" class="glass-table-check" aria-label="Select Ines Hartmann"> | |
| </td> | |
| <td data-label="Member"> | |
| <div class="glass-table-member"> | |
| <div class="glass-table-avatar" style="background:linear-gradient(135deg,#c5b0e8,#8a63cc)">👩💼</div> | |
| <div class="glass-table-member__info"> | |
| <span class="glass-table-member__name">Ines Hartmann</span> | |
| <span class="glass-table-member__email">ines@cosmos.io</span> | |
| </div> | |
| </div> | |
| </td> | |
| <td data-label="Role">Product</td> | |
| <td data-label="Status"><span class="glass-badge glass-badge--lime"><span class="glass-badge__dot"></span>Online</span></td> | |
| <td class="glass-table__cell--num" data-label="Projects">14</td> | |
| <td class="glass-table__cell--num" data-label="Score"> | |
| <div class="glass-table-score"> | |
| <div class="glass-table-score__bar"> | |
| <div class="glass-table-score__fill" style="width:99%"></div> | |
| </div> | |
| <span class="glass-table-score__num">99</span> | |
| </div> | |
| </td> | |
| <td> | |
| <button class="glass-table-action" aria-label="Actions for Ines Hartmann">⋯</button> | |
| </td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <!-- Pagination --> | |
| <div class="glass-table-pagination"> | |
| <span class="glass-table-pagination__info" id="tbl-page-info">Showing 1–5 of 5</span> | |
| <div class="glass-table-pagination__controls"> | |
| <button class="glass-table-page-btn" id="tbl-prev" aria-label="Previous page" disabled>‹</button> | |
| <button class="glass-table-page-btn is-active" aria-label="Page 1" aria-current="page">1</button> | |
| <button class="glass-table-page-btn" aria-label="Page 2">2</button> | |
| <button class="glass-table-page-btn" aria-label="Page 3">3</button> | |
| <button class="glass-table-page-btn" id="tbl-next" aria-label="Next page">›</button> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <div class="divider"></div> | |
| <!-- ─── TOAST ─────────────────────────────────────────────────────── --> | |
| <section class="section"> | |
| <div class="section__header"> | |
| <h2 class="section__title">Toast / Snackbar</h2> | |
| <span class="section__subtitle">Animated entry · Auto-dismiss · Click to close</span> | |
| </div> | |
| <div class="toast-demo-row"> | |
| <button class="glass glass-btn glass-btn--ghost" data-toast="success" data-toast-title="Saved!" data-toast-desc="Your changes have been saved.">✓ Success</button> | |
| <button class="glass glass-btn glass-btn--ghost" data-toast="error" data-toast-title="Error" data-toast-desc="Something went wrong. Try again.">✕ Error</button> | |
| <button class="glass glass-btn glass-btn--ghost" data-toast="warning" data-toast-title="Warning" data-toast-desc="You're running low on storage.">⚠ Warning</button> | |
| <button class="glass glass-btn glass-btn--ghost" data-toast="info" data-toast-title="New update" data-toast-desc="v2.1.0 is available now.">ℹ Info</button> | |
| </div> | |
| </section> | |
| <div class="divider"></div> | |
| <!-- ─── BADGES & TAGS ──────────────────────────────────────────────── --> | |
| <section class="section"> | |
| <div class="section__header"> | |
| <h2 class="section__title">Badges & Tags</h2> | |
| <span class="section__subtitle">All colour variants</span> | |
| </div> | |
| <div style="display:flex;flex-direction:column;gap:var(--space-md)"> | |
| <div class="btn-row"> | |
| <span class="glass-badge glass-badge--aqua"><span class="glass-badge__dot"></span>Active</span> | |
| <span class="glass-badge glass-badge--violet"><span class="glass-badge__dot"></span>Premium</span> | |
| <span class="glass-badge glass-badge--rose">New</span> | |
| <span class="glass-badge glass-badge--amber">Beta</span> | |
| <span class="glass-badge glass-badge--lime"><span class="glass-badge__dot"></span>Online</span> | |
| <span class="glass-badge glass-badge--error">Error</span> | |
| </div> | |
| <div class="tags-row"> | |
| <span class="glass-tag">glassmorphism</span> | |
| <span class="glass-tag">liquid glass</span> | |
| <span class="glass-tag">backdrop-filter</span> | |
| <span class="glass-tag">blur effects</span> | |
| <span class="glass-tag">CSS animations</span> | |
| <span class="glass-tag">zero deps</span> | |
| <span class="glass-tag">WCAG 2.1 AA</span> | |
| </div> | |
| </div> | |
| </section> | |
| <div class="divider"></div> | |
| <!-- ─── NOTIFICATIONS ─────────────────────────────────────────────── --> | |
| <section class="section"> | |
| <div class="section__header"> | |
| <h2 class="section__title">Notifications</h2> | |
| <span class="section__subtitle">Hover to slide</span> | |
| </div> | |
| <div> | |
| <div class="glass notif" role="alert"> | |
| <div class="notif__icon notif__icon--aqua">🌊</div> | |
| <div> | |
| <div class="notif__title">New message from Lyra</div> | |
| <div class="notif__desc">The glass effect looks stunning on mobile…</div> | |
| </div> | |
| <span class="notif__time">2m ago</span> | |
| </div> | |
| <div class="glass notif"> | |
| <div class="notif__icon notif__icon--violet">⚡</div> | |
| <div> | |
| <div class="notif__title">Build complete</div> | |
| <div class="notif__desc">UI Kit compiled successfully — 0 errors.</div> | |
| </div> | |
| <span class="notif__time">8m ago</span> | |
| </div> | |
| <div class="glass notif"> | |
| <div class="notif__icon notif__icon--amber">✦</div> | |
| <div> | |
| <div class="notif__title">New component added</div> | |
| <div class="notif__desc">glass-modal is ready. Check the docs.</div> | |
| </div> | |
| <span class="notif__time">1h ago</span> | |
| </div> | |
| </div> | |
| </section> | |
| <div class="divider"></div> | |
| <!-- ─── MODAL ─────────────────────────────────────────────────────── --> | |
| <section class="section"> | |
| <div class="section__header"> | |
| <h2 class="section__title">Modal</h2> | |
| <span class="section__subtitle">Focus trap · ARIA · Liquid entry</span> | |
| </div> | |
| <button class="glass glass-btn glass-btn--primary" id="modal-open">Open Modal →</button> | |
| </section> | |
| <div class="glass-modal-backdrop" id="modal-backdrop" role="dialog" aria-modal="true" aria-labelledby="modal-title" inert> | |
| <div class="glass-modal"> | |
| <button class="glass-modal__close" id="modal-close" aria-label="Close dialog">✕</button> | |
| <div class="glass-modal__header"> | |
| <div class="glass-card__label">Dialog · Liquid Glass</div> | |
| <h3 class="glass-card__title" id="modal-title">Frosted Overlay</h3> | |
| </div> | |
| <div class="glass-modal__body"> | |
| <p class="glass-card__body">This modal uses a dedicated opaque surface — not a transparent glass panel — so text is always readable regardless of what's behind it. An accent gradient stripe marks the top edge, and the entry uses a liquid spring curve.</p> | |
| <div style="margin-top:var(--space-md); padding:var(--space-md); border-radius:var(--radius-md); background:rgba(94,231,223,.08); border:1px solid rgba(94,231,223,.18);"> | |
| <p style="font-size:var(--text-sm); opacity:.80; line-height:1.6;">Focus is trapped inside while the modal is open. <kbd style="padding:2px 6px; border-radius:4px; font-size:.75rem; border:1px solid rgba(255,255,255,.2); background:rgba(255,255,255,.1)">Tab</kbd> cycles through buttons, <kbd style="padding:2px 6px; border-radius:4px; font-size:.75rem; border:1px solid rgba(255,255,255,.2); background:rgba(255,255,255,.1)">Esc</kbd> closes.</p> | |
| </div> | |
| </div> | |
| <div class="glass-modal__footer"> | |
| <button class="glass glass-btn glass-btn--primary" id="modal-confirm">Confirm</button> | |
| <button class="glass glass-btn glass-btn--ghost" id="modal-cancel">Cancel</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="divider"></div> | |
| <!-- ─── SKELETON ──────────────────────────────────────────────────── --> | |
| <section class="section"> | |
| <div class="section__header"> | |
| <h2 class="section__title">Skeleton Loader</h2> | |
| <span class="section__subtitle">Shimmer state for async content</span> | |
| </div> | |
| <div class="card-grid"> | |
| <!-- Skeleton card --> | |
| <div class="glass skeleton-layout"> | |
| <div class="skeleton-row"> | |
| <div class="skeleton skeleton--circle" style="width:48px;height:48px;flex-shrink:0"></div> | |
| <div class="skeleton-lines"> | |
| <div class="skeleton skeleton--text" style="width:70%"></div> | |
| <div class="skeleton skeleton--text" style="width:45%"></div> | |
| </div> | |
| </div> | |
| <div class="skeleton skeleton--text" style="width:100%"></div> | |
| <div class="skeleton skeleton--text" style="width:88%"></div> | |
| <div class="skeleton skeleton--text" style="width:65%"></div> | |
| <div class="skeleton skeleton--text" style="width:40%;height:28px;border-radius:var(--radius-full)"></div> | |
| </div> | |
| <!-- Second skeleton --> | |
| <div class="glass skeleton-layout"> | |
| <div class="skeleton skeleton--heading" style="width:60%"></div> | |
| <div class="skeleton skeleton--text" style="width:100%;height:120px;border-radius:var(--radius-md)"></div> | |
| <div class="skeleton-row" style="gap:8px"> | |
| <div class="skeleton skeleton--text" style="width:80px;height:24px;border-radius:var(--radius-full)"></div> | |
| <div class="skeleton skeleton--text" style="width:60px;height:24px;border-radius:var(--radius-full)"></div> | |
| </div> | |
| </div> | |
| <!-- Third skeleton --> | |
| <div class="glass skeleton-layout"> | |
| <div class="skeleton skeleton--text" style="width:100%;height:160px;border-radius:var(--radius-lg)"></div> | |
| <div class="skeleton-row" style="gap:8px"> | |
| <div class="skeleton skeleton--circle" style="width:32px;height:32px;flex-shrink:0"></div> | |
| <div class="skeleton skeleton--circle" style="width:32px;height:32px;flex-shrink:0"></div> | |
| <div class="skeleton skeleton--circle" style="width:32px;height:32px;flex-shrink:0"></div> | |
| </div> | |
| <div class="skeleton skeleton--text" style="width:50%"></div> | |
| </div> | |
| </div> | |
| </section> | |
| <div class="divider"></div> | |
| <!-- ─── CHIPS ─────────────────────────────────────────────────────── --> | |
| <section class="section" id="chips"> | |
| <div class="section__header"> | |
| <h2 class="section__title">Chips</h2> | |
| <span class="section__subtitle">Dismissible · Selectable · Dynamic</span> | |
| </div> | |
| <div style="display:flex;flex-direction:column;gap:var(--space-xl)"> | |
| <div> | |
| <p class="section__subtitle" style="margin-bottom:14px">Colour variants</p> | |
| <div class="glass-chip-group"> | |
| <span class="glass glass-chip glass-chip--aqua is-entering"> | |
| <span class="glass-chip__icon">🌊</span> | |
| <span class="glass-chip__label">Aqua</span> | |
| <span class="glass-chip__dismiss" role="button" aria-label="Remove Aqua">✕</span> | |
| </span> | |
| <span class="glass glass-chip glass-chip--violet is-entering" style="animation-delay:.06s"> | |
| <span class="glass-chip__icon">⚡</span> | |
| <span class="glass-chip__label">Violet</span> | |
| <span class="glass-chip__dismiss" role="button" aria-label="Remove Violet">✕</span> | |
| </span> | |
| <span class="glass glass-chip glass-chip--rose is-entering" style="animation-delay:.12s"> | |
| <span class="glass-chip__icon">🌸</span> | |
| <span class="glass-chip__label">Rose</span> | |
| <span class="glass-chip__dismiss" role="button" aria-label="Remove Rose">✕</span> | |
| </span> | |
| <span class="glass glass-chip glass-chip--amber is-entering" style="animation-delay:.18s"> | |
| <span class="glass-chip__icon">✦</span> | |
| <span class="glass-chip__label">Amber</span> | |
| <span class="glass-chip__dismiss" role="button" aria-label="Remove Amber">✕</span> | |
| </span> | |
| </div> | |
| </div> | |
| <div> | |
| <p class="section__subtitle" style="margin-bottom:14px">Selectable filter chips</p> | |
| <div class="glass-chip-group" id="filter-chips" role="group" aria-label="Filter chips"> | |
| <span class="glass glass-chip js-selectable" role="checkbox" aria-checked="false" tabindex="0" data-value="design"><span class="glass-chip__label">Design</span></span> | |
| <span class="glass glass-chip js-selectable is-selected" role="checkbox" aria-checked="true" tabindex="0" data-value="frontend"><span class="glass-chip__label">Frontend</span></span> | |
| <span class="glass glass-chip js-selectable" role="checkbox" aria-checked="false" tabindex="0" data-value="motion"><span class="glass-chip__label">Motion</span></span> | |
| <span class="glass glass-chip js-selectable" role="checkbox" aria-checked="false" tabindex="0" data-value="3d"><span class="glass-chip__label">3D</span></span> | |
| <span class="glass glass-chip js-selectable is-selected" role="checkbox" aria-checked="true" tabindex="0" data-value="glassmorphism"><span class="glass-chip__label">Glassmorphism</span></span> | |
| </div> | |
| </div> | |
| <div> | |
| <p class="section__subtitle" style="margin-bottom:14px">Dynamic — dismiss or add new</p> | |
| <div class="glass-chip-group" id="dynamic-chips" aria-label="Dynamic chips"> | |
| <span class="glass glass-chip" data-chip><span class="glass-chip__icon">🎨</span><span class="glass-chip__label">Illustrator</span><span class="glass-chip__dismiss" role="button" aria-label="Remove Illustrator">✕</span></span> | |
| <span class="glass glass-chip" data-chip><span class="glass-chip__icon">🖥</span><span class="glass-chip__label">Figma</span><span class="glass-chip__dismiss" role="button" aria-label="Remove Figma">✕</span></span> | |
| <span class="glass glass-chip" data-chip><span class="glass-chip__icon">✏️</span><span class="glass-chip__label">Sketch</span><span class="glass-chip__dismiss" role="button" aria-label="Remove Sketch">✕</span></span> | |
| <button class="glass-chip-add" id="chip-add-btn" title="Add chip" aria-label="Add chip">+</button> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <div class="divider"></div> | |
| <!-- ─── AVATAR GROUP ──────────────────────────────────────────────── --> | |
| <section class="section" id="avatars"> | |
| <div class="section__header"> | |
| <h2 class="section__title">Avatar Group</h2> | |
| <span class="section__subtitle">Photos · Initials · Status · Overflow · Sizes</span> | |
| </div> | |
| <div style="display:flex;flex-direction:column;gap:var(--space-2xl)"> | |
| <div> | |
| <p class="section__subtitle" style="margin-bottom:18px">Photo avatars — hover for name card</p> | |
| <div class="glass-avatar-group"> | |
| <div class="glass-avatar glass-avatar--photo" style="--from:#e8a87c;--to:#d4845a" tabindex="0" aria-label="Lyra Voss, UI Design, online"> | |
| <div class="glass-avatar__face">👩</div> | |
| <span class="glass-avatar__status glass-avatar__status--online"></span> | |
| <div class="glass glass-avatar-popover" aria-hidden="true">Lyra Voss<br><span style="opacity:.5;font-size:.72rem">UI Design</span></div> | |
| </div> | |
| <div class="glass-avatar glass-avatar--photo" style="--from:#6baed6;--to:#3182bd" tabindex="0" aria-label="Marc Dubois, Frontend, online"> | |
| <div class="glass-avatar__face">👨💻</div> | |
| <span class="glass-avatar__status glass-avatar__status--online"></span> | |
| <div class="glass glass-avatar-popover" aria-hidden="true">Marc Dubois<br><span style="opacity:.5;font-size:.72rem">Frontend</span></div> | |
| </div> | |
| <div class="glass-avatar glass-avatar--photo" style="--from:#f7b7d3;--to:#e06fa4" tabindex="0" aria-label="Sara Kim, Motion, away"> | |
| <div class="glass-avatar__face">👩🎨</div> | |
| <span class="glass-avatar__status glass-avatar__status--away"></span> | |
| <div class="glass glass-avatar-popover" aria-hidden="true">Sara Kim<br><span style="opacity:.5;font-size:.72rem">Motion</span></div> | |
| </div> | |
| <div class="glass-avatar glass-avatar--overflow" style="z-index:5" tabindex="0" aria-label="7 more members"> | |
| <div class="glass-avatar__face">+7</div> | |
| <div class="glass glass-avatar-popover glass-avatar-popover--list" aria-hidden="true"> | |
| <ul class="glass-avatar-popover__list"> | |
| <li class="glass-avatar-popover__item"> | |
| <div class="glass-avatar-popover__mini" style="background:linear-gradient(135deg,#5ee7df,#3b82f6)">JL</div><span>Jin Lee</span> | |
| </li> | |
| <li class="glass-avatar-popover__item"> | |
| <div class="glass-avatar-popover__mini" style="background:linear-gradient(135deg,#f7a8c4,#b490f5)">AP</div><span>Anya Park</span> | |
| </li> | |
| <li class="glass-avatar-popover__item" style="opacity:.45;font-size:.72rem;padding-left:34px">+5 more…</li> | |
| </ul> | |
| </div> | |
| </div> | |
| <span class="glass-avatar-group__meta">10 members · 3 online</span> | |
| </div> | |
| </div> | |
| <div> | |
| <p class="section__subtitle" style="margin-bottom:18px">Size variants</p> | |
| <div style="display:flex;flex-direction:column;gap:var(--space-md);align-items:flex-start"> | |
| <div class="glass-avatar-group glass-avatar-group--sm"> | |
| <div class="glass-avatar glass-avatar--photo" style="--from:#e8a87c;--to:#d4845a"> | |
| <div class="glass-avatar__face">👩</div> | |
| </div> | |
| <div class="glass-avatar glass-avatar--photo" style="--from:#6baed6;--to:#3182bd"> | |
| <div class="glass-avatar__face">👨💻</div> | |
| </div> | |
| <div class="glass-avatar glass-avatar--overflow"> | |
| <div class="glass-avatar__face">+9</div> | |
| </div> | |
| <span class="glass-avatar-group__meta">sm · 32px</span> | |
| </div> | |
| <div class="glass-avatar-group"> | |
| <div class="glass-avatar glass-avatar--photo" style="--from:#e8a87c;--to:#d4845a"> | |
| <div class="glass-avatar__face">👩</div> | |
| </div> | |
| <div class="glass-avatar glass-avatar--photo" style="--from:#6baed6;--to:#3182bd"> | |
| <div class="glass-avatar__face">👨💻</div> | |
| </div> | |
| <div class="glass-avatar glass-avatar--overflow"> | |
| <div class="glass-avatar__face">+9</div> | |
| </div> | |
| <span class="glass-avatar-group__meta">default · 44px</span> | |
| </div> | |
| <div class="glass-avatar-group glass-avatar-group--lg"> | |
| <div class="glass-avatar glass-avatar--photo" style="--from:#e8a87c;--to:#d4845a"> | |
| <div class="glass-avatar__face">👩</div> | |
| </div> | |
| <div class="glass-avatar glass-avatar--photo" style="--from:#6baed6;--to:#3182bd"> | |
| <div class="glass-avatar__face">👨💻</div> | |
| </div> | |
| <div class="glass-avatar glass-avatar--overflow"> | |
| <div class="glass-avatar__face">+9</div> | |
| </div> | |
| <span class="glass-avatar-group__meta">lg · 56px</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <div class="divider"></div> | |
| <!-- ─── BREADCRUMB ────────────────────────────────────────────────── --> | |
| <section class="section" id="breadcrumb"> | |
| <div class="section__header"> | |
| <h2 class="section__title">Breadcrumb</h2> | |
| <span class="section__subtitle">Collapsed · Icons · Sizes</span> | |
| </div> | |
| <div style="display:flex;flex-direction:column;gap:var(--space-lg);align-items:flex-start"> | |
| <nav class="glass glass-breadcrumb" aria-label="Breadcrumb"> | |
| <span class="glass-breadcrumb__crumb"><span class="glass-breadcrumb__crumb-icon">⌂</span>Home</span> | |
| <span class="glass-breadcrumb__sep" aria-hidden="true">›</span> | |
| <span class="glass-breadcrumb__crumb">Design System</span> | |
| <span class="glass-breadcrumb__sep" aria-hidden="true">›</span> | |
| <span class="glass-breadcrumb__crumb">Components</span> | |
| <span class="glass-breadcrumb__sep" aria-hidden="true">›</span> | |
| <span class="glass-breadcrumb__crumb glass-breadcrumb__crumb--active" aria-current="page"><span class="glass-breadcrumb__crumb-icon">✦</span>Breadcrumb</span> | |
| </nav> | |
| <nav class="glass glass-breadcrumb" aria-label="Collapsed breadcrumb"> | |
| <span class="glass-breadcrumb__crumb"><span class="glass-breadcrumb__crumb-icon">⌂</span>Home</span> | |
| <span class="glass-breadcrumb__sep" aria-hidden="true">›</span> | |
| <div class="glass-breadcrumb__ellipsis-wrap" id="bc-ellipsis-wrap"> | |
| <span class="glass-breadcrumb__ellipsis" id="bc-ellipsis" role="button" aria-expanded="false" aria-label="Show hidden breadcrumbs">…</span> | |
| <div class="glass glass-breadcrumb-dropdown" role="list"> | |
| <div class="glass-breadcrumb-dropdown__item" role="listitem" tabindex="0">🗂 Products</div> | |
| <div class="glass-breadcrumb-dropdown__item" role="listitem" tabindex="0">📦 UI Kits</div> | |
| <div class="glass-breadcrumb-dropdown__item" role="listitem" tabindex="0">💎 Glass Series</div> | |
| </div> | |
| </div> | |
| <span class="glass-breadcrumb__sep" aria-hidden="true">›</span> | |
| <span class="glass-breadcrumb__crumb glass-breadcrumb__crumb--active" aria-current="page">Liquid Glass Kit</span> | |
| </nav> | |
| </div> | |
| </section> | |
| <div class="divider"></div> | |
| <!-- ─── 12 ANIMATIONS ─────────────────────────────────────────────── --> | |
| <section class="section" id="animations"> | |
| <div class="section__header"> | |
| <h2 class="section__title">12 Liquid Animations</h2> | |
| <span class="section__subtitle">Pure CSS · Infinite loop · Respects reduced-motion</span> | |
| </div> | |
| <div class="anim-grid" aria-label="Animation demos"> | |
| <div class="glass anim-demo" aria-label="Liquid Morph animation"> | |
| <div class="anim-demo__orb"></div><span class="anim-demo__label">1 · Liquid Morph</span> | |
| </div> | |
| <div class="glass anim-demo" aria-label="Shimmer animation"> | |
| <div class="anim-demo__orb"></div><span class="anim-demo__label">2 · Shimmer</span> | |
| </div> | |
| <div class="glass anim-demo" aria-label="Float animation"> | |
| <div class="anim-demo__orb"></div><span class="anim-demo__label">3 · Float</span> | |
| </div> | |
| <div class="glass anim-demo" aria-label="Refraction animation"> | |
| <div class="anim-demo__orb"></div><span class="anim-demo__label">4 · Refraction</span> | |
| </div> | |
| <div class="glass anim-demo" aria-label="Ripple animation"> | |
| <div class="anim-demo__orb" style="position:relative"></div><span class="anim-demo__label">5 · Ripple</span> | |
| </div> | |
| <div class="glass anim-demo" aria-label="Edge Glow animation"> | |
| <div class="anim-demo__orb"></div><span class="anim-demo__label">6 · Edge Glow</span> | |
| </div> | |
| <div class="glass anim-demo" aria-label="Breathing Blur animation"> | |
| <div class="anim-demo__orb"></div><span class="anim-demo__label">7 · Breathing Blur</span> | |
| </div> | |
| <div class="glass anim-demo" aria-label="Depth Shift animation"> | |
| <div class="anim-demo__orb"></div><span class="anim-demo__label">8 · Depth Shift</span> | |
| </div> | |
| <div class="glass anim-demo" aria-label="Cascade Reveal animation"> | |
| <div class="anim-demo__orb"></div><span class="anim-demo__label">9 · Cascade Reveal</span> | |
| </div> | |
| <div class="glass anim-demo" aria-label="Prism Hue Shift animation"> | |
| <div class="anim-demo__orb"></div><span class="anim-demo__label">10 · Prism Hue Shift</span> | |
| </div> | |
| <div class="glass anim-demo" aria-label="Liquid Drain animation"> | |
| <div class="anim-demo__orb"></div><span class="anim-demo__label">11 · Liquid Drain</span> | |
| </div> | |
| <div class="glass anim-demo" aria-label="Surface Tension animation"> | |
| <div class="anim-demo__orb"></div><span class="anim-demo__label">12 · Surface Tension</span> | |
| </div> | |
| </div> | |
| </section> | |
| <footer class="footer"> | |
| <p class="footer__text">Liquid Glass UI Kit · WCAG AA · @supports fallback · prefers-reduced-motion · CSS Guidelines compliant</p> | |
| </footer> | |
| </div> | |
| </main> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * liquidGlassKit.js | |
| * ───────────────────────────────────────────────────────────────────────── | |
| * Class-based component architecture with custom events. | |
| * Each component: | |
| * - Has an init() static factory method | |
| * - Emits custom 'glass:*' events on its host element | |
| * - Is keyboard accessible | |
| * - Does not rely on global state | |
| * ───────────────────────────────────────────────────────────────────────── | |
| */ | |
| (function liquidGlassKit() { | |
| "use strict"; | |
| /* ====================================================================== | |
| UTILITIES | |
| ====================================================================== */ | |
| function emit(el, name, detail) { | |
| el.dispatchEvent( | |
| new CustomEvent("glass:" + name, { bubbles: true, detail: detail || {} }) | |
| ); | |
| } | |
| function getFocusable(container) { | |
| return Array.from( | |
| container.querySelectorAll( | |
| "a[href], button:not([disabled]), input:not([disabled]), " + | |
| "select:not([disabled]), textarea:not([disabled]), " + | |
| '[tabindex]:not([tabindex="-1"])' | |
| ) | |
| ); | |
| } | |
| function trapFocus(container, evt) { | |
| var focusable = getFocusable(container); | |
| if (!focusable.length) return; | |
| var first = focusable[0]; | |
| var last = focusable[focusable.length - 1]; | |
| if (evt.key === "Tab") { | |
| if (evt.shiftKey && document.activeElement === first) { | |
| evt.preventDefault(); | |
| last.focus(); | |
| } else if (!evt.shiftKey && document.activeElement === last) { | |
| evt.preventDefault(); | |
| first.focus(); | |
| } | |
| } | |
| } | |
| /* ====================================================================== | |
| GlassTheme | |
| ====================================================================== */ | |
| var GlassTheme = { | |
| init: function () { | |
| var btn = document.getElementById("theme-toggle"); | |
| var root = document.documentElement; | |
| var key = "glass-theme"; | |
| var saved = localStorage.getItem(key); | |
| if (saved) root.setAttribute("data-theme", saved); | |
| if (btn) { | |
| btn.addEventListener("click", function () { | |
| var next = | |
| root.getAttribute("data-theme") === "light" ? "dark" : "light"; | |
| root.setAttribute("data-theme", next); | |
| localStorage.setItem(key, next); | |
| emit(root, "theme:change", { theme: next }); | |
| }); | |
| } | |
| } | |
| }; | |
| /* ====================================================================== | |
| GlassModal | |
| ====================================================================== */ | |
| function GlassModal(backdropEl) { | |
| this.backdrop = backdropEl; | |
| this.modal = backdropEl.querySelector(".glass-modal"); | |
| this._onKey = this._handleKey.bind(this); | |
| this._bindTriggers(); | |
| } | |
| GlassModal.prototype._bindTriggers = function () { | |
| var self = this; | |
| /* Open triggers — any [data-modal-open] or #modal-open */ | |
| document | |
| .querySelectorAll("[data-modal-open], #modal-open") | |
| .forEach(function (el) { | |
| el.addEventListener("click", function () { | |
| self.open(); | |
| }); | |
| }); | |
| /* Close triggers */ | |
| this.backdrop | |
| .querySelectorAll( | |
| '[id^="modal-close"], [id^="modal-confirm"], [id^="modal-cancel"]' | |
| ) | |
| .forEach(function (el) { | |
| el.addEventListener("click", function () { | |
| self.close(); | |
| }); | |
| }); | |
| /* Backdrop click */ | |
| this.backdrop.addEventListener("click", function (evt) { | |
| if (evt.target === self.backdrop) self.close(); | |
| }); | |
| }; | |
| GlassModal.prototype.open = function () { | |
| this._lastFocus = document.activeElement; | |
| this.backdrop.removeAttribute("inert"); | |
| this.backdrop.classList.add("is-open"); | |
| document.addEventListener("keydown", this._onKey); | |
| /* Focus first focusable after animation */ | |
| var self = this; | |
| setTimeout(function () { | |
| var focusable = getFocusable(self.modal); | |
| if (focusable.length) focusable[0].focus(); | |
| }, 50); | |
| emit(this.backdrop, "modal:open"); | |
| }; | |
| GlassModal.prototype.close = function () { | |
| this.backdrop.classList.remove("is-open"); | |
| this.backdrop.setAttribute("inert", ""); | |
| document.removeEventListener("keydown", this._onKey); | |
| if (this._lastFocus) this._lastFocus.focus(); | |
| emit(this.backdrop, "modal:close"); | |
| }; | |
| GlassModal.prototype._handleKey = function (evt) { | |
| if (evt.key === "Escape") { | |
| this.close(); | |
| return; | |
| } | |
| trapFocus(this.modal, evt); | |
| }; | |
| GlassModal.init = function () { | |
| var backdrop = document.getElementById("modal-backdrop"); | |
| if (backdrop) new GlassModal(backdrop); | |
| }; | |
| /* ====================================================================== | |
| GlassTabs | |
| ====================================================================== */ | |
| function GlassTabs(containerEl) { | |
| this.container = containerEl; | |
| this.tabs = Array.from(containerEl.querySelectorAll(".glass-tab")); | |
| this.panels = Array.from(containerEl.querySelectorAll(".glass-tab-panel")); | |
| this._bind(); | |
| } | |
| GlassTabs.prototype._bind = function () { | |
| var self = this; | |
| this.tabs.forEach(function (tab, i) { | |
| tab.addEventListener("click", function () { | |
| self.activate(i); | |
| }); | |
| tab.addEventListener("keydown", function (evt) { | |
| if (evt.key === "ArrowRight") { | |
| evt.preventDefault(); | |
| self.activate((i + 1) % self.tabs.length); | |
| self.tabs[(i + 1) % self.tabs.length].focus(); | |
| } | |
| if (evt.key === "ArrowLeft") { | |
| evt.preventDefault(); | |
| var prev = (i - 1 + self.tabs.length) % self.tabs.length; | |
| self.activate(prev); | |
| self.tabs[prev].focus(); | |
| } | |
| if (evt.key === "Home") { | |
| evt.preventDefault(); | |
| self.activate(0); | |
| self.tabs[0].focus(); | |
| } | |
| if (evt.key === "End") { | |
| evt.preventDefault(); | |
| self.activate(self.tabs.length - 1); | |
| self.tabs[self.tabs.length - 1].focus(); | |
| } | |
| }); | |
| }); | |
| }; | |
| GlassTabs.prototype.activate = function (index) { | |
| var self = this; | |
| this.tabs.forEach(function (t, i) { | |
| var on = i === index; | |
| t.setAttribute("aria-selected", String(on)); | |
| t.setAttribute("tabindex", on ? "0" : "-1"); | |
| }); | |
| this.panels.forEach(function (p, i) { | |
| if (i === index) { | |
| p.classList.add("is-active"); | |
| } else { | |
| p.classList.remove("is-active"); | |
| } | |
| }); | |
| emit(this.container, "tabs:change", { index: index }); | |
| }; | |
| GlassTabs.init = function () { | |
| document.querySelectorAll(".glass-tabs").forEach(function (el) { | |
| new GlassTabs(el); | |
| }); | |
| }; | |
| /* ====================================================================== | |
| GlassAccordion | |
| ====================================================================== */ | |
| function GlassAccordion(containerEl) { | |
| this.container = containerEl; | |
| this._bind(); | |
| } | |
| GlassAccordion.prototype._bind = function () { | |
| var self = this; | |
| this.container | |
| .querySelectorAll(".glass-accordion-trigger") | |
| .forEach(function (trigger) { | |
| trigger.addEventListener("click", function () { | |
| var item = trigger.closest(".glass-accordion-item"); | |
| if (!item) return; | |
| var isOpen = item.classList.contains("is-open"); | |
| /* Allow multiple open — remove this block to make exclusive */ | |
| item.classList.toggle("is-open", !isOpen); | |
| trigger.setAttribute("aria-expanded", String(!isOpen)); | |
| emit(item, "accordion:toggle", { open: !isOpen }); | |
| }); | |
| trigger.addEventListener("keydown", function (evt) { | |
| if (evt.key === "Enter" || evt.key === " ") { | |
| evt.preventDefault(); | |
| trigger.click(); | |
| } | |
| }); | |
| }); | |
| }; | |
| GlassAccordion.init = function () { | |
| document.querySelectorAll(".glass-accordion").forEach(function (el) { | |
| new GlassAccordion(el); | |
| }); | |
| }; | |
| /* ====================================================================== | |
| GlassDropdown | |
| ====================================================================== */ | |
| function GlassDropdown(containerEl) { | |
| this.container = containerEl; | |
| this.trigger = containerEl.querySelector(".glass-dropdown-trigger"); | |
| this.menu = containerEl.querySelector(".glass-dropdown-menu"); | |
| this._onOutside = this._closeOnOutside.bind(this); | |
| this._bind(); | |
| } | |
| GlassDropdown.prototype._bind = function () { | |
| var self = this; | |
| if (this.trigger) { | |
| this.trigger.addEventListener("click", function (evt) { | |
| evt.stopPropagation(); | |
| self.toggle(); | |
| }); | |
| this.trigger.addEventListener("keydown", function (evt) { | |
| if (evt.key === "ArrowDown") { | |
| evt.preventDefault(); | |
| self.open(); | |
| self._focusFirst(); | |
| } | |
| if (evt.key === "Escape") { | |
| self.close(); | |
| } | |
| }); | |
| } | |
| if (this.menu) { | |
| this.menu | |
| .querySelectorAll(".glass-dropdown-item") | |
| .forEach(function (item) { | |
| item.addEventListener("click", function () { | |
| self.close(); | |
| }); | |
| item.addEventListener("keydown", function (evt) { | |
| if (evt.key === "Escape") { | |
| self.close(); | |
| self.trigger && self.trigger.focus(); | |
| } | |
| if (evt.key === "ArrowDown") { | |
| evt.preventDefault(); | |
| var next = item.nextElementSibling; | |
| while (next && next.classList.contains("glass-dropdown-divider")) | |
| next = next.nextElementSibling; | |
| if (next) next.focus(); | |
| } | |
| if (evt.key === "ArrowUp") { | |
| evt.preventDefault(); | |
| var prev = item.previousElementSibling; | |
| while (prev && prev.classList.contains("glass-dropdown-divider")) | |
| prev = prev.previousElementSibling; | |
| if (prev) prev.focus(); | |
| else self.trigger && self.trigger.focus(); | |
| } | |
| }); | |
| }); | |
| } | |
| }; | |
| GlassDropdown.prototype.open = function () { | |
| this.container.classList.add("is-open"); | |
| this.trigger && this.trigger.setAttribute("aria-expanded", "true"); | |
| document.addEventListener("click", this._onOutside); | |
| document.addEventListener( | |
| "keydown", | |
| (this._onKey = function (evt) { | |
| if (evt.key === "Escape") this.close(); | |
| }.bind(this)) | |
| ); | |
| emit(this.container, "dropdown:open"); | |
| }; | |
| GlassDropdown.prototype.close = function () { | |
| this.container.classList.remove("is-open"); | |
| this.trigger && this.trigger.setAttribute("aria-expanded", "false"); | |
| document.removeEventListener("click", this._onOutside); | |
| emit(this.container, "dropdown:close"); | |
| }; | |
| GlassDropdown.prototype.toggle = function () { | |
| this.container.classList.contains("is-open") ? this.close() : this.open(); | |
| }; | |
| GlassDropdown.prototype._focusFirst = function () { | |
| var first = this.menu && this.menu.querySelector(".glass-dropdown-item"); | |
| if (first) | |
| setTimeout(function () { | |
| first.focus(); | |
| }, 20); | |
| }; | |
| GlassDropdown.prototype._closeOnOutside = function (evt) { | |
| if (!this.container.contains(evt.target)) this.close(); | |
| }; | |
| GlassDropdown.init = function () { | |
| document.querySelectorAll(".glass-dropdown").forEach(function (el) { | |
| new GlassDropdown(el); | |
| }); | |
| }; | |
| /* ====================================================================== | |
| GlassToast | |
| ====================================================================== */ | |
| var GlassToast = { | |
| ICONS: { success: "✓", error: "✕", warning: "⚠", info: "ℹ" }, | |
| LABELS: { | |
| success: "Success", | |
| error: "Error", | |
| warning: "Warning", | |
| info: "Info" | |
| }, | |
| show: function (type, title, desc, duration) { | |
| var region = document.getElementById("toast-region"); | |
| if (!region) return; | |
| var toast = document.createElement("div"); | |
| toast.className = "glass glass-toast glass-toast--" + type; | |
| toast.setAttribute("role", "status"); | |
| toast.setAttribute("aria-live", "polite"); | |
| toast.innerHTML = | |
| '<span class="glass-toast__icon" aria-hidden="true">' + | |
| (this.ICONS[type] || "ℹ") + | |
| "</span>" + | |
| '<div class="glass-toast__body">' + | |
| '<div class="glass-toast__title">' + | |
| (title || this.LABELS[type]) + | |
| "</div>" + | |
| (desc ? '<div class="glass-toast__desc">' + desc + "</div>" : "") + | |
| "</div>" + | |
| '<span class="glass-toast__close" aria-label="Dismiss">✕</span>'; | |
| region.appendChild(toast); | |
| emit(region, "toast:show", { type: type, title: title }); | |
| var dismiss = function () { | |
| toast.classList.add("is-exiting"); | |
| toast.addEventListener( | |
| "animationend", | |
| function () { | |
| toast.remove(); | |
| }, | |
| { once: true } | |
| ); | |
| }; | |
| toast | |
| .querySelector(".glass-toast__close") | |
| .addEventListener("click", dismiss); | |
| toast.addEventListener("click", dismiss); | |
| if (duration !== 0) setTimeout(dismiss, duration || 4000); | |
| }, | |
| init: function () { | |
| document.addEventListener("click", function (evt) { | |
| var btn = evt.target.closest("[data-toast]"); | |
| if (!btn) return; | |
| GlassToast.show( | |
| btn.dataset.toast, | |
| btn.dataset.toastTitle, | |
| btn.dataset.toastDesc | |
| ); | |
| }); | |
| } | |
| }; | |
| /* ====================================================================== | |
| GlassStepper | |
| ====================================================================== */ | |
| var GlassStepper = { | |
| init: function () { | |
| var stepper = document.getElementById("stepper-h"); | |
| var prevBtn = document.getElementById("stepper-prev"); | |
| var nextBtn = document.getElementById("stepper-next"); | |
| if (!stepper || !prevBtn || !nextBtn) return; | |
| var steps = Array.from(stepper.querySelectorAll(".glass-step")); | |
| var current = steps.findIndex(function (s) { | |
| return s.classList.contains("is-active"); | |
| }); | |
| function update(idx) { | |
| steps.forEach(function (step, i) { | |
| step.classList.toggle("is-complete", i < idx); | |
| step.classList.toggle("is-active", i === idx); | |
| step.classList.toggle("is-pending", i > idx); | |
| var node = step.querySelector(".glass-step__node"); | |
| if (node) { | |
| if (i < idx) node.textContent = "✓"; | |
| else node.textContent = String(i + 1); | |
| } | |
| var nodeEl = step.querySelector(".glass-step__node"); | |
| if (nodeEl) { | |
| if (i === idx) { | |
| nodeEl.setAttribute("aria-current", "step"); | |
| } else { | |
| nodeEl.removeAttribute("aria-current"); | |
| } | |
| } | |
| }); | |
| current = idx; | |
| prevBtn.disabled = idx === 0; | |
| nextBtn.disabled = idx === steps.length - 1; | |
| emit(stepper, "stepper:change", { step: idx }); | |
| } | |
| prevBtn.addEventListener("click", function () { | |
| if (current > 0) update(current - 1); | |
| }); | |
| nextBtn.addEventListener("click", function () { | |
| if (current < steps.length - 1) update(current + 1); | |
| }); | |
| update(current >= 0 ? current : 0); | |
| } | |
| }; | |
| /* ====================================================================== | |
| GlassNavTabs (pill nav) | |
| ====================================================================== */ | |
| var GlassNavTabs = { | |
| init: function () { | |
| document.querySelectorAll(".glass-nav").forEach(function (nav) { | |
| nav.addEventListener("click", function (evt) { | |
| var item = evt.target.closest(".glass-nav__item"); | |
| if (!item) return; | |
| nav.querySelectorAll(".glass-nav__item").forEach(function (el) { | |
| el.classList.remove("glass-nav__item--active"); | |
| }); | |
| item.classList.add("glass-nav__item--active"); | |
| }); | |
| nav.addEventListener("keydown", function (evt) { | |
| var item = evt.target.closest(".glass-nav__item"); | |
| if (!item) return; | |
| if (evt.key === "Enter" || evt.key === " ") { | |
| evt.preventDefault(); | |
| item.click(); | |
| } | |
| }); | |
| }); | |
| } | |
| }; | |
| /* ====================================================================== | |
| GlassToggle | |
| ====================================================================== */ | |
| var GlassToggle = { | |
| init: function () { | |
| document.querySelectorAll(".glass-toggle").forEach(function (toggle) { | |
| toggle.addEventListener("click", function (evt) { | |
| var opt = evt.target.closest(".glass-toggle__opt"); | |
| if (!opt) return; | |
| toggle.querySelectorAll(".glass-toggle__opt").forEach(function (el) { | |
| el.classList.remove("is-on"); | |
| }); | |
| opt.classList.add("is-on"); | |
| }); | |
| }); | |
| } | |
| }; | |
| /* ====================================================================== | |
| GlassRipple | |
| ====================================================================== */ | |
| var GlassRipple = { | |
| create: function (el, evt) { | |
| /* Limit DOM nodes: skip if too many ripples present */ | |
| if (el.querySelectorAll("[data-ripple]").length > 3) return; | |
| var ripple = document.createElement("span"); | |
| ripple.setAttribute("data-ripple", ""); | |
| var rect = el.getBoundingClientRect(); | |
| var size = Math.max(rect.width, rect.height) * 1.2; | |
| var x = evt.clientX - rect.left - size / 2; | |
| var y = evt.clientY - rect.top - size / 2; | |
| Object.assign(ripple.style, { | |
| position: "absolute", | |
| left: x + "px", | |
| top: y + "px", | |
| width: size + "px", | |
| height: size + "px", | |
| borderRadius: "50%", | |
| background: "rgba(255,255,255,0.18)", | |
| transform: "scale(0)", | |
| pointerEvents: "none", | |
| animation: "ripple 600ms ease-out forwards", | |
| zIndex: "999" | |
| }); | |
| el.appendChild(ripple); | |
| ripple.addEventListener( | |
| "animationend", | |
| function () { | |
| ripple.remove(); | |
| }, | |
| { once: true } | |
| ); | |
| }, | |
| init: function () { | |
| document.addEventListener("click", function (evt) { | |
| var target = evt.target.closest(".glass-btn, .glass-card, .notif"); | |
| if (target) GlassRipple.create(target, evt); | |
| }); | |
| } | |
| }; | |
| /* ====================================================================== | |
| GlassParallax (mouse tilt on cards) | |
| ====================================================================== */ | |
| var GlassParallax = { | |
| init: function () { | |
| /* Skip on touch devices */ | |
| if (window.matchMedia("(hover: none)").matches) return; | |
| /* Respect reduced motion */ | |
| if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; | |
| document.querySelectorAll(".glass-card").forEach(function (card) { | |
| card.addEventListener("mousemove", function (evt) { | |
| var rect = card.getBoundingClientRect(); | |
| var dx = | |
| (evt.clientX - (rect.left + rect.width / 2)) / (rect.width / 2); | |
| var dy = | |
| (evt.clientY - (rect.top + rect.height / 2)) / (rect.height / 2); | |
| card.style.transform = | |
| "translateY(-6px) scale(1.01) rotateX(" + | |
| -dy * 4 + | |
| "deg) rotateY(" + | |
| dx * 4 + | |
| "deg)"; | |
| card.style.transition = "none"; | |
| }); | |
| card.addEventListener("mouseleave", function () { | |
| card.style.transform = ""; | |
| card.style.transition = ""; | |
| }); | |
| }); | |
| } | |
| }; | |
| /* ====================================================================== | |
| GlassChips | |
| ====================================================================== */ | |
| var GlassChips = { | |
| init: function () { | |
| /* Dismiss */ | |
| document.addEventListener("click", function (evt) { | |
| var btn = evt.target.closest(".glass-chip__dismiss"); | |
| if (!btn) return; | |
| var chip = btn.closest(".glass-chip"); | |
| if (!chip) return; | |
| chip.classList.add("is-exiting"); | |
| chip.addEventListener( | |
| "animationend", | |
| function () { | |
| chip.remove(); | |
| }, | |
| { once: true } | |
| ); | |
| emit(chip, "chip:dismiss"); | |
| }); | |
| /* Selectable */ | |
| var filterGroup = document.getElementById("filter-chips"); | |
| if (filterGroup) { | |
| function toggleChip(chip) { | |
| var on = !chip.classList.contains("is-selected"); | |
| chip.classList.toggle("is-selected", on); | |
| chip.setAttribute("aria-checked", String(on)); | |
| emit(chip, "chip:select", { | |
| value: chip.dataset.value, | |
| selected: on | |
| }); | |
| } | |
| filterGroup.addEventListener("click", function (evt) { | |
| var chip = evt.target.closest(".js-selectable"); | |
| if (chip) toggleChip(chip); | |
| }); | |
| filterGroup.addEventListener("keydown", function (evt) { | |
| var chip = evt.target.closest(".js-selectable"); | |
| if (chip && (evt.key === "Enter" || evt.key === " ")) { | |
| evt.preventDefault(); | |
| toggleChip(chip); | |
| } | |
| }); | |
| } | |
| /* Dynamic add */ | |
| var addBtn = document.getElementById("chip-add-btn"); | |
| var dynGroup = document.getElementById("dynamic-chips"); | |
| if (!addBtn || !dynGroup) return; | |
| var activeInputWrap = null; | |
| function commitChip() { | |
| if (!activeInputWrap) return; | |
| var val = | |
| (activeInputWrap.querySelector(".glass-chip-input") || {}).value || | |
| ""; | |
| var label = val.trim(); | |
| cancelInput(); | |
| if (!label) return; | |
| var chip = document.createElement("span"); | |
| chip.className = "glass glass-chip is-entering"; | |
| chip.setAttribute("data-chip", ""); | |
| chip.innerHTML = | |
| '<span class="glass-chip__label">' + | |
| label.replace(/</g, "<").replace(/>/g, ">") + | |
| '</span><span class="glass-chip__dismiss" role="button" aria-label="Remove ' + | |
| label + | |
| '">✕</span>'; | |
| dynGroup.insertBefore(chip, addBtn); | |
| chip.addEventListener( | |
| "animationend", | |
| function () { | |
| chip.classList.remove("is-entering"); | |
| }, | |
| { once: true } | |
| ); | |
| emit(dynGroup, "chip:add", { label: label }); | |
| } | |
| function cancelInput() { | |
| if (!activeInputWrap) return; | |
| activeInputWrap.remove(); | |
| activeInputWrap = null; | |
| addBtn.style.display = ""; | |
| } | |
| function openInput() { | |
| if (activeInputWrap) return; | |
| addBtn.style.display = "none"; | |
| var wrap = document.createElement("div"); | |
| wrap.className = "glass-chip-input-wrap"; | |
| wrap.innerHTML = | |
| '<input class="glass-chip-input" type="text" placeholder="Tag name…" maxlength="24" autocomplete="off" aria-label="New chip name"><button class="glass-chip-input-confirm" aria-label="Add chip">✓</button>'; | |
| activeInputWrap = wrap; | |
| dynGroup.insertBefore(wrap, addBtn); | |
| var inputEl = wrap.querySelector(".glass-chip-input"); | |
| var confirmEl = wrap.querySelector(".glass-chip-input-confirm"); | |
| inputEl.focus(); | |
| inputEl.addEventListener("keydown", function (evt) { | |
| if (evt.key === "Enter") { | |
| evt.preventDefault(); | |
| commitChip(); | |
| } | |
| if (evt.key === "Escape") { | |
| cancelInput(); | |
| } | |
| }); | |
| confirmEl.addEventListener("click", function (evt) { | |
| evt.stopPropagation(); | |
| commitChip(); | |
| }); | |
| setTimeout(function () { | |
| document.addEventListener("click", function onOut(evt) { | |
| if (!wrap.contains(evt.target) && evt.target !== addBtn) { | |
| document.removeEventListener("click", onOut); | |
| cancelInput(); | |
| } | |
| }); | |
| }, 0); | |
| } | |
| addBtn.addEventListener("click", function (evt) { | |
| evt.stopPropagation(); | |
| openInput(); | |
| }); | |
| } | |
| }; | |
| /* ====================================================================== | |
| GlassAvatarGroups | |
| ====================================================================== */ | |
| var GlassAvatarGroups = { | |
| init: function () { | |
| document | |
| .querySelectorAll(".glass-avatar-group") | |
| .forEach(function (group) { | |
| var avatars = Array.from(group.querySelectorAll(".glass-avatar")); | |
| avatars.forEach(function (av, i) { | |
| av.style.zIndex = i + 1; | |
| av.addEventListener("mouseenter", function () { | |
| av.style.zIndex = avatars.length + 10; | |
| }); | |
| av.addEventListener("mouseleave", function () { | |
| av.style.zIndex = i + 1; | |
| }); | |
| }); | |
| }); | |
| } | |
| }; | |
| /* ====================================================================== | |
| GlassBreadcrumb | |
| ====================================================================== */ | |
| var GlassBreadcrumb = { | |
| init: function () { | |
| var wrap = document.getElementById("bc-ellipsis-wrap"); | |
| var btn = document.getElementById("bc-ellipsis"); | |
| if (!wrap || !btn) return; | |
| function close() { | |
| wrap.classList.remove("is-open"); | |
| btn.setAttribute("aria-expanded", "false"); | |
| } | |
| btn.addEventListener("click", function (evt) { | |
| evt.stopPropagation(); | |
| var open = wrap.classList.toggle("is-open"); | |
| btn.setAttribute("aria-expanded", String(open)); | |
| }); | |
| wrap.querySelector(".glass-breadcrumb-dropdown") && | |
| wrap | |
| .querySelector(".glass-breadcrumb-dropdown") | |
| .addEventListener("click", close); | |
| document.addEventListener("click", function (evt) { | |
| if (!wrap.contains(evt.target)) close(); | |
| }); | |
| document.addEventListener("keydown", function (evt) { | |
| if (evt.key === "Escape") close(); | |
| }); | |
| } | |
| }; | |
| /* ====================================================================== | |
| GlassTable — sort, search, row selection, bulk actions | |
| ====================================================================== */ | |
| function GlassTable(wrapEl) { | |
| this.wrap = wrapEl; | |
| this.table = wrapEl.querySelector(".glass-table"); | |
| this.tbody = wrapEl.querySelector("#tbl-body"); | |
| this.searchEl = wrapEl.querySelector("#tbl-search"); | |
| this.countEl = wrapEl.querySelector("#tbl-count"); | |
| this.bulkBar = wrapEl.querySelector("#tbl-bulk"); | |
| this.bulkCount = wrapEl.querySelector("#tbl-bulk-count"); | |
| this.deselect = wrapEl.querySelector("#tbl-deselect"); | |
| this.checkAll = wrapEl.querySelector("#tbl-check-all"); | |
| this.pageInfo = wrapEl.querySelector("#tbl-page-info"); | |
| this.sortCol = "name"; | |
| this.sortDir = "asc"; | |
| /* Cache all original rows as data objects */ | |
| this._rows = Array.from(this.tbody.querySelectorAll("tr")).map(function ( | |
| tr | |
| ) { | |
| return { | |
| el: tr, | |
| id: tr.dataset.id, | |
| name: tr.querySelector(".glass-table-member__name") | |
| ? tr.querySelector(".glass-table-member__name").textContent | |
| : "", | |
| role: tr.cells[2] ? tr.cells[2].textContent.trim() : "", | |
| projects: parseInt( | |
| tr.cells[4] ? tr.cells[4].textContent.trim() : "0", | |
| 10 | |
| ), | |
| score: parseInt( | |
| tr.querySelector(".glass-table-score__num") | |
| ? tr.querySelector(".glass-table-score__num").textContent.trim() | |
| : "0", | |
| 10 | |
| ), | |
| selected: false | |
| }; | |
| }); | |
| this._filtered = this._rows.slice(); | |
| this._bind(); | |
| this._refreshCount(); | |
| } | |
| GlassTable.prototype._bind = function () { | |
| var self = this; | |
| /* Column sort */ | |
| this.table.querySelectorAll("thead th[data-col]").forEach(function (th) { | |
| th.addEventListener("click", function () { | |
| var col = th.dataset.col; | |
| if (self.sortCol === col) { | |
| self.sortDir = self.sortDir === "asc" ? "desc" : "asc"; | |
| } else { | |
| self.sortCol = col; | |
| self.sortDir = "asc"; | |
| } | |
| self._applySort(); | |
| self._updateSortUI(); | |
| }); | |
| }); | |
| /* Search */ | |
| if (this.searchEl) { | |
| this.searchEl.addEventListener("input", function () { | |
| self._applyFilter(); | |
| }); | |
| } | |
| /* Row checkboxes */ | |
| this.tbody.addEventListener("change", function (evt) { | |
| if (!evt.target.classList.contains("glass-table-check")) return; | |
| var tr = evt.target.closest("tr"); | |
| var row = self._rows.find(function (r) { | |
| return r.el === tr; | |
| }); | |
| if (row) row.selected = evt.target.checked; | |
| tr.classList.toggle("is-selected", evt.target.checked); | |
| self._refreshBulk(); | |
| }); | |
| /* Select all */ | |
| if (this.checkAll) { | |
| this.checkAll.addEventListener("change", function () { | |
| var checked = self.checkAll.checked; | |
| self._filtered.forEach(function (row) { | |
| row.selected = checked; | |
| row.el.classList.toggle("is-selected", checked); | |
| var cb = row.el.querySelector(".glass-table-check"); | |
| if (cb) cb.checked = checked; | |
| }); | |
| self._refreshBulk(); | |
| }); | |
| } | |
| /* Deselect all */ | |
| if (this.deselect) { | |
| this.deselect.addEventListener("click", function () { | |
| self._rows.forEach(function (row) { | |
| row.selected = false; | |
| row.el.classList.remove("is-selected"); | |
| var cb = row.el.querySelector(".glass-table-check"); | |
| if (cb) cb.checked = false; | |
| }); | |
| if (self.checkAll) self.checkAll.checked = false; | |
| self._refreshBulk(); | |
| }); | |
| } | |
| }; | |
| GlassTable.prototype._applyFilter = function () { | |
| var query = this.searchEl ? this.searchEl.value.trim().toLowerCase() : ""; | |
| this._filtered = this._rows.filter(function (row) { | |
| return ( | |
| !query || | |
| row.name.toLowerCase().includes(query) || | |
| row.role.toLowerCase().includes(query) | |
| ); | |
| }); | |
| /* Show/hide rows */ | |
| this._rows.forEach(function (row) { | |
| row.el.style.display = "none"; | |
| }); | |
| this._filtered.forEach(function (row) { | |
| row.el.style.display = ""; | |
| }); | |
| this._refreshCount(); | |
| }; | |
| GlassTable.prototype._applySort = function () { | |
| var col = this.sortCol; | |
| var dir = this.sortDir; | |
| this._filtered.sort(function (a, b) { | |
| var av = a[col], | |
| bv = b[col]; | |
| if (typeof av === "string") av = av.toLowerCase(); | |
| if (typeof bv === "string") bv = bv.toLowerCase(); | |
| if (av < bv) return dir === "asc" ? -1 : 1; | |
| if (av > bv) return dir === "asc" ? 1 : -1; | |
| return 0; | |
| }); | |
| /* Reorder DOM */ | |
| var tbody = this.tbody; | |
| this._filtered.forEach(function (row) { | |
| tbody.appendChild(row.el); | |
| }); | |
| /* Stagger re-entry */ | |
| this._filtered.forEach(function (row, i) { | |
| row.el.style.animation = "none"; | |
| row.el.offsetHeight; /* force reflow */ | |
| row.el.style.animation = "cascade-in 200ms ease both"; | |
| row.el.style.animationDelay = i * 30 + "ms"; | |
| }); | |
| }; | |
| GlassTable.prototype._updateSortUI = function () { | |
| var col = this.sortCol; | |
| var dir = this.sortDir; | |
| this.table.querySelectorAll("thead th[data-col]").forEach(function (th) { | |
| var isSorted = th.dataset.col === col; | |
| th.classList.toggle("is-sorted", isSorted); | |
| th.classList.toggle("sort-desc", isSorted && dir === "desc"); | |
| var icon = th.querySelector(".glass-table__sort-icon"); | |
| if (icon) { | |
| if (!isSorted) { | |
| icon.textContent = "↕"; | |
| } else { | |
| icon.textContent = dir === "asc" ? "↑" : "↓"; | |
| } | |
| } | |
| }); | |
| }; | |
| GlassTable.prototype._refreshBulk = function () { | |
| var count = this._rows.filter(function (r) { | |
| return r.selected; | |
| }).length; | |
| if (this.bulkBar) this.bulkBar.classList.toggle("is-visible", count > 0); | |
| if (this.bulkCount) this.bulkCount.textContent = count + " selected"; | |
| /* Sync indeterminate state on select-all */ | |
| if (this.checkAll) { | |
| var total = this._filtered.length; | |
| this.checkAll.indeterminate = count > 0 && count < total; | |
| this.checkAll.checked = count > 0 && count === total; | |
| } | |
| emit(this.wrap, "table:select", { count: count }); | |
| }; | |
| GlassTable.prototype._refreshCount = function () { | |
| if (this.countEl) { | |
| this.countEl.textContent = | |
| this._filtered.length + | |
| " member" + | |
| (this._filtered.length !== 1 ? "s" : ""); | |
| } | |
| if (this.pageInfo) { | |
| this.pageInfo.textContent = | |
| "Showing 1–" + this._filtered.length + " of " + this._rows.length; | |
| } | |
| }; | |
| GlassTable.init = function () { | |
| var wrap = document.getElementById("data-table-wrap"); | |
| if (wrap) new GlassTable(wrap); | |
| }; | |
| function boot() { | |
| GlassTheme.init(); | |
| GlassModal.init(); | |
| GlassTabs.init(); | |
| GlassAccordion.init(); | |
| GlassDropdown.init(); | |
| GlassToast.init(); | |
| GlassStepper.init(); | |
| GlassNavTabs.init(); | |
| GlassToggle.init(); | |
| GlassRipple.init(); | |
| GlassParallax.init(); | |
| GlassChips.init(); | |
| GlassAvatarGroups.init(); | |
| GlassBreadcrumb.init(); | |
| GlassTable.init(); | |
| } | |
| if (document.readyState === "loading") { | |
| document.addEventListener("DOMContentLoaded", boot); | |
| } else { | |
| boot(); | |
| } | |
| })(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* ========================================================================== | |
| LIQUID GLASS UI KIT | |
| Following CSS Guidelines (cssguidelin.es) — BEM methodology, | |
| single responsibility, progressive enhancement. | |
| ========================================================================== | |
| TABLE OF CONTENTS | |
| ----------------- | |
| 1. Custom Properties — Complete Design Token System | |
| 2. Reset & Base | |
| 3. Light Theme Tokens | |
| 4. @supports — Graceful Degradation | |
| 5. prefers-reduced-motion | |
| 6. Animated Background Scene | |
| 7. Glass Utility Classes | |
| 8. Focus Visible | |
| 9. Component: Card | |
| 10. Component: Button | |
| 11. Component: Badge & Tag | |
| 12. Component: Input | |
| 13. Component: Checkbox & Radio | |
| 14. Component: Switch | |
| 15. Component: Navigation | |
| 16. Component: Tabs + Panels | |
| 17. Component: Accordion | |
| 18. Component: Modal / Dialog | |
| 19. Component: Toast / Snackbar | |
| 20. Component: Tooltip | |
| 21. Component: Dropdown / Select | |
| 22. Component: Chip | |
| 23. Component: Avatar Group | |
| 24. Component: Breadcrumb | |
| 25. Component: Skeleton Loader | |
| 26. Component: Stepper | |
| 27. Component: Data Table | |
| 28. 12 Liquid Glass Animations | |
| 29. Page Layout & Hero | |
| 30. Theme Toggle Button | |
| 31. Responsive | |
| ========================================================================== */ | |
| /* -------------------------------------------------------------------------- | |
| 1. CUSTOM PROPERTIES — Complete Design Token System | |
| -------------------------------------------------------------------------- */ | |
| :root { | |
| /* ── Glass surface tints ── */ | |
| --glass-white: rgba(255, 255, 255, 0.12); | |
| --glass-white-md: rgba(255, 255, 255, 0.2); | |
| --glass-white-lg: rgba(255, 255, 255, 0.3); | |
| --glass-dark: rgba(0, 0, 0, 0.18); | |
| --glass-dark-md: rgba(0, 0, 0, 0.28); | |
| /* ── Borders ── */ | |
| --glass-border: rgba(255, 255, 255, 0.25); | |
| --glass-border-subtle: rgba(255, 255, 255, 0.12); | |
| --glass-border-bright: rgba(255, 255, 255, 0.5); | |
| /* ── Blur values ── */ | |
| --blur-sm: blur(8px); | |
| --blur-md: blur(18px); | |
| --blur-lg: blur(32px); | |
| --blur-xl: blur(60px); | |
| /* ── Shadows ── */ | |
| --shadow-glass: 0 8px 32px rgba(0, 0, 0, 0.28), | |
| inset 0 1px 0 rgba(255, 255, 255, 0.3); | |
| --shadow-float: 0 20px 60px rgba(0, 0, 0, 0.4), 0 4px 16px rgba(0, 0, 0, 0.2); | |
| --shadow-glow: 0 0 40px rgba(120, 200, 255, 0.35); | |
| /* ── Reflections ── */ | |
| --reflection-top: linear-gradient( | |
| 135deg, | |
| rgba(255, 255, 255, 0.4) 0%, | |
| rgba(255, 255, 255, 0) 50% | |
| ); | |
| --reflection-inner: inset 0 1px 1px rgba(255, 255, 255, 0.45), | |
| inset 0 -1px 1px rgba(0, 0, 0, 0.1); | |
| /* ── Colour palette ── */ | |
| --accent-aqua: #5ee7df; | |
| --accent-violet: #b490f5; | |
| --accent-rose: #f7a8c4; | |
| --accent-amber: #ffd27f; | |
| --accent-lime: #a8f08a; | |
| /* ── Semantic surface / text ── */ | |
| --color-bg: #0b0e1a; | |
| --color-surface: rgba(255, 255, 255, 0.04); | |
| --color-text: #ffffff; | |
| --color-text-muted: rgba(255, 255, 255, 0.55); | |
| --color-text-subtle: rgba(255, 255, 255, 0.35); | |
| /* ── Spacing scale ── */ | |
| --space-2xs: 4px; | |
| --space-xs: 8px; | |
| --space-sm: 12px; | |
| --space-md: 16px; | |
| --space-lg: 24px; | |
| --space-xl: 32px; | |
| --space-2xl: 48px; | |
| --space-3xl: 64px; | |
| --space-4xl: 96px; | |
| /* ── Type scale ── */ | |
| --text-2xs: 0.68rem; | |
| --text-xs: 0.75rem; | |
| --text-sm: 0.82rem; | |
| --text-md: 0.9rem; | |
| --text-base: 1rem; | |
| --text-lg: 1.125rem; | |
| --text-xl: 1.3rem; | |
| --text-2xl: 1.6rem; | |
| --text-3xl: 2rem; | |
| --text-4xl: 2.8rem; | |
| /* ── Radius scale ── */ | |
| --radius-xs: 6px; | |
| --radius-sm: 10px; | |
| --radius-md: 14px; | |
| --radius-lg: 20px; | |
| --radius-xl: 24px; | |
| --radius-2xl: 32px; | |
| --radius-full: 9999px; | |
| /* ── Z-index scale ── */ | |
| --z-base: 1; | |
| --z-raised: 10; | |
| --z-overlay: 100; | |
| --z-modal: 500; | |
| --z-toast: 800; | |
| --z-tooltip: 900; | |
| /* ── Easing tokens ── */ | |
| --ease-glass: cubic-bezier(0.22, 0.68, 0, 1.2); | |
| --ease-liquid: cubic-bezier(0.34, 1.56, 0.64, 1); | |
| --ease-smooth: cubic-bezier(0.4, 0, 0.2, 1); | |
| --ease-out: cubic-bezier(0, 0, 0.2, 1); | |
| /* ── Duration tokens ── */ | |
| --dur-fast: 180ms; | |
| --dur-mid: 360ms; | |
| --dur-slow: 600ms; | |
| /* ── Typography ── */ | |
| --font-display: "Italiana", serif; | |
| --font-body: "DM Sans", sans-serif; | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 3. LIGHT THEME TOKENS | |
| -------------------------------------------------------------------------- */ | |
| [data-theme="light"] { | |
| /* ── Surfaces ── */ | |
| --glass-white: rgba(255, 255, 255, 0.62); | |
| --glass-white-md: rgba(255, 255, 255, 0.78); | |
| --glass-white-lg: rgba(255, 255, 255, 0.9); | |
| --glass-dark: rgba(0, 0, 0, 0.06); | |
| --glass-dark-md: rgba(15, 23, 42, 0.08); | |
| /* ── Borders — dark on light bg (white borders are invisible) ── */ | |
| --glass-border: rgba(15, 23, 42, 0.14); | |
| --glass-border-subtle: rgba(15, 23, 42, 0.09); | |
| --glass-border-bright: rgba(15, 23, 42, 0.22); | |
| /* ── Shadows ── */ | |
| --shadow-glass: 0 8px 32px rgba(0, 0, 0, 0.1), | |
| inset 0 1px 0 rgba(255, 255, 255, 0.9); | |
| --shadow-float: 0 20px 60px rgba(0, 0, 0, 0.16), | |
| 0 4px 16px rgba(0, 0, 0, 0.08); | |
| /* ── Background ── */ | |
| --color-bg: #e8f0fa; | |
| --color-surface: rgba(255, 255, 255, 0.55); | |
| --color-text: #0f172a; | |
| --color-text-muted: rgba(15, 23, 42, 0.58); | |
| --color-text-subtle: rgba(15, 23, 42, 0.38); | |
| /* ── Reflections ── */ | |
| --reflection-top: linear-gradient( | |
| 135deg, | |
| rgba(255, 255, 255, 0.65) 0%, | |
| rgba(255, 255, 255, 0) 50% | |
| ); | |
| --reflection-inner: inset 0 1px 1px rgba(255, 255, 255, 0.95), | |
| inset 0 -1px 1px rgba(0, 0, 0, 0.04); | |
| /* ── Accent colours — darkened for WCAG AA on light backgrounds ── */ | |
| --accent-aqua: #0891b2; /* cyan-600 — was #5ee7df */ | |
| --accent-violet: #6d28d9; /* violet-700 — was #b490f5 */ | |
| --accent-rose: #be185d; /* pink-700 — was #f7a8c4 */ | |
| --accent-amber: #b45309; /* amber-700 — was #ffd27f */ | |
| --accent-lime: #15803d; /* green-700 — was #a8f08a */ | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 4. @supports — Graceful Degradation | |
| When backdrop-filter is unsupported (older Firefox / Edge), | |
| fall back to a solid-ish semi-transparent surface. | |
| -------------------------------------------------------------------------- */ | |
| @supports not (backdrop-filter: blur(1px)) { | |
| .glass { | |
| background: rgba(20, 25, 45, 0.88) !important; | |
| border-color: rgba(255, 255, 255, 0.15) !important; | |
| } | |
| [data-theme="light"] .glass { | |
| background: rgba(255, 255, 255, 0.92) !important; | |
| border-color: rgba(15, 23, 42, 0.18) !important; | |
| } | |
| .glass-modal-backdrop { | |
| background: rgba(0, 0, 0, 0.7) !important; | |
| } | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 5. prefers-reduced-motion — legal requirement in many markets | |
| -------------------------------------------------------------------------- */ | |
| @media (prefers-reduced-motion: reduce) { | |
| *, | |
| *::before, | |
| *::after { | |
| animation-duration: 0.01ms !important; | |
| animation-iteration-count: 1 !important; | |
| transition-duration: 0.01ms !important; | |
| scroll-behavior: auto !important; | |
| } | |
| .scene__blob { | |
| animation: none !important; | |
| } | |
| .hero__title, | |
| .hero__kicker, | |
| .hero__sub, | |
| .hero__cta, | |
| .stats, | |
| .section { | |
| animation: none !important; | |
| opacity: 1 !important; | |
| transform: none !important; | |
| } | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 2. RESET & BASE | |
| -------------------------------------------------------------------------- */ | |
| *, | |
| *::before, | |
| *::after { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| html { | |
| scroll-behavior: smooth; | |
| -webkit-font-smoothing: antialiased; | |
| } | |
| body { | |
| font-family: var(--font-body); | |
| font-weight: 300; | |
| color: var(--color-text); | |
| background: var(--color-bg); | |
| overflow-x: hidden; | |
| min-height: 100vh; | |
| transition: background var(--dur-mid) var(--ease-smooth), | |
| color var(--dur-mid) var(--ease-smooth); | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 6. ANIMATED BACKGROUND SCENE | |
| -------------------------------------------------------------------------- */ | |
| .scene { | |
| position: fixed; | |
| inset: 0; | |
| z-index: 0; | |
| overflow: hidden; | |
| pointer-events: none; | |
| } | |
| .scene__blob { | |
| position: absolute; | |
| border-radius: 50%; | |
| filter: blur(80px); | |
| opacity: 0.55; | |
| animation: blob-drift var(--dur, 18s) ease-in-out infinite alternate; | |
| } | |
| [data-theme="light"] .scene__blob { | |
| opacity: 0.3; | |
| } | |
| .scene__blob--1 { | |
| width: 700px; | |
| height: 700px; | |
| background: radial-gradient(circle, #5ee7df, #3b82f6); | |
| top: -200px; | |
| left: -150px; | |
| --dur: 22s; | |
| } | |
| .scene__blob--2 { | |
| width: 600px; | |
| height: 600px; | |
| background: radial-gradient(circle, #b490f5, #ec4899); | |
| bottom: -200px; | |
| right: -100px; | |
| --dur: 17s; | |
| animation-delay: -8s; | |
| } | |
| .scene__blob--3 { | |
| width: 400px; | |
| height: 400px; | |
| background: radial-gradient(circle, #ffd27f, #f7a8c4); | |
| top: 40%; | |
| left: 50%; | |
| --dur: 25s; | |
| animation-delay: -13s; | |
| } | |
| @keyframes blob-drift { | |
| 0% { | |
| transform: translate(0, 0) scale(1); | |
| } | |
| 33% { | |
| transform: translate(60px, -40px) scale(1.08); | |
| } | |
| 66% { | |
| transform: translate(-40px, 60px) scale(0.94); | |
| } | |
| 100% { | |
| transform: translate(30px, 30px) scale(1.04); | |
| } | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 7. GLASS UTILITY CLASSES | |
| -------------------------------------------------------------------------- */ | |
| .glass { | |
| backdrop-filter: var(--blur-md); | |
| -webkit-backdrop-filter: var(--blur-md); | |
| background: var(--glass-white); | |
| border: 1px solid var(--glass-border); | |
| box-shadow: var(--shadow-glass); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .glass::before { | |
| content: ""; | |
| position: absolute; | |
| inset: 0; | |
| background: var(--reflection-top); | |
| pointer-events: none; | |
| border-radius: inherit; | |
| z-index: 1; | |
| } | |
| .glass--dark { | |
| background: var(--glass-dark-md); | |
| border-color: rgba(255, 255, 255, 0.1); | |
| } | |
| .glass--bright { | |
| background: var(--glass-white-lg); | |
| border-color: var(--glass-border-bright); | |
| } | |
| .glass--frosted { | |
| backdrop-filter: var(--blur-lg); | |
| -webkit-backdrop-filter: var(--blur-lg); | |
| background: rgba(255, 255, 255, 0.08); | |
| } | |
| .glass--liquid { | |
| backdrop-filter: var(--blur-xl); | |
| -webkit-backdrop-filter: var(--blur-xl); | |
| background: rgba(255, 255, 255, 0.06); | |
| border-color: rgba(255, 255, 255, 0.18); | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 8. FOCUS VISIBLE — Keyboard accessibility | |
| -------------------------------------------------------------------------- */ | |
| :focus { | |
| outline: none; | |
| } | |
| :focus-visible { | |
| outline: 2px solid var(--accent-aqua); | |
| outline-offset: 3px; | |
| border-radius: var(--radius-xs); | |
| } | |
| .glass-btn:focus-visible, | |
| .glass-nav__item:focus-visible, | |
| .glass-toggle__opt:focus-visible { | |
| outline: 2px solid var(--accent-aqua); | |
| outline-offset: 2px; | |
| } | |
| .glass-input:focus-visible { | |
| outline: none; /* handled by box-shadow below */ | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 9. COMPONENT: CARD | |
| -------------------------------------------------------------------------- */ | |
| .glass-card { | |
| border-radius: var(--radius-xl); | |
| padding: var(--space-xl); | |
| transition: transform var(--dur-mid) var(--ease-glass), | |
| box-shadow var(--dur-mid) var(--ease-glass), background var(--dur-mid); | |
| } | |
| .glass-card:hover { | |
| transform: translateY(-6px) scale(1.01); | |
| box-shadow: var(--shadow-float); | |
| background: var(--glass-white-md); | |
| } | |
| .glass-card::after { | |
| content: ""; | |
| position: absolute; | |
| inset: -1px; | |
| border-radius: inherit; | |
| background: linear-gradient( | |
| 135deg, | |
| rgba(255, 255, 255, 0.22) 0%, | |
| rgba(255, 255, 255, 0) 40%, | |
| rgba(255, 255, 255, 0.08) 100% | |
| ); | |
| opacity: 0; | |
| transition: opacity var(--dur-mid); | |
| pointer-events: none; | |
| z-index: 2; | |
| } | |
| .glass-card:hover::after { | |
| opacity: 1; | |
| } | |
| .glass-card__label { | |
| font-size: var(--text-2xs); | |
| letter-spacing: 0.14em; | |
| text-transform: uppercase; | |
| opacity: 0.55; | |
| margin-bottom: var(--space-xs); | |
| } | |
| .glass-card__title { | |
| font-family: var(--font-display); | |
| font-size: clamp(1.4rem, 2.5vw, 2rem); | |
| line-height: 1.15; | |
| margin-bottom: var(--space-sm); | |
| } | |
| .glass-card__body { | |
| font-size: var(--text-md); | |
| line-height: 1.65; | |
| opacity: 0.75; | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 10. COMPONENT: BUTTON | |
| -------------------------------------------------------------------------- */ | |
| .glass-btn { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: var(--space-xs); | |
| padding: var(--space-sm) var(--space-lg); | |
| border-radius: var(--radius-full); | |
| border: none; | |
| cursor: pointer; | |
| font-family: var(--font-body); | |
| font-size: var(--text-sm); | |
| font-weight: 500; | |
| letter-spacing: 0.04em; | |
| color: var(--color-text); | |
| text-decoration: none; | |
| transition: transform var(--dur-fast) var(--ease-liquid), | |
| box-shadow var(--dur-fast), background var(--dur-fast); | |
| } | |
| .glass-btn::before { | |
| content: ""; | |
| position: absolute; | |
| inset: 0; | |
| border-radius: inherit; | |
| background: var(--reflection-top); | |
| pointer-events: none; | |
| } | |
| .glass-btn:active { | |
| transform: scale(0.96); | |
| } | |
| .glass-btn:disabled { | |
| opacity: 0.4; | |
| cursor: not-allowed; | |
| pointer-events: none; | |
| } | |
| .glass-btn--primary { | |
| background: linear-gradient( | |
| 135deg, | |
| rgba(94, 231, 223, 0.5) 0%, | |
| rgba(59, 130, 246, 0.5) 100% | |
| ); | |
| border: 1px solid rgba(94, 231, 223, 0.45); | |
| box-shadow: 0 4px 24px rgba(94, 231, 223, 0.3), var(--reflection-inner); | |
| } | |
| .glass-btn--primary:hover { | |
| background: linear-gradient( | |
| 135deg, | |
| rgba(94, 231, 223, 0.7) 0%, | |
| rgba(59, 130, 246, 0.7) 100% | |
| ); | |
| box-shadow: 0 8px 36px rgba(94, 231, 223, 0.5), var(--reflection-inner); | |
| transform: translateY(-2px); | |
| } | |
| .glass-btn--ghost { | |
| background: var(--glass-white); | |
| border: 1px solid var(--glass-border); | |
| box-shadow: var(--shadow-glass); | |
| } | |
| .glass-btn--ghost:hover { | |
| background: var(--glass-white-md); | |
| transform: translateY(-2px); | |
| } | |
| .glass-btn--accent { | |
| background: linear-gradient( | |
| 135deg, | |
| rgba(180, 144, 245, 0.5) 0%, | |
| rgba(247, 168, 196, 0.5) 100% | |
| ); | |
| border: 1px solid rgba(180, 144, 245, 0.45); | |
| box-shadow: 0 4px 24px rgba(180, 144, 245, 0.3), var(--reflection-inner); | |
| } | |
| .glass-btn--accent:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 8px 40px rgba(180, 144, 245, 0.5), var(--reflection-inner); | |
| } | |
| .glass-btn--danger { | |
| background: linear-gradient( | |
| 135deg, | |
| rgba(239, 68, 68, 0.45) 0%, | |
| rgba(220, 38, 38, 0.45) 100% | |
| ); | |
| border: 1px solid rgba(239, 68, 68, 0.4); | |
| box-shadow: 0 4px 24px rgba(239, 68, 68, 0.25), var(--reflection-inner); | |
| } | |
| .glass-btn--danger:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 8px 36px rgba(239, 68, 68, 0.45), var(--reflection-inner); | |
| } | |
| .glass-btn--icon { | |
| width: 48px; | |
| height: 48px; | |
| padding: 0; | |
| justify-content: center; | |
| line-height: 1; | |
| } | |
| .glass-btn--sm { | |
| padding: var(--space-2xs) var(--space-md); | |
| font-size: var(--text-xs); | |
| } | |
| .glass-btn--lg { | |
| padding: var(--space-md) var(--space-xl); | |
| font-size: var(--text-base); | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 11. COMPONENT: BADGE & TAG | |
| -------------------------------------------------------------------------- */ | |
| .glass-badge { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| padding: 5px 14px; | |
| border-radius: var(--radius-full); | |
| font-size: var(--text-2xs); | |
| font-weight: 500; | |
| letter-spacing: 0.08em; | |
| text-transform: uppercase; | |
| } | |
| .glass-badge--aqua { | |
| background: rgba(94, 231, 223, 0.2); | |
| border: 1px solid rgba(94, 231, 223, 0.4); | |
| color: var(--accent-aqua); | |
| box-shadow: 0 0 12px rgba(94, 231, 223, 0.18); | |
| } | |
| .glass-badge--violet { | |
| background: rgba(180, 144, 245, 0.2); | |
| border: 1px solid rgba(180, 144, 245, 0.4); | |
| color: var(--accent-violet); | |
| box-shadow: 0 0 12px rgba(180, 144, 245, 0.18); | |
| } | |
| .glass-badge--rose { | |
| background: rgba(247, 168, 196, 0.2); | |
| border: 1px solid rgba(247, 168, 196, 0.4); | |
| color: var(--accent-rose); | |
| } | |
| .glass-badge--amber { | |
| background: rgba(255, 210, 127, 0.2); | |
| border: 1px solid rgba(255, 210, 127, 0.4); | |
| color: var(--accent-amber); | |
| } | |
| .glass-badge--lime { | |
| background: rgba(168, 240, 138, 0.2); | |
| border: 1px solid rgba(168, 240, 138, 0.4); | |
| color: var(--accent-lime); | |
| } | |
| .glass-badge--error { | |
| background: rgba(239, 68, 68, 0.2); | |
| border: 1px solid rgba(239, 68, 68, 0.4); | |
| color: #f87171; | |
| } | |
| .glass-badge__dot { | |
| width: 6px; | |
| height: 6px; | |
| border-radius: 50%; | |
| background: currentColor; | |
| animation: pulse-badge 2s ease infinite; | |
| } | |
| @keyframes pulse-badge { | |
| 0%, | |
| 100% { | |
| opacity: 1; | |
| transform: scale(1); | |
| } | |
| 50% { | |
| opacity: 0.4; | |
| transform: scale(0.7); | |
| } | |
| } | |
| .glass-tag { | |
| display: inline-block; | |
| padding: 6px 16px; | |
| border-radius: var(--radius-sm); | |
| font-size: var(--text-xs); | |
| font-weight: 400; | |
| background: var(--glass-white); | |
| border: 1px solid var(--glass-border-subtle); | |
| backdrop-filter: var(--blur-sm); | |
| -webkit-backdrop-filter: var(--blur-sm); | |
| transition: transform var(--dur-fast) var(--ease-liquid), | |
| background var(--dur-fast); | |
| cursor: default; | |
| } | |
| .glass-tag:hover { | |
| background: var(--glass-white-md); | |
| transform: translateY(-2px); | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 12. COMPONENT: INPUT | |
| -------------------------------------------------------------------------- */ | |
| .glass-input-wrap { | |
| position: relative; | |
| display: flex; | |
| align-items: center; | |
| } | |
| .glass-input { | |
| width: 100%; | |
| padding: var(--space-sm) var(--space-md); | |
| border-radius: var(--radius-md); | |
| border: 1px solid var(--glass-border-subtle); | |
| background: var(--glass-white); | |
| backdrop-filter: var(--blur-sm); | |
| -webkit-backdrop-filter: var(--blur-sm); | |
| color: var(--color-text); | |
| font-family: var(--font-body); | |
| font-size: var(--text-md); | |
| outline: none; | |
| transition: border-color var(--dur-fast), box-shadow var(--dur-fast), | |
| background var(--dur-fast); | |
| } | |
| .glass-input::placeholder { | |
| opacity: 0.45; | |
| } | |
| .glass-input:focus { | |
| border-color: rgba(94, 231, 223, 0.6); | |
| background: var(--glass-white-md); | |
| box-shadow: 0 0 0 3px rgba(94, 231, 223, 0.15), var(--shadow-glass); | |
| } | |
| .glass-input[aria-invalid="true"] { | |
| border-color: rgba(239, 68, 68, 0.6); | |
| box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.15); | |
| } | |
| .glass-input-icon { | |
| position: absolute; | |
| right: 16px; | |
| opacity: 0.5; | |
| pointer-events: none; | |
| font-size: 1rem; | |
| } | |
| /* Textarea */ | |
| .glass-textarea { | |
| width: 100%; | |
| padding: var(--space-sm) var(--space-md); | |
| border-radius: var(--radius-md); | |
| border: 1px solid var(--glass-border-subtle); | |
| background: var(--glass-white); | |
| backdrop-filter: var(--blur-sm); | |
| -webkit-backdrop-filter: var(--blur-sm); | |
| color: var(--color-text); | |
| font-family: var(--font-body); | |
| font-size: var(--text-md); | |
| outline: none; | |
| resize: vertical; | |
| min-height: 100px; | |
| transition: border-color var(--dur-fast), box-shadow var(--dur-fast), | |
| background var(--dur-fast); | |
| } | |
| .glass-textarea::placeholder { | |
| opacity: 0.45; | |
| } | |
| .glass-textarea:focus { | |
| border-color: rgba(94, 231, 223, 0.6); | |
| background: var(--glass-white-md); | |
| box-shadow: 0 0 0 3px rgba(94, 231, 223, 0.15), var(--shadow-glass); | |
| } | |
| /* Select */ | |
| .glass-select { | |
| width: 100%; | |
| padding: var(--space-sm) 40px var(--space-sm) var(--space-md); | |
| border-radius: var(--radius-md); | |
| border: 1px solid var(--glass-border-subtle); | |
| background: var(--glass-white); | |
| backdrop-filter: var(--blur-sm); | |
| -webkit-backdrop-filter: var(--blur-sm); | |
| color: var(--color-text); | |
| font-family: var(--font-body); | |
| font-size: var(--text-md); | |
| outline: none; | |
| appearance: none; | |
| -webkit-appearance: none; | |
| cursor: pointer; | |
| background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='rgba(255,255,255,0.5)' stroke-width='2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E"); | |
| background-repeat: no-repeat; | |
| background-position: right 14px center; | |
| transition: border-color var(--dur-fast), box-shadow var(--dur-fast), | |
| background-color var(--dur-fast); | |
| } | |
| .glass-select:focus { | |
| border-color: rgba(94, 231, 223, 0.6); | |
| box-shadow: 0 0 0 3px rgba(94, 231, 223, 0.15), var(--shadow-glass); | |
| } | |
| .glass-select option { | |
| background: #1a1f35; | |
| color: #fff; | |
| } | |
| [data-theme="light"] .glass-select option { | |
| background: #dce8f5; | |
| color: #0f172a; | |
| } | |
| [data-theme="light"] .glass-select { | |
| background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='rgba(15,23,42,0.5)' stroke-width='2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E"); | |
| } | |
| .glass-field-error { | |
| font-size: var(--text-xs); | |
| color: #f87171; | |
| margin-top: var(--space-2xs); | |
| display: flex; | |
| align-items: center; | |
| gap: 4px; | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 13. COMPONENT: CHECKBOX & RADIO | |
| -------------------------------------------------------------------------- */ | |
| .glass-check-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--space-sm); | |
| } | |
| .glass-check-label { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: var(--space-sm); | |
| font-size: var(--text-sm); | |
| cursor: pointer; | |
| user-select: none; | |
| color: var(--color-text); | |
| } | |
| /* Hide native input */ | |
| .glass-check-label input { | |
| position: absolute; | |
| opacity: 0; | |
| width: 0; | |
| height: 0; | |
| } | |
| /* Custom box */ | |
| .glass-check-box { | |
| width: 20px; | |
| height: 20px; | |
| border-radius: var(--radius-xs); | |
| border: 1px solid var(--glass-border); | |
| background: var(--glass-white); | |
| backdrop-filter: var(--blur-sm); | |
| -webkit-backdrop-filter: var(--blur-sm); | |
| display: grid; | |
| place-items: center; | |
| flex-shrink: 0; | |
| transition: background var(--dur-fast), border-color var(--dur-fast), | |
| box-shadow var(--dur-fast), transform var(--dur-fast) var(--ease-liquid); | |
| } | |
| .glass-check-box::after { | |
| content: "✓"; | |
| font-size: 0.75rem; | |
| color: var(--accent-aqua); | |
| opacity: 0; | |
| transform: scale(0.5); | |
| transition: opacity var(--dur-fast), | |
| transform var(--dur-fast) var(--ease-liquid); | |
| } | |
| .glass-check-label input:checked + .glass-check-box { | |
| background: rgba(94, 231, 223, 0.25); | |
| border-color: rgba(94, 231, 223, 0.65); | |
| box-shadow: 0 0 14px rgba(94, 231, 223, 0.22), var(--reflection-inner); | |
| } | |
| .glass-check-label input:checked + .glass-check-box::after { | |
| opacity: 1; | |
| transform: scale(1); | |
| } | |
| .glass-check-label input:focus-visible + .glass-check-box { | |
| outline: 2px solid var(--accent-aqua); | |
| outline-offset: 2px; | |
| } | |
| .glass-check-label:hover .glass-check-box { | |
| transform: scale(1.08); | |
| } | |
| /* Radio variant */ | |
| .glass-radio-box { | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| border: 1px solid var(--glass-border); | |
| background: var(--glass-white); | |
| backdrop-filter: var(--blur-sm); | |
| -webkit-backdrop-filter: var(--blur-sm); | |
| display: grid; | |
| place-items: center; | |
| flex-shrink: 0; | |
| transition: background var(--dur-fast), border-color var(--dur-fast), | |
| box-shadow var(--dur-fast), transform var(--dur-fast) var(--ease-liquid); | |
| } | |
| .glass-radio-box::after { | |
| content: ""; | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| background: var(--accent-aqua); | |
| opacity: 0; | |
| transform: scale(0.3); | |
| transition: opacity var(--dur-fast), | |
| transform var(--dur-fast) var(--ease-liquid); | |
| } | |
| .glass-check-label input:checked + .glass-radio-box { | |
| background: rgba(94, 231, 223, 0.18); | |
| border-color: rgba(94, 231, 223, 0.65); | |
| box-shadow: 0 0 14px rgba(94, 231, 223, 0.22); | |
| } | |
| .glass-check-label input:checked + .glass-radio-box::after { | |
| opacity: 1; | |
| transform: scale(1); | |
| } | |
| .glass-check-label input:focus-visible + .glass-radio-box { | |
| outline: 2px solid var(--accent-aqua); | |
| outline-offset: 2px; | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 14. COMPONENT: SWITCH | |
| -------------------------------------------------------------------------- */ | |
| .glass-switch-label { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: var(--space-sm); | |
| cursor: pointer; | |
| user-select: none; | |
| font-size: var(--text-sm); | |
| color: var(--color-text); | |
| } | |
| .glass-switch-label input { | |
| position: absolute; | |
| opacity: 0; | |
| width: 0; | |
| height: 0; | |
| } | |
| .glass-switch-track { | |
| position: relative; | |
| width: 44px; | |
| height: 24px; | |
| border-radius: var(--radius-full); | |
| border: 1px solid var(--glass-border); | |
| background: var(--glass-white); | |
| backdrop-filter: var(--blur-sm); | |
| -webkit-backdrop-filter: var(--blur-sm); | |
| transition: background var(--dur-fast), border-color var(--dur-fast), | |
| box-shadow var(--dur-fast); | |
| flex-shrink: 0; | |
| } | |
| .glass-switch-thumb { | |
| position: absolute; | |
| top: 3px; | |
| left: 3px; | |
| width: 16px; | |
| height: 16px; | |
| border-radius: 50%; | |
| background: rgba(255, 255, 255, 0.65); | |
| border: 1px solid rgba(255, 255, 255, 0.35); | |
| box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25); | |
| transition: transform var(--dur-fast) var(--ease-liquid), | |
| background var(--dur-fast); | |
| } | |
| .glass-switch-label input:checked + .glass-switch-track { | |
| background: rgba(94, 231, 223, 0.3); | |
| border-color: rgba(94, 231, 223, 0.65); | |
| box-shadow: 0 0 14px rgba(94, 231, 223, 0.22), var(--reflection-inner); | |
| } | |
| .glass-switch-label input:checked + .glass-switch-track .glass-switch-thumb { | |
| transform: translateX(20px); | |
| background: var(--accent-aqua); | |
| } | |
| .glass-switch-label input:focus-visible + .glass-switch-track { | |
| outline: 2px solid var(--accent-aqua); | |
| outline-offset: 2px; | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 15. COMPONENT: NAVIGATION | |
| -------------------------------------------------------------------------- */ | |
| .glass-nav { | |
| display: flex; | |
| align-items: center; | |
| gap: 4px; | |
| padding: var(--space-xs); | |
| border-radius: var(--radius-full); | |
| } | |
| .glass-nav__item { | |
| padding: 9px 22px; | |
| border-radius: var(--radius-full); | |
| font-size: var(--text-sm); | |
| font-weight: 400; | |
| opacity: 0.65; | |
| cursor: pointer; | |
| transition: background var(--dur-fast), opacity var(--dur-fast), | |
| transform var(--dur-fast) var(--ease-liquid); | |
| } | |
| .glass-nav__item:hover { | |
| opacity: 1; | |
| transform: translateY(-1px); | |
| } | |
| .glass-nav__item--active { | |
| background: var(--glass-white-md); | |
| border: 1px solid var(--glass-border); | |
| opacity: 1; | |
| box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2); | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 16. COMPONENT: TABS + PANELS | |
| -------------------------------------------------------------------------- */ | |
| .glass-tabs { | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--space-md); | |
| } | |
| .glass-tab-list { | |
| display: flex; | |
| align-items: center; | |
| gap: 4px; | |
| padding: var(--space-2xs); | |
| border-radius: var(--radius-full); | |
| overflow-x: auto; | |
| -ms-overflow-style: none; | |
| scrollbar-width: none; | |
| } | |
| .glass-tab-list::-webkit-scrollbar { | |
| display: none; | |
| } | |
| .glass-tab { | |
| padding: 9px 22px; | |
| border-radius: var(--radius-full); | |
| font-size: var(--text-sm); | |
| font-weight: 400; | |
| opacity: 0.65; | |
| cursor: pointer; | |
| border: none; | |
| background: transparent; | |
| color: var(--color-text); | |
| font-family: var(--font-body); | |
| white-space: nowrap; | |
| transition: background var(--dur-fast), opacity var(--dur-fast), | |
| transform var(--dur-fast) var(--ease-liquid); | |
| } | |
| .glass-tab:hover { | |
| opacity: 1; | |
| transform: translateY(-1px); | |
| background: var(--glass-white); | |
| } | |
| .glass-tab[aria-selected="true"] { | |
| background: var(--glass-white-md); | |
| border: 1px solid var(--glass-border); | |
| opacity: 1; | |
| box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2); | |
| } | |
| .glass-tab-panel { | |
| display: none; | |
| animation: cascade-in var(--dur-mid) var(--ease-glass) both; | |
| } | |
| .glass-tab-panel.is-active { | |
| display: block; | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 17. COMPONENT: ACCORDION | |
| -------------------------------------------------------------------------- */ | |
| .glass-accordion { | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--space-xs); | |
| } | |
| .glass-accordion-item { | |
| border-radius: var(--radius-lg); | |
| overflow: hidden; | |
| transition: box-shadow var(--dur-fast); | |
| } | |
| .glass-accordion-trigger { | |
| width: 100%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: var(--space-md) var(--space-lg); | |
| font-family: var(--font-body); | |
| font-size: var(--text-sm); | |
| font-weight: 500; | |
| color: var(--color-text); | |
| cursor: pointer; | |
| border: none; | |
| background: transparent; | |
| text-align: left; | |
| transition: background var(--dur-fast); | |
| } | |
| .glass-accordion-trigger:hover { | |
| background: var(--glass-white); | |
| } | |
| .glass-accordion-icon { | |
| width: 24px; | |
| height: 24px; | |
| border-radius: 50%; | |
| border: 1px solid var(--glass-border); | |
| background: var(--glass-white); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 0.65rem; | |
| line-height: 1; | |
| flex-shrink: 0; | |
| transition: transform var(--dur-mid) var(--ease-liquid), | |
| background var(--dur-fast); | |
| } | |
| .glass-accordion-item.is-open .glass-accordion-icon { | |
| transform: rotate(180deg); | |
| background: rgba(94, 231, 223, 0.2); | |
| border-color: rgba(94, 231, 223, 0.5); | |
| } | |
| .glass-accordion-body { | |
| overflow: hidden; | |
| max-height: 0; | |
| transition: max-height var(--dur-mid) var(--ease-smooth), | |
| padding var(--dur-mid) var(--ease-smooth); | |
| } | |
| .glass-accordion-item.is-open .glass-accordion-body { | |
| max-height: 400px; | |
| } | |
| .glass-accordion-content { | |
| padding: 0 var(--space-lg) var(--space-md); | |
| font-size: var(--text-sm); | |
| line-height: 1.7; | |
| opacity: 0.75; | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 18. COMPONENT: MODAL / DIALOG | |
| -------------------------------------------------------------------------- */ | |
| .glass-modal-backdrop { | |
| position: fixed; | |
| inset: 0; | |
| background: rgba(0, 0, 0, 0.5); | |
| backdrop-filter: blur(6px); | |
| -webkit-backdrop-filter: blur(6px); | |
| display: grid; | |
| place-items: center; | |
| z-index: var(--z-modal); | |
| opacity: 0; | |
| pointer-events: none; | |
| transition: opacity var(--dur-mid); | |
| padding: var(--space-md); | |
| } | |
| .glass-modal-backdrop.is-open { | |
| opacity: 1; | |
| pointer-events: all; | |
| } | |
| /* The modal itself gets its own opaque tinted surface — NOT using .glass | |
| directly so it isn't transparent like other glass panels. */ | |
| .glass-modal { | |
| border-radius: var(--radius-2xl); | |
| padding: 0; | |
| width: min(500px, 90vw); | |
| transform: translateY(24px) scale(0.96); | |
| transition: transform var(--dur-slow) var(--ease-liquid), | |
| opacity var(--dur-slow); | |
| opacity: 0; | |
| overflow: hidden; | |
| /* Dark: deep navy with enough opacity to read clearly */ | |
| background: rgba(16, 20, 40, 0.92); | |
| border: 1px solid rgba(255, 255, 255, 0.16); | |
| box-shadow: 0 32px 80px rgba(0, 0, 0, 0.55), 0 8px 24px rgba(0, 0, 0, 0.35), | |
| inset 0 1px 0 rgba(255, 255, 255, 0.12); | |
| backdrop-filter: var(--blur-lg); | |
| -webkit-backdrop-filter: var(--blur-lg); | |
| position: relative; | |
| } | |
| /* Top accent bar — thin gradient stripe across the header */ | |
| .glass-modal::before { | |
| content: ""; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| height: 2px; | |
| background: linear-gradient( | |
| 90deg, | |
| var(--accent-aqua) 0%, | |
| var(--accent-violet) 50%, | |
| var(--accent-rose) 100% | |
| ); | |
| z-index: 10; | |
| pointer-events: none; | |
| } | |
| .glass-modal-backdrop.is-open .glass-modal { | |
| transform: translateY(0) scale(1); | |
| opacity: 1; | |
| } | |
| /* Header area — slightly lighter strip */ | |
| .glass-modal__header { | |
| padding: var(--space-xl) var(--space-xl) var(--space-md); | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.08); | |
| background: rgba(255, 255, 255, 0.04); | |
| } | |
| /* Body area */ | |
| .glass-modal__body { | |
| padding: var(--space-xl); | |
| } | |
| /* Footer actions */ | |
| .glass-modal__footer { | |
| padding: var(--space-md) var(--space-xl) var(--space-xl); | |
| display: flex; | |
| gap: var(--space-sm); | |
| } | |
| .glass-modal__close { | |
| position: absolute; | |
| top: var(--space-md); | |
| right: var(--space-md); | |
| width: 32px; | |
| height: 32px; | |
| border-radius: 50%; | |
| border: 1px solid rgba(255, 255, 255, 0.15); | |
| background: rgba(255, 255, 255, 0.08); | |
| color: rgba(255, 255, 255, 0.7); | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 0.8rem; | |
| line-height: 1; | |
| z-index: 20; | |
| transition: background var(--dur-fast), color var(--dur-fast), | |
| transform var(--dur-fast) var(--ease-liquid); | |
| } | |
| .glass-modal__close:hover { | |
| background: rgba(255, 255, 255, 0.16); | |
| color: #fff; | |
| transform: rotate(90deg) scale(1.1); | |
| } | |
| /* Light mode modal overrides */ | |
| [data-theme="light"] .glass-modal-backdrop { | |
| background: rgba(15, 23, 42, 0.45); | |
| } | |
| [data-theme="light"] .glass-modal { | |
| background: rgba(255, 255, 255, 0.96); | |
| border-color: rgba(15, 23, 42, 0.12); | |
| box-shadow: 0 32px 80px rgba(0, 0, 0, 0.18), 0 8px 24px rgba(0, 0, 0, 0.1), | |
| inset 0 1px 0 rgba(255, 255, 255, 0.95); | |
| } | |
| [data-theme="light"] .glass-modal__header { | |
| border-bottom-color: rgba(15, 23, 42, 0.08); | |
| background: rgba(15, 23, 42, 0.02); | |
| } | |
| [data-theme="light"] .glass-modal__close { | |
| border-color: rgba(15, 23, 42, 0.14); | |
| background: rgba(15, 23, 42, 0.06); | |
| color: rgba(15, 23, 42, 0.55); | |
| } | |
| [data-theme="light"] .glass-modal__close:hover { | |
| background: rgba(15, 23, 42, 0.12); | |
| color: #0f172a; | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 19. COMPONENT: TOAST / SNACKBAR | |
| -------------------------------------------------------------------------- */ | |
| .glass-toast-region { | |
| position: fixed; | |
| bottom: var(--space-lg); | |
| right: var(--space-lg); | |
| z-index: var(--z-toast); | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--space-xs); | |
| pointer-events: none; | |
| width: min(360px, 90vw); | |
| } | |
| .glass-toast { | |
| border-radius: var(--radius-lg); | |
| padding: var(--space-md) var(--space-lg); | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-sm); | |
| pointer-events: all; | |
| cursor: pointer; | |
| animation: toast-in var(--dur-mid) var(--ease-liquid) both; | |
| transition: transform var(--dur-fast) var(--ease-liquid), | |
| opacity var(--dur-fast); | |
| } | |
| .glass-toast:hover { | |
| transform: translateX(-4px); | |
| } | |
| .glass-toast.is-exiting { | |
| animation: toast-out var(--dur-mid) var(--ease-glass) forwards; | |
| } | |
| @keyframes toast-in { | |
| from { | |
| opacity: 0; | |
| transform: translateX(60px) scale(0.95); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateX(0) scale(1); | |
| } | |
| } | |
| @keyframes toast-out { | |
| from { | |
| opacity: 1; | |
| transform: translateX(0) scale(1); | |
| max-height: 80px; | |
| } | |
| to { | |
| opacity: 0; | |
| transform: translateX(60px) scale(0.9); | |
| max-height: 0; | |
| padding: 0; | |
| margin: 0; | |
| } | |
| } | |
| .glass-toast__icon { | |
| font-size: 1.2rem; | |
| flex-shrink: 0; | |
| } | |
| .glass-toast__body { | |
| flex: 1; | |
| } | |
| .glass-toast__title { | |
| font-size: var(--text-sm); | |
| font-weight: 500; | |
| } | |
| .glass-toast__desc { | |
| font-size: var(--text-xs); | |
| opacity: 0.65; | |
| margin-top: 2px; | |
| } | |
| .glass-toast__close { | |
| opacity: 0.5; | |
| font-size: 0.75rem; | |
| flex-shrink: 0; | |
| transition: opacity var(--dur-fast); | |
| } | |
| .glass-toast:hover .glass-toast__close { | |
| opacity: 1; | |
| } | |
| .glass-toast--success { | |
| border-color: rgba(74, 222, 128, 0.4) !important; | |
| box-shadow: 0 4px 24px rgba(74, 222, 128, 0.2) !important; | |
| } | |
| .glass-toast--error { | |
| border-color: rgba(239, 68, 68, 0.4) !important; | |
| box-shadow: 0 4px 24px rgba(239, 68, 68, 0.2) !important; | |
| } | |
| .glass-toast--warning { | |
| border-color: rgba(251, 191, 36, 0.4) !important; | |
| box-shadow: 0 4px 24px rgba(251, 191, 36, 0.2) !important; | |
| } | |
| .glass-toast--info { | |
| border-color: rgba(94, 231, 223, 0.4) !important; | |
| box-shadow: 0 4px 24px rgba(94, 231, 223, 0.2) !important; | |
| } | |
| /* Toast demo buttons */ | |
| .toast-demo-row { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: var(--space-sm); | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 20. COMPONENT: TOOLTIP | |
| -------------------------------------------------------------------------- */ | |
| .glass-tooltip-wrap { | |
| position: relative; | |
| display: inline-flex; | |
| } | |
| .glass-tooltip { | |
| position: absolute; | |
| bottom: calc(100% + 10px); | |
| left: 50%; | |
| transform: translateX(-50%) translateY(6px); | |
| padding: 8px 14px; | |
| border-radius: var(--radius-sm); | |
| font-size: var(--text-xs); | |
| white-space: nowrap; | |
| opacity: 0; | |
| pointer-events: none; | |
| transition: opacity var(--dur-fast), | |
| transform var(--dur-fast) var(--ease-liquid); | |
| z-index: var(--z-tooltip); | |
| } | |
| .glass-tooltip::after { | |
| content: ""; | |
| position: absolute; | |
| top: 100%; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| border: 5px solid transparent; | |
| border-top-color: rgba(255, 255, 255, 0.22); | |
| } | |
| .glass-tooltip-wrap:hover .glass-tooltip { | |
| opacity: 1; | |
| transform: translateX(-50%) translateY(0); | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 21. COMPONENT: DROPDOWN / SELECT (Custom) | |
| -------------------------------------------------------------------------- */ | |
| .glass-dropdown { | |
| position: relative; | |
| display: inline-block; | |
| } | |
| .glass-dropdown-menu { | |
| position: absolute; | |
| top: calc(100% + 8px); | |
| left: 0; | |
| min-width: 200px; | |
| border-radius: var(--radius-lg); | |
| padding: var(--space-2xs); | |
| pointer-events: none; | |
| opacity: 0; | |
| transform: translateY(-8px) scale(0.97); | |
| transition: opacity var(--dur-fast) var(--ease-smooth), | |
| transform var(--dur-fast) var(--ease-liquid); | |
| z-index: var(--z-overlay); | |
| overflow: hidden; | |
| } | |
| .glass-dropdown.is-open .glass-dropdown-menu { | |
| opacity: 1; | |
| transform: translateY(0) scale(1); | |
| pointer-events: all; | |
| } | |
| .glass-dropdown-item { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-xs); | |
| padding: var(--space-sm) var(--space-md); | |
| border-radius: var(--radius-sm); | |
| font-size: var(--text-sm); | |
| cursor: pointer; | |
| transition: background var(--dur-fast); | |
| color: var(--color-text); | |
| font-family: var(--font-body); | |
| border: none; | |
| background: transparent; | |
| width: 100%; | |
| text-align: left; | |
| } | |
| .glass-dropdown-item:hover { | |
| background: var(--glass-white-md); | |
| } | |
| .glass-dropdown-item.is-active { | |
| background: rgba(94, 231, 223, 0.18); | |
| color: var(--accent-aqua); | |
| } | |
| .glass-dropdown-item--danger { | |
| color: #f87171; | |
| } | |
| .glass-dropdown-item--danger:hover { | |
| background: rgba(239, 68, 68, 0.15); | |
| } | |
| .glass-dropdown-divider { | |
| height: 1px; | |
| background: var(--glass-border-subtle); | |
| margin: var(--space-2xs) 0; | |
| } | |
| .glass-dropdown-trigger { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: var(--space-xs); | |
| } | |
| .glass-dropdown-trigger-arrow { | |
| font-size: 0.65rem; | |
| opacity: 0.55; | |
| transition: transform var(--dur-fast) var(--ease-liquid); | |
| } | |
| .glass-dropdown.is-open .glass-dropdown-trigger-arrow { | |
| transform: rotate(180deg); | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 22. COMPONENT: CHIP | |
| -------------------------------------------------------------------------- */ | |
| .glass-chip { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: var(--space-xs); | |
| padding: 7px 14px 7px 10px; | |
| border-radius: var(--radius-full); | |
| font-size: var(--text-sm); | |
| font-weight: 400; | |
| letter-spacing: 0.02em; | |
| cursor: pointer; | |
| user-select: none; | |
| transition: background var(--dur-fast), border-color var(--dur-fast), | |
| box-shadow var(--dur-fast), transform var(--dur-fast) var(--ease-liquid), | |
| opacity var(--dur-fast), max-width var(--dur-mid) var(--ease-liquid), | |
| padding var(--dur-mid) var(--ease-liquid); | |
| } | |
| .glass-chip__icon { | |
| width: 22px; | |
| height: 22px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 0.85rem; | |
| line-height: 1; | |
| flex-shrink: 0; | |
| background: rgba(255, 255, 255, 0.15); | |
| border: 1px solid rgba(255, 255, 255, 0.2); | |
| overflow: hidden; | |
| } | |
| .glass-chip__icon img { | |
| width: 100%; | |
| height: 100%; | |
| object-fit: cover; | |
| border-radius: 50%; | |
| } | |
| .glass-chip__label { | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| .glass-chip__dismiss { | |
| width: 18px; | |
| height: 18px; | |
| border-radius: 50%; | |
| background: rgba(255, 255, 255, 0.12); | |
| border: 1px solid rgba(255, 255, 255, 0.18); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 0.6rem; | |
| line-height: 1; | |
| flex-shrink: 0; | |
| cursor: pointer; | |
| transition: background var(--dur-fast), | |
| transform var(--dur-fast) var(--ease-liquid); | |
| } | |
| .glass-chip__dismiss:hover { | |
| background: rgba(255, 255, 255, 0.28); | |
| transform: scale(1.2) rotate(90deg); | |
| } | |
| .glass-chip:hover { | |
| background: var(--glass-white-md); | |
| transform: translateY(-2px); | |
| } | |
| .glass-chip.is-selected { | |
| background: linear-gradient( | |
| 135deg, | |
| rgba(94, 231, 223, 0.28) 0%, | |
| rgba(180, 144, 245, 0.28) 100% | |
| ); | |
| border-color: rgba(94, 231, 223, 0.55); | |
| box-shadow: 0 0 16px rgba(94, 231, 223, 0.22), var(--reflection-inner); | |
| } | |
| .glass-chip--aqua { | |
| background: rgba(94, 231, 223, 0.14); | |
| border: 1px solid rgba(94, 231, 223, 0.3); | |
| color: var(--accent-aqua); | |
| } | |
| .glass-chip--violet { | |
| background: rgba(180, 144, 245, 0.14); | |
| border: 1px solid rgba(180, 144, 245, 0.3); | |
| color: var(--accent-violet); | |
| } | |
| .glass-chip--rose { | |
| background: rgba(247, 168, 196, 0.14); | |
| border: 1px solid rgba(247, 168, 196, 0.3); | |
| color: var(--accent-rose); | |
| } | |
| .glass-chip--amber { | |
| background: rgba(255, 210, 127, 0.14); | |
| border: 1px solid rgba(255, 210, 127, 0.3); | |
| color: var(--accent-amber); | |
| } | |
| @keyframes chip-exit { | |
| 0% { | |
| opacity: 1; | |
| transform: scale(1); | |
| max-width: 220px; | |
| } | |
| 40% { | |
| opacity: 0; | |
| transform: scale(0.85); | |
| } | |
| 100% { | |
| opacity: 0; | |
| transform: scale(0.8); | |
| max-width: 0; | |
| padding: 0; | |
| margin: 0; | |
| } | |
| } | |
| @keyframes chip-enter { | |
| 0% { | |
| opacity: 0; | |
| transform: scale(0.72) translateY(4px); | |
| } | |
| 100% { | |
| opacity: 1; | |
| transform: scale(1) translateY(0); | |
| } | |
| } | |
| .glass-chip.is-exiting { | |
| animation: chip-exit var(--dur-mid) var(--ease-glass) forwards; | |
| pointer-events: none; | |
| overflow: hidden; | |
| } | |
| .glass-chip.is-entering { | |
| animation: chip-enter var(--dur-mid) var(--ease-liquid) both; | |
| } | |
| .glass-chip-group { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: var(--space-xs); | |
| align-items: center; | |
| } | |
| .glass-chip-add { | |
| width: 34px; | |
| height: 34px; | |
| border-radius: 50%; | |
| border: 1px dashed var(--glass-border); | |
| background: transparent; | |
| color: var(--color-text); | |
| display: grid; | |
| place-items: center; | |
| font-size: 1rem; | |
| cursor: pointer; | |
| opacity: 0.45; | |
| transition: opacity var(--dur-fast), background var(--dur-fast), | |
| transform var(--dur-fast) var(--ease-liquid); | |
| } | |
| .glass-chip-add:hover { | |
| opacity: 1; | |
| background: var(--glass-white); | |
| transform: rotate(90deg) scale(1.1); | |
| } | |
| .glass-chip-input-wrap { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: var(--space-xs); | |
| padding: 5px 10px 5px 14px; | |
| border-radius: var(--radius-full); | |
| border: 1px solid rgba(94, 231, 223, 0.5); | |
| background: rgba(94, 231, 223, 0.1); | |
| box-shadow: 0 0 14px rgba(94, 231, 223, 0.18), var(--reflection-inner); | |
| animation: chip-enter var(--dur-mid) var(--ease-liquid) both; | |
| } | |
| .glass-chip-input { | |
| background: none; | |
| border: none; | |
| outline: none; | |
| color: var(--color-text); | |
| font-family: var(--font-body); | |
| font-size: var(--text-sm); | |
| width: 90px; | |
| caret-color: var(--accent-aqua); | |
| } | |
| .glass-chip-input::placeholder { | |
| opacity: 0.4; | |
| } | |
| .glass-chip-input-confirm { | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| background: rgba(94, 231, 223, 0.25); | |
| border: 1px solid rgba(94, 231, 223, 0.45); | |
| color: var(--accent-aqua); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 0.6rem; | |
| line-height: 1; | |
| cursor: pointer; | |
| flex-shrink: 0; | |
| transition: background var(--dur-fast), | |
| transform var(--dur-fast) var(--ease-liquid); | |
| } | |
| .glass-chip-input-confirm:hover { | |
| background: rgba(94, 231, 223, 0.45); | |
| transform: scale(1.15); | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 23. COMPONENT: AVATAR GROUP | |
| -------------------------------------------------------------------------- */ | |
| .glass-avatar-group { | |
| display: flex; | |
| align-items: center; | |
| padding: 70px 8px 16px; | |
| margin-top: -60px; | |
| } | |
| .glass-avatar { | |
| position: relative; | |
| width: var(--avatar-size, 44px); | |
| height: var(--avatar-size, 44px); | |
| border-radius: 50%; | |
| border: 2.5px solid rgba(255, 255, 255, 0.3); | |
| background: linear-gradient(135deg, var(--from, #5ee7df), var(--to, #b490f5)); | |
| display: grid; | |
| place-items: center; | |
| font-size: calc(var(--avatar-size, 44px) * 0.38); | |
| font-weight: 500; | |
| overflow: visible; | |
| cursor: pointer; | |
| flex-shrink: 0; | |
| margin-left: calc(var(--avatar-size, 44px) * -0.25); | |
| transition: transform var(--dur-fast) var(--ease-liquid), | |
| box-shadow var(--dur-fast); | |
| box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35), | |
| inset 0 1px 0 rgba(255, 255, 255, 0.35); | |
| } | |
| .glass-avatar:first-child { | |
| margin-left: 0; | |
| } | |
| .glass-avatar__face { | |
| width: 100%; | |
| height: 100%; | |
| border-radius: 50%; | |
| overflow: hidden; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: inherit; | |
| line-height: 1; | |
| } | |
| .glass-avatar__face img { | |
| width: 100%; | |
| height: 100%; | |
| object-fit: cover; | |
| } | |
| .glass-avatar:hover { | |
| transform: translateY(-8px) scale(1.1); | |
| z-index: 10; | |
| box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45), | |
| inset 0 1px 0 rgba(255, 255, 255, 0.45); | |
| } | |
| .glass-avatar--overflow { | |
| background: var(--glass-white-md); | |
| backdrop-filter: var(--blur-sm); | |
| -webkit-backdrop-filter: var(--blur-sm); | |
| border: 2px solid var(--glass-border); | |
| font-size: calc(var(--avatar-size, 44px) * 0.3); | |
| font-weight: 500; | |
| letter-spacing: -0.02em; | |
| color: rgba(255, 255, 255, 0.75); | |
| } | |
| .glass-avatar--overflow:hover { | |
| background: var(--glass-white-lg); | |
| } | |
| .glass-avatar--photo .glass-avatar__face { | |
| font-size: calc(var(--avatar-size, 44px) * 0.6); | |
| } | |
| .glass-avatar-popover { | |
| position: absolute; | |
| bottom: calc(100% + 10px); | |
| left: 50%; | |
| transform: translateX(-50%) translateY(6px); | |
| min-width: 120px; | |
| border-radius: var(--radius-md); | |
| padding: 10px 14px; | |
| pointer-events: none; | |
| opacity: 0; | |
| white-space: nowrap; | |
| font-size: var(--text-xs); | |
| transition: opacity var(--dur-fast), | |
| transform var(--dur-fast) var(--ease-liquid); | |
| z-index: 50; | |
| } | |
| .glass-avatar:hover .glass-avatar-popover { | |
| opacity: 1; | |
| transform: translateX(-50%) translateY(0); | |
| } | |
| .glass-avatar-popover--list { | |
| min-width: 180px; | |
| padding: 10px; | |
| white-space: normal; | |
| } | |
| .glass-avatar-popover__list { | |
| list-style: none; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 6px; | |
| } | |
| .glass-avatar-popover__item { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| font-size: var(--text-xs); | |
| } | |
| .glass-avatar-popover__mini { | |
| width: 24px; | |
| height: 24px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 0.6rem; | |
| line-height: 1; | |
| flex-shrink: 0; | |
| border: 1px solid rgba(255, 255, 255, 0.2); | |
| } | |
| .glass-avatar-group--sm .glass-avatar { | |
| --avatar-size: 32px; | |
| } | |
| .glass-avatar-group--lg .glass-avatar { | |
| --avatar-size: 56px; | |
| } | |
| .glass-avatar-group__meta { | |
| margin-left: 14px; | |
| font-size: var(--text-xs); | |
| opacity: 0.55; | |
| white-space: nowrap; | |
| } | |
| .glass-avatar__status { | |
| position: absolute; | |
| bottom: 1px; | |
| right: 1px; | |
| width: 10px; | |
| height: 10px; | |
| border-radius: 50%; | |
| border: 2px solid var(--color-bg); | |
| z-index: 2; | |
| } | |
| .glass-avatar__status--online { | |
| background: #4ade80; | |
| } | |
| .glass-avatar__status--away { | |
| background: var(--accent-amber); | |
| } | |
| .glass-avatar__status--offline { | |
| background: rgba(255, 255, 255, 0.28); | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 24. COMPONENT: BREADCRUMB | |
| -------------------------------------------------------------------------- */ | |
| .glass-breadcrumb { | |
| display: flex; | |
| align-items: center; | |
| padding: 6px 8px; | |
| border-radius: var(--radius-full); | |
| flex-wrap: nowrap; | |
| max-width: 100%; | |
| overflow: visible; | |
| } | |
| .glass-breadcrumb__crumb { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| padding: 6px 14px; | |
| border-radius: var(--radius-full); | |
| font-size: var(--text-sm); | |
| white-space: nowrap; | |
| cursor: pointer; | |
| transition: background var(--dur-fast), color var(--dur-fast), | |
| transform var(--dur-fast) var(--ease-liquid); | |
| } | |
| .glass-breadcrumb__crumb:hover { | |
| background: rgba(255, 255, 255, 0.12); | |
| transform: translateY(-1px); | |
| } | |
| .glass-breadcrumb__crumb--active { | |
| background: var(--glass-white-md); | |
| border: 1px solid var(--glass-border); | |
| box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2); | |
| cursor: default; | |
| pointer-events: none; | |
| } | |
| .glass-breadcrumb__crumb--active:hover { | |
| transform: none; | |
| } | |
| .glass-breadcrumb__crumb-icon { | |
| font-size: 0.85rem; | |
| opacity: 0.7; | |
| } | |
| .glass-breadcrumb__sep { | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| width: 16px; | |
| flex-shrink: 0; | |
| opacity: 0.25; | |
| font-size: 0.65rem; | |
| pointer-events: none; | |
| user-select: none; | |
| } | |
| .glass-breadcrumb__ellipsis-wrap { | |
| position: relative; | |
| z-index: 300; | |
| } | |
| .glass-breadcrumb__ellipsis { | |
| padding: 6px 10px; | |
| border-radius: var(--radius-full); | |
| font-size: var(--text-xs); | |
| cursor: pointer; | |
| opacity: 0.5; | |
| transition: background var(--dur-fast), opacity var(--dur-fast); | |
| } | |
| .glass-breadcrumb__ellipsis:hover { | |
| background: rgba(255, 255, 255, 0.12); | |
| opacity: 1; | |
| } | |
| .glass-breadcrumb-dropdown { | |
| position: absolute; | |
| top: calc(100% + 8px); | |
| left: 0; | |
| min-width: 190px; | |
| border-radius: var(--radius-lg); | |
| padding: var(--space-2xs); | |
| pointer-events: none; | |
| opacity: 0; | |
| transform: translateY(-6px) scale(0.97); | |
| transition: opacity var(--dur-fast), | |
| transform var(--dur-fast) var(--ease-liquid); | |
| z-index: 400; | |
| } | |
| .glass-breadcrumb__ellipsis-wrap.is-open .glass-breadcrumb-dropdown { | |
| opacity: 1; | |
| transform: translateY(0) scale(1); | |
| pointer-events: all; | |
| } | |
| .glass-breadcrumb-dropdown__item { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-xs); | |
| padding: 9px 12px; | |
| border-radius: var(--radius-sm); | |
| font-size: var(--text-sm); | |
| cursor: pointer; | |
| transition: background var(--dur-fast); | |
| } | |
| .glass-breadcrumb-dropdown__item:hover { | |
| background: rgba(255, 255, 255, 0.12); | |
| } | |
| .glass-breadcrumb--sm .glass-breadcrumb__crumb { | |
| font-size: var(--text-2xs); | |
| padding: 4px 10px; | |
| } | |
| .glass-breadcrumb--lg .glass-breadcrumb__crumb { | |
| font-size: var(--text-base); | |
| padding: 8px 18px; | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 25. COMPONENT: SKELETON LOADER | |
| -------------------------------------------------------------------------- */ | |
| .skeleton { | |
| position: relative; | |
| overflow: hidden; | |
| border-radius: var(--radius-sm); | |
| background: var(--glass-white); | |
| } | |
| .skeleton::after { | |
| content: ""; | |
| position: absolute; | |
| inset: 0; | |
| background: linear-gradient( | |
| 90deg, | |
| rgba(255, 255, 255, 0) 0%, | |
| rgba(255, 255, 255, 0.18) 40%, | |
| rgba(255, 255, 255, 0.28) 50%, | |
| rgba(255, 255, 255, 0.18) 60%, | |
| rgba(255, 255, 255, 0) 100% | |
| ); | |
| background-size: 200% auto; | |
| animation: skeleton-shimmer 1.8s linear infinite; | |
| } | |
| @keyframes skeleton-shimmer { | |
| 0% { | |
| background-position: -200% center; | |
| } | |
| 100% { | |
| background-position: 200% center; | |
| } | |
| } | |
| .skeleton--text { | |
| height: 16px; | |
| } | |
| .skeleton--heading { | |
| height: 28px; | |
| border-radius: var(--radius-xs); | |
| } | |
| .skeleton--circle { | |
| border-radius: 50%; | |
| } | |
| .skeleton--card { | |
| border-radius: var(--radius-xl); | |
| } | |
| .skeleton-layout { | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--space-md); | |
| padding: var(--space-xl); | |
| border-radius: var(--radius-xl); | |
| } | |
| .skeleton-row { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-md); | |
| } | |
| .skeleton-lines { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--space-xs); | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 26. COMPONENT: STEPPER | |
| -------------------------------------------------------------------------- */ | |
| .glass-stepper { | |
| display: flex; | |
| align-items: center; | |
| gap: 0; | |
| } | |
| .glass-step { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-xs); | |
| flex: 1; | |
| } | |
| .glass-step:last-child { | |
| flex: none; | |
| } | |
| .glass-step__connector { | |
| flex: 1; | |
| height: 1px; | |
| margin: 0 var(--space-2xs); | |
| background: var(--glass-border-subtle); | |
| transition: background var(--dur-mid); | |
| } | |
| .glass-step.is-complete .glass-step__connector { | |
| background: rgba(94, 231, 223, 0.5); | |
| } | |
| .glass-step__node { | |
| width: 36px; | |
| height: 36px; | |
| border-radius: 50%; | |
| border: 2px solid var(--glass-border); | |
| background: var(--glass-white); | |
| backdrop-filter: var(--blur-sm); | |
| -webkit-backdrop-filter: var(--blur-sm); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: var(--text-xs); | |
| font-weight: 500; | |
| line-height: 1; | |
| flex-shrink: 0; | |
| transition: background var(--dur-mid) var(--ease-liquid), | |
| border-color var(--dur-mid), box-shadow var(--dur-mid), | |
| transform var(--dur-mid) var(--ease-liquid); | |
| } | |
| .glass-step.is-complete .glass-step__node { | |
| background: rgba(94, 231, 223, 0.25); | |
| border-color: rgba(94, 231, 223, 0.65); | |
| box-shadow: 0 0 16px rgba(94, 231, 223, 0.25); | |
| transform: scale(1.05); | |
| } | |
| .glass-step.is-active .glass-step__node { | |
| background: rgba(180, 144, 245, 0.25); | |
| border-color: rgba(180, 144, 245, 0.65); | |
| box-shadow: 0 0 20px rgba(180, 144, 245, 0.3); | |
| animation: step-pulse 2s ease infinite; | |
| } | |
| @keyframes step-pulse { | |
| 0%, | |
| 100% { | |
| box-shadow: 0 0 12px rgba(180, 144, 245, 0.25); | |
| } | |
| 50% { | |
| box-shadow: 0 0 24px rgba(180, 144, 245, 0.5); | |
| } | |
| } | |
| .glass-step__label { | |
| font-size: var(--text-xs); | |
| opacity: 0.55; | |
| white-space: nowrap; | |
| } | |
| .glass-step.is-active .glass-step__label { | |
| opacity: 1; | |
| font-weight: 500; | |
| } | |
| .glass-step.is-complete .glass-step__label { | |
| opacity: 0.7; | |
| } | |
| .glass-stepper--vertical { | |
| flex-direction: column; | |
| align-items: flex-start; | |
| } | |
| .glass-stepper--vertical .glass-step { | |
| flex-direction: column; | |
| align-items: flex-start; | |
| flex: auto; | |
| width: 100%; | |
| } | |
| .glass-stepper--vertical .glass-step__connector { | |
| width: 2px; | |
| height: 32px; | |
| flex: none; | |
| margin: var(--space-2xs) 0 var(--space-2xs) 17px; | |
| } | |
| .glass-stepper--vertical .glass-step__inner { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-sm); | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 27. COMPONENT: DATA TABLE | |
| -------------------------------------------------------------------------- */ | |
| .glass-table-wrap { | |
| overflow-x: auto; | |
| border-radius: var(--radius-xl); | |
| -webkit-overflow-scrolling: touch; | |
| } | |
| /* ── Table toolbar ── */ | |
| .glass-table-toolbar { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-md); | |
| padding: var(--space-md) var(--space-lg); | |
| border-bottom: 1px solid var(--glass-border-subtle); | |
| flex-wrap: wrap; | |
| } | |
| .glass-table-toolbar__title { | |
| font-family: var(--font-display); | |
| font-size: var(--text-lg); | |
| flex: 1; | |
| min-width: 120px; | |
| } | |
| .glass-table-toolbar__count { | |
| font-size: var(--text-xs); | |
| opacity: 0.45; | |
| white-space: nowrap; | |
| } | |
| .glass-table-search { | |
| position: relative; | |
| width: min(220px, 100%); | |
| } | |
| .glass-table-search__icon { | |
| position: absolute; | |
| left: 12px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| opacity: 0.4; | |
| font-size: 0.85rem; | |
| pointer-events: none; | |
| } | |
| .glass-table-search__input { | |
| width: 100%; | |
| padding: 8px 12px 8px 34px; | |
| border-radius: var(--radius-full); | |
| border: 1px solid var(--glass-border-subtle); | |
| background: var(--glass-white); | |
| backdrop-filter: var(--blur-sm); | |
| -webkit-backdrop-filter: var(--blur-sm); | |
| color: var(--color-text); | |
| font-family: var(--font-body); | |
| font-size: var(--text-xs); | |
| outline: none; | |
| transition: border-color var(--dur-fast), box-shadow var(--dur-fast); | |
| } | |
| .glass-table-search__input::placeholder { | |
| opacity: 0.4; | |
| } | |
| .glass-table-search__input:focus { | |
| border-color: rgba(94, 231, 223, 0.55); | |
| box-shadow: 0 0 0 3px rgba(94, 231, 223, 0.12); | |
| } | |
| [data-theme="light"] .glass-table-search__input { | |
| background: rgba(255, 255, 255, 0.7); | |
| border-color: rgba(15, 23, 42, 0.14); | |
| color: #0f172a; | |
| } | |
| [data-theme="light"] .glass-table-search__input:focus { | |
| border-color: rgba(8, 145, 178, 0.45); | |
| box-shadow: 0 0 0 3px rgba(8, 145, 178, 0.12); | |
| } | |
| /* ── Table core ── */ | |
| .glass-table { | |
| width: 100%; | |
| border-collapse: separate; | |
| border-spacing: 0; | |
| font-size: var(--text-sm); | |
| } | |
| .glass-table thead th { | |
| padding: var(--space-sm) var(--space-md); | |
| text-align: left; | |
| font-size: var(--text-2xs); | |
| font-weight: 500; | |
| letter-spacing: 0.08em; | |
| text-transform: uppercase; | |
| color: var(--color-text-muted); | |
| border-bottom: 1px solid var(--glass-border-subtle); | |
| white-space: nowrap; | |
| cursor: pointer; | |
| user-select: none; | |
| transition: color var(--dur-fast); | |
| position: relative; | |
| } | |
| .glass-table thead th:hover { | |
| color: var(--color-text); | |
| } | |
| .glass-table thead th.is-sorted { | |
| color: var(--accent-aqua); | |
| } | |
| [data-theme="light"] .glass-table thead th.is-sorted { | |
| color: #0891b2; | |
| } | |
| .glass-table__sort-icon { | |
| margin-left: 5px; | |
| font-size: 0.6rem; | |
| opacity: 0.45; | |
| display: inline-block; | |
| transition: opacity var(--dur-fast), | |
| transform var(--dur-fast) var(--ease-liquid); | |
| } | |
| .glass-table thead th.is-sorted .glass-table__sort-icon { | |
| opacity: 1; | |
| } | |
| .glass-table thead th.is-sorted.sort-desc .glass-table__sort-icon { | |
| transform: rotate(180deg); | |
| } | |
| /* Checkbox column */ | |
| .glass-table th.col-check, | |
| .glass-table td.col-check { | |
| width: 44px; | |
| padding-right: 0; | |
| text-align: center; | |
| } | |
| /* Inline checkbox */ | |
| .glass-table-check { | |
| width: 16px; | |
| height: 16px; | |
| border-radius: 4px; | |
| border: 1px solid var(--glass-border); | |
| background: var(--glass-white); | |
| cursor: pointer; | |
| appearance: none; | |
| -webkit-appearance: none; | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| flex-shrink: 0; | |
| transition: background var(--dur-fast), border-color var(--dur-fast); | |
| vertical-align: middle; | |
| position: relative; | |
| } | |
| .glass-table-check::after { | |
| content: "✓"; | |
| font-size: 0.6rem; | |
| color: var(--accent-aqua); | |
| opacity: 0; | |
| transform: scale(0.4); | |
| transition: opacity var(--dur-fast), | |
| transform var(--dur-fast) var(--ease-liquid); | |
| position: absolute; | |
| } | |
| .glass-table-check:checked { | |
| background: rgba(94, 231, 223, 0.22); | |
| border-color: rgba(94, 231, 223, 0.65); | |
| } | |
| .glass-table-check:checked::after { | |
| opacity: 1; | |
| transform: scale(1); | |
| } | |
| [data-theme="light"] .glass-table-check { | |
| border-color: rgba(15, 23, 42, 0.22); | |
| background: rgba(255, 255, 255, 0.7); | |
| } | |
| [data-theme="light"] .glass-table-check:checked { | |
| background: rgba(8, 145, 178, 0.18); | |
| border-color: rgba(8, 145, 178, 0.55); | |
| } | |
| [data-theme="light"] .glass-table-check:checked::after { | |
| color: #0891b2; | |
| } | |
| /* Row states */ | |
| .glass-table tbody tr { | |
| transition: background var(--dur-fast); | |
| } | |
| .glass-table tbody tr:hover { | |
| background: var(--glass-white); | |
| } | |
| .glass-table tbody tr.is-selected { | |
| background: rgba(94, 231, 223, 0.07); | |
| } | |
| .glass-table tbody tr.is-selected:hover { | |
| background: rgba(94, 231, 223, 0.11); | |
| } | |
| [data-theme="light"] .glass-table tbody tr:hover { | |
| background: rgba(255, 255, 255, 0.55); | |
| } | |
| [data-theme="light"] .glass-table tbody tr.is-selected { | |
| background: rgba(8, 145, 178, 0.07); | |
| } | |
| [data-theme="light"] .glass-table tbody tr.is-selected:hover { | |
| background: rgba(8, 145, 178, 0.12); | |
| } | |
| .glass-table tbody td { | |
| padding: var(--space-sm) var(--space-md); | |
| border-bottom: 1px solid var(--glass-border-subtle); | |
| vertical-align: middle; | |
| } | |
| .glass-table tbody tr:last-child td { | |
| border-bottom: none; | |
| } | |
| [data-theme="light"] .glass-table tbody td, | |
| [data-theme="light"] .glass-table thead th { | |
| border-color: rgba(15, 23, 42, 0.08); | |
| } | |
| /* Cell types */ | |
| .glass-table__cell--num { | |
| text-align: right; | |
| font-variant-numeric: tabular-nums; | |
| } | |
| /* Member cell with avatar */ | |
| .glass-table-member { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-sm); | |
| } | |
| .glass-table-avatar { | |
| width: 32px; | |
| height: 32px; | |
| border-radius: 50%; | |
| flex-shrink: 0; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1rem; | |
| line-height: 1; | |
| border: 1.5px solid rgba(255, 255, 255, 0.2); | |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.22); | |
| } | |
| .glass-table-member__info { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1px; | |
| } | |
| .glass-table-member__name { | |
| font-weight: 500; | |
| font-size: var(--text-sm); | |
| } | |
| .glass-table-member__email { | |
| font-size: var(--text-2xs); | |
| opacity: 0.45; | |
| } | |
| /* Score mini bar */ | |
| .glass-table-score { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-xs); | |
| justify-content: flex-end; | |
| } | |
| .glass-table-score__num { | |
| font-size: var(--text-sm); | |
| font-variant-numeric: tabular-nums; | |
| font-weight: 500; | |
| min-width: 28px; | |
| text-align: right; | |
| } | |
| .glass-table-score__bar { | |
| width: 60px; | |
| height: 4px; | |
| border-radius: var(--radius-full); | |
| background: rgba(255, 255, 255, 0.12); | |
| overflow: hidden; | |
| flex-shrink: 0; | |
| } | |
| [data-theme="light"] .glass-table-score__bar { | |
| background: rgba(15, 23, 42, 0.1); | |
| } | |
| .glass-table-score__fill { | |
| height: 100%; | |
| border-radius: var(--radius-full); | |
| background: linear-gradient(90deg, var(--accent-aqua), var(--accent-violet)); | |
| transform-origin: left; | |
| animation: progress-grow 0.8s var(--ease-liquid) both; | |
| } | |
| /* Action button */ | |
| .glass-table-action { | |
| width: 28px; | |
| height: 28px; | |
| border-radius: var(--radius-sm); | |
| border: 1px solid var(--glass-border-subtle); | |
| background: transparent; | |
| color: var(--color-text); | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 0.75rem; | |
| opacity: 0.45; | |
| transition: opacity var(--dur-fast), background var(--dur-fast), | |
| border-color var(--dur-fast); | |
| } | |
| .glass-table-action:hover { | |
| opacity: 1; | |
| background: var(--glass-white); | |
| border-color: var(--glass-border); | |
| } | |
| /* ── Bulk action bar (shown when rows selected) ── */ | |
| .glass-table-bulk { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-sm); | |
| padding: var(--space-sm) var(--space-lg); | |
| border-bottom: 1px solid rgba(94, 231, 223, 0.2); | |
| background: rgba(94, 231, 223, 0.06); | |
| font-size: var(--text-xs); | |
| opacity: 0; | |
| height: 0; | |
| overflow: hidden; | |
| transition: opacity var(--dur-fast), height var(--dur-fast); | |
| } | |
| .glass-table-bulk.is-visible { | |
| opacity: 1; | |
| height: 44px; | |
| } | |
| [data-theme="light"] .glass-table-bulk { | |
| background: rgba(8, 145, 178, 0.06); | |
| border-color: rgba(8, 145, 178, 0.2); | |
| } | |
| .glass-table-bulk__count { | |
| font-weight: 500; | |
| color: var(--accent-aqua); | |
| } | |
| [data-theme="light"] .glass-table-bulk__count { | |
| color: #0891b2; | |
| } | |
| /* ── Pagination ── */ | |
| .glass-table-pagination { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: var(--space-sm) var(--space-lg); | |
| border-top: 1px solid var(--glass-border-subtle); | |
| font-size: var(--text-xs); | |
| color: var(--color-text-muted); | |
| flex-wrap: wrap; | |
| gap: var(--space-sm); | |
| } | |
| [data-theme="light"] .glass-table-pagination { | |
| border-top-color: rgba(15, 23, 42, 0.08); | |
| } | |
| .glass-table-pagination__info { | |
| opacity: 0.55; | |
| } | |
| .glass-table-pagination__controls { | |
| display: flex; | |
| align-items: center; | |
| gap: 4px; | |
| } | |
| .glass-table-page-btn { | |
| width: 28px; | |
| height: 28px; | |
| border-radius: var(--radius-sm); | |
| border: 1px solid var(--glass-border-subtle); | |
| background: transparent; | |
| color: var(--color-text); | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: var(--text-xs); | |
| font-family: var(--font-body); | |
| transition: background var(--dur-fast), border-color var(--dur-fast), | |
| color var(--dur-fast); | |
| } | |
| .glass-table-page-btn:hover:not(:disabled) { | |
| background: var(--glass-white); | |
| border-color: var(--glass-border); | |
| } | |
| .glass-table-page-btn.is-active { | |
| background: rgba(94, 231, 223, 0.18); | |
| border-color: rgba(94, 231, 223, 0.45); | |
| color: var(--accent-aqua); | |
| } | |
| .glass-table-page-btn:disabled { | |
| opacity: 0.25; | |
| cursor: not-allowed; | |
| } | |
| [data-theme="light"] .glass-table-page-btn.is-active { | |
| background: rgba(8, 145, 178, 0.14); | |
| border-color: rgba(8, 145, 178, 0.4); | |
| color: #0891b2; | |
| } | |
| [data-theme="light"] .glass-table-page-btn:hover:not(:disabled) { | |
| background: rgba(255, 255, 255, 0.65); | |
| } | |
| /* ── Responsive stacked cards ── */ | |
| @media (max-width: 640px) { | |
| .glass-table thead { | |
| display: none; | |
| } | |
| .glass-table tbody tr { | |
| display: block; | |
| border: 1px solid var(--glass-border-subtle); | |
| border-radius: var(--radius-md); | |
| margin-bottom: var(--space-sm); | |
| } | |
| .glass-table tbody td { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| border-bottom: none; | |
| border-top: 1px solid var(--glass-border-subtle); | |
| padding: 10px var(--space-md); | |
| } | |
| .glass-table tbody td:first-child { | |
| border-top: none; | |
| } | |
| .glass-table tbody td::before { | |
| content: attr(data-label); | |
| font-size: var(--text-2xs); | |
| letter-spacing: 0.06em; | |
| text-transform: uppercase; | |
| opacity: 0.45; | |
| } | |
| .glass-table-member { | |
| flex-direction: row; | |
| } | |
| .glass-table-score { | |
| justify-content: flex-end; | |
| } | |
| .glass-table-pagination { | |
| justify-content: center; | |
| } | |
| .glass-table-toolbar { | |
| flex-direction: column; | |
| align-items: flex-start; | |
| } | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 28. TWELVE LIQUID GLASS ANIMATIONS | |
| -------------------------------------------------------------------------- */ | |
| @keyframes liquid-morph { | |
| 0% { | |
| border-radius: 60% 40% 30% 70%/60% 30% 70% 40%; | |
| } | |
| 25% { | |
| border-radius: 30% 60% 70% 40%/50% 60% 30% 60%; | |
| } | |
| 50% { | |
| border-radius: 50% 60% 30% 60%/40% 40% 70% 60%; | |
| } | |
| 75% { | |
| border-radius: 60% 40% 60% 30%/60% 70% 40% 50%; | |
| } | |
| 100% { | |
| border-radius: 60% 40% 30% 70%/60% 30% 70% 40%; | |
| } | |
| } | |
| @keyframes shimmer { | |
| 0% { | |
| background-position: -200% center; | |
| } | |
| 100% { | |
| background-position: 200% center; | |
| } | |
| } | |
| @keyframes float { | |
| 0%, | |
| 100% { | |
| transform: translateY(0); | |
| } | |
| 50% { | |
| transform: translateY(-12px); | |
| } | |
| } | |
| @keyframes refraction { | |
| 0%, | |
| 100% { | |
| opacity: 0.15; | |
| transform: rotate(0deg) scale(1); | |
| } | |
| 50% { | |
| opacity: 0.35; | |
| transform: rotate(180deg) scale(1.2); | |
| } | |
| } | |
| @keyframes ripple { | |
| 0% { | |
| transform: scale(0); | |
| opacity: 0.5; | |
| } | |
| 100% { | |
| transform: scale(2.5); | |
| opacity: 0; | |
| } | |
| } | |
| @keyframes edge-glow { | |
| 0%, | |
| 100% { | |
| box-shadow: 0 0 0 0 rgba(94, 231, 223, 0); | |
| } | |
| 50% { | |
| box-shadow: 0 0 30px 6px rgba(94, 231, 223, 0.35); | |
| } | |
| } | |
| @keyframes breathing-blur { | |
| 0%, | |
| 100% { | |
| backdrop-filter: blur(10px); | |
| background: rgba(255, 255, 255, 0.1); | |
| } | |
| 50% { | |
| backdrop-filter: blur(30px); | |
| background: rgba(255, 255, 255, 0.22); | |
| } | |
| } | |
| @keyframes depth-shift { | |
| 0%, | |
| 100% { | |
| transform: perspective(400px) rotateY(0deg); | |
| } | |
| 50% { | |
| transform: perspective(400px) rotateY(8deg); | |
| } | |
| } | |
| @keyframes cascade-in { | |
| 0% { | |
| opacity: 0; | |
| transform: translateY(24px) scale(0.97); | |
| filter: blur(8px); | |
| } | |
| 100% { | |
| opacity: 1; | |
| transform: translateY(0) scale(1); | |
| filter: blur(0); | |
| } | |
| } | |
| @keyframes hue-shift { | |
| 0% { | |
| filter: hue-rotate(0deg) brightness(1); | |
| } | |
| 50% { | |
| filter: hue-rotate(40deg) brightness(1.1); | |
| } | |
| 100% { | |
| filter: hue-rotate(0deg) brightness(1); | |
| } | |
| } | |
| @keyframes liquid-drain { | |
| 0% { | |
| clip-path: inset(0 0 0 0 round 20px); | |
| opacity: 1; | |
| } | |
| 100% { | |
| clip-path: inset(0 0 100% 0 round 20px); | |
| opacity: 0; | |
| } | |
| } | |
| @keyframes tension { | |
| 0%, | |
| 100% { | |
| transform: scaleX(1) scaleY(1); | |
| } | |
| 30% { | |
| transform: scaleX(1.06) scaleY(0.94); | |
| } | |
| 60% { | |
| transform: scaleX(0.95) scaleY(1.05); | |
| } | |
| } | |
| @keyframes progress-grow { | |
| from { | |
| transform: scaleX(0); | |
| } | |
| to { | |
| transform: scaleX(1); | |
| } | |
| } | |
| .anim-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); | |
| gap: var(--space-md); | |
| } | |
| .anim-demo { | |
| border-radius: var(--radius-lg); | |
| padding: 28px 20px; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| gap: var(--space-md); | |
| cursor: pointer; | |
| min-height: 160px; | |
| text-align: center; | |
| } | |
| .anim-demo__orb { | |
| width: 60px; | |
| height: 60px; | |
| border-radius: 50%; | |
| background: linear-gradient(135deg, var(--accent-aqua), var(--accent-violet)); | |
| border: 1px solid rgba(255, 255, 255, 0.4); | |
| box-shadow: 0 4px 20px rgba(94, 231, 223, 0.3); | |
| } | |
| .anim-demo__label { | |
| font-size: var(--text-xs); | |
| letter-spacing: 0.08em; | |
| text-transform: uppercase; | |
| opacity: 0.6; | |
| } | |
| .anim-demo:nth-child(1) .anim-demo__orb { | |
| animation: liquid-morph 5s ease-in-out infinite; | |
| } | |
| .anim-demo:nth-child(2) .anim-demo__orb { | |
| background: linear-gradient( | |
| 90deg, | |
| rgba(94, 231, 223, 0) 0%, | |
| rgba(255, 255, 255, 0.6) 50%, | |
| rgba(94, 231, 223, 0) 100% | |
| ); | |
| background-size: 200% auto; | |
| animation: shimmer 2s linear infinite; | |
| } | |
| .anim-demo:nth-child(3) .anim-demo__orb { | |
| animation: float 3s ease-in-out infinite; | |
| } | |
| .anim-demo:nth-child(4) .anim-demo__orb { | |
| background: conic-gradient( | |
| var(--accent-aqua), | |
| var(--accent-violet), | |
| var(--accent-rose), | |
| var(--accent-aqua) | |
| ); | |
| animation: refraction 4s ease-in-out infinite; | |
| } | |
| .anim-demo:nth-child(5) { | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .anim-demo:nth-child(5) .anim-demo__orb { | |
| cursor: pointer; | |
| position: relative; | |
| } | |
| .anim-demo:nth-child(5) .anim-demo__orb::after { | |
| content: ""; | |
| position: absolute; | |
| inset: 0; | |
| border-radius: 50%; | |
| background: rgba(94, 231, 223, 0.5); | |
| animation: ripple 2s ease-out infinite; | |
| } | |
| .anim-demo:nth-child(6) .anim-demo__orb { | |
| animation: edge-glow 2.5s ease-in-out infinite; | |
| } | |
| .anim-demo:nth-child(7) { | |
| animation: breathing-blur 4s ease-in-out infinite; | |
| } | |
| .anim-demo:nth-child(8) { | |
| animation: depth-shift 4s ease-in-out infinite; | |
| } | |
| .anim-demo:nth-child(9) { | |
| animation: cascade-in var(--dur-slow) var(--ease-glass) both; | |
| animation-delay: 0.4s; | |
| } | |
| .anim-demo:nth-child(10) { | |
| animation: hue-shift 6s ease-in-out infinite; | |
| } | |
| .anim-demo:nth-child(11) .anim-demo__orb { | |
| animation: liquid-drain 2.5s ease-in-out infinite alternate; | |
| } | |
| .anim-demo:nth-child(12) { | |
| animation: tension 2s ease-in-out infinite; | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 29. PAGE LAYOUT & HERO | |
| -------------------------------------------------------------------------- */ | |
| .page { | |
| position: relative; | |
| z-index: 10; | |
| } | |
| .section { | |
| padding: var(--space-3xl) 0; | |
| animation: cascade-in var(--dur-slow) var(--ease-glass) both; | |
| } | |
| .section:nth-child(1) { | |
| animation-delay: 0.05s; | |
| } | |
| .section:nth-child(2) { | |
| animation-delay: 0.15s; | |
| } | |
| .section:nth-child(3) { | |
| animation-delay: 0.25s; | |
| } | |
| .section:nth-child(4) { | |
| animation-delay: 0.35s; | |
| } | |
| .section:nth-child(5) { | |
| animation-delay: 0.45s; | |
| } | |
| .section:nth-child(6) { | |
| animation-delay: 0.55s; | |
| } | |
| .section:nth-child(7) { | |
| animation-delay: 0.65s; | |
| } | |
| .container { | |
| width: min(1100px, 92vw); | |
| margin-inline: auto; | |
| } | |
| .section__header { | |
| display: flex; | |
| align-items: baseline; | |
| gap: var(--space-md); | |
| margin-bottom: var(--space-xl); | |
| } | |
| .section__title { | |
| font-family: var(--font-display); | |
| font-size: clamp(1.6rem, 3vw, 2.2rem); | |
| letter-spacing: 0.01em; | |
| } | |
| .section__subtitle { | |
| font-size: var(--text-xs); | |
| opacity: 0.45; | |
| letter-spacing: 0.06em; | |
| text-transform: uppercase; | |
| } | |
| .hero { | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| text-align: center; | |
| padding: 80px 24px; | |
| gap: var(--space-lg); | |
| } | |
| .hero__kicker { | |
| font-size: var(--text-xs); | |
| letter-spacing: 0.2em; | |
| text-transform: uppercase; | |
| opacity: 0.55; | |
| animation: cascade-in 0.7s var(--ease-glass) 0.1s both; | |
| } | |
| .hero__title { | |
| font-family: var(--font-display); | |
| font-size: clamp(3rem, 9vw, 7rem); | |
| line-height: 1; | |
| letter-spacing: -0.02em; | |
| background: linear-gradient( | |
| 135deg, | |
| #fff 30%, | |
| var(--accent-aqua) 65%, | |
| var(--accent-violet) 100% | |
| ); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| animation: cascade-in 0.8s var(--ease-glass) 0.2s both; | |
| } | |
| [data-theme="light"] .hero__title { | |
| background: linear-gradient(135deg, #0f172a 20%, #0369a1 60%, #7c3aed 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| } | |
| .hero__sub { | |
| max-width: 480px; | |
| line-height: 1.7; | |
| opacity: 0.65; | |
| font-size: var(--text-base); | |
| animation: cascade-in 0.8s var(--ease-glass) 0.3s both; | |
| } | |
| .hero__cta { | |
| display: flex; | |
| gap: var(--space-sm); | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| animation: cascade-in 0.8s var(--ease-glass) 0.4s both; | |
| } | |
| .stats { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: var(--space-md); | |
| margin-top: var(--space-3xl); | |
| animation: cascade-in 0.8s var(--ease-glass) 0.5s both; | |
| } | |
| .stats__item { | |
| border-radius: var(--radius-lg); | |
| padding: var(--space-lg); | |
| text-align: center; | |
| } | |
| .stats__num { | |
| font-family: var(--font-display); | |
| font-size: 2.2rem; | |
| background: linear-gradient(135deg, #fff, var(--accent-aqua)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| } | |
| [data-theme="light"] .stats__num { | |
| background: linear-gradient(135deg, #0f172a, #0369a1); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| } | |
| .stats__desc { | |
| font-size: var(--text-xs); | |
| opacity: 0.5; | |
| letter-spacing: 0.04em; | |
| margin-top: 4px; | |
| } | |
| .card-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); | |
| gap: var(--space-md); | |
| } | |
| .btn-row { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: var(--space-sm); | |
| align-items: center; | |
| } | |
| .tags-row { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: var(--space-xs); | |
| } | |
| .profile-card { | |
| border-radius: var(--radius-xl); | |
| padding: var(--space-xl); | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: var(--space-md); | |
| text-align: center; | |
| } | |
| .profile-card__avatar { | |
| width: 72px; | |
| height: 72px; | |
| border-radius: 50%; | |
| border: 2px solid var(--glass-border); | |
| background: linear-gradient(135deg, var(--accent-violet), var(--accent-aqua)); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.8rem; | |
| line-height: 1; | |
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25); | |
| } | |
| .profile-card__name { | |
| font-family: var(--font-display); | |
| font-size: 1.3rem; | |
| } | |
| .profile-card__role { | |
| font-size: var(--text-sm); | |
| opacity: 0.5; | |
| } | |
| .progress-wrap { | |
| margin-top: var(--space-sm); | |
| width: 100%; | |
| } | |
| .progress-bar { | |
| height: 6px; | |
| border-radius: var(--radius-full); | |
| background: rgba(255, 255, 255, 0.12); | |
| overflow: hidden; | |
| margin-top: 6px; | |
| } | |
| .progress-bar__fill { | |
| height: 100%; | |
| border-radius: var(--radius-full); | |
| background: linear-gradient(90deg, var(--accent-aqua), var(--accent-violet)); | |
| animation: progress-grow 1.2s var(--ease-liquid) 0.5s both; | |
| transform-origin: left; | |
| } | |
| .progress-label { | |
| display: flex; | |
| justify-content: space-between; | |
| font-size: var(--text-xs); | |
| opacity: 0.55; | |
| } | |
| .glass-form { | |
| border-radius: var(--radius-2xl); | |
| padding: var(--space-2xl); | |
| } | |
| .glass-form__row { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: var(--space-md); | |
| margin-bottom: var(--space-md); | |
| } | |
| .glass-form__row--full { | |
| grid-template-columns: 1fr; | |
| } | |
| label { | |
| display: block; | |
| font-size: var(--text-xs); | |
| letter-spacing: 0.06em; | |
| text-transform: uppercase; | |
| opacity: 0.55; | |
| margin-bottom: var(--space-xs); | |
| color: var(--color-text); | |
| } | |
| .notif { | |
| border-radius: var(--radius-lg); | |
| padding: 18px 22px; | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-md); | |
| margin-bottom: var(--space-sm); | |
| transition: transform var(--dur-fast) var(--ease-liquid); | |
| } | |
| .notif:hover { | |
| transform: translateX(6px); | |
| } | |
| .notif__icon { | |
| width: 40px; | |
| height: 40px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.1rem; | |
| line-height: 1; | |
| flex-shrink: 0; | |
| } | |
| .notif__icon--aqua { | |
| background: rgba(94, 231, 223, 0.2); | |
| } | |
| .notif__icon--violet { | |
| background: rgba(180, 144, 245, 0.2); | |
| } | |
| .notif__icon--amber { | |
| background: rgba(255, 210, 127, 0.2); | |
| } | |
| .notif__title { | |
| font-size: var(--text-md); | |
| font-weight: 500; | |
| margin-bottom: 3px; | |
| } | |
| .notif__desc { | |
| font-size: var(--text-xs); | |
| opacity: 0.55; | |
| } | |
| .notif__time { | |
| margin-left: auto; | |
| font-size: var(--text-2xs); | |
| opacity: 0.4; | |
| white-space: nowrap; | |
| } | |
| .divider { | |
| height: 1px; | |
| background: linear-gradient( | |
| 90deg, | |
| transparent, | |
| var(--glass-border), | |
| transparent | |
| ); | |
| margin: var(--space-3xl) 0; | |
| } | |
| .footer { | |
| padding: var(--space-2xl) 0 var(--space-3xl); | |
| text-align: center; | |
| } | |
| .footer__text { | |
| font-size: var(--text-xs); | |
| opacity: 0.35; | |
| letter-spacing: 0.06em; | |
| } | |
| .glass-toggle { | |
| display: flex; | |
| border-radius: var(--radius-full); | |
| padding: 4px; | |
| gap: 4px; | |
| } | |
| .glass-toggle__opt { | |
| padding: 8px 20px; | |
| border-radius: var(--radius-full); | |
| font-size: var(--text-sm); | |
| cursor: pointer; | |
| transition: background var(--dur-fast), box-shadow var(--dur-fast); | |
| } | |
| .glass-toggle__opt.is-on { | |
| background: var(--glass-white-md); | |
| box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); | |
| } | |
| .glass-range { | |
| -webkit-appearance: none; | |
| appearance: none; | |
| width: 100%; | |
| height: 6px; | |
| border-radius: var(--radius-full); | |
| background: rgba(255, 255, 255, 0.15); | |
| outline: none; | |
| cursor: pointer; | |
| } | |
| .glass-range::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| background: linear-gradient(135deg, var(--accent-aqua), var(--accent-violet)); | |
| border: 2px solid rgba(255, 255, 255, 0.5); | |
| box-shadow: 0 2px 10px rgba(94, 231, 223, 0.4); | |
| cursor: pointer; | |
| transition: transform var(--dur-fast) var(--ease-liquid); | |
| } | |
| .glass-range::-webkit-slider-thumb:active { | |
| transform: scale(1.3); | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 30. THEME TOGGLE | |
| -------------------------------------------------------------------------- */ | |
| .theme-toggle-btn { | |
| position: fixed; | |
| top: var(--space-md); | |
| right: var(--space-md); | |
| z-index: var(--z-toast); | |
| width: 48px; | |
| height: 48px; | |
| border-radius: 50%; | |
| border: none; | |
| cursor: pointer; | |
| font-size: 1.2rem; | |
| line-height: 1; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: transform var(--dur-fast) var(--ease-liquid), | |
| box-shadow var(--dur-fast); | |
| } | |
| .theme-toggle-btn span { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| line-height: 1; | |
| } | |
| .theme-toggle-btn:hover { | |
| transform: scale(1.1) rotate(15deg); | |
| } | |
| .theme-toggle-btn .icon-dark { | |
| display: block; | |
| } | |
| .theme-toggle-btn .icon-light { | |
| display: none; | |
| } | |
| [data-theme="light"] .theme-toggle-btn .icon-dark { | |
| display: none; | |
| } | |
| [data-theme="light"] .theme-toggle-btn .icon-light { | |
| display: block; | |
| } | |
| /* -------------------------------------------------------------------------- | |
| 31. RESPONSIVE | |
| -------------------------------------------------------------------------- */ | |
| @media (max-width: 768px) { | |
| .glass-form__row { | |
| grid-template-columns: 1fr; | |
| } | |
| .glass-stepper:not(.glass-stepper--vertical) .glass-step__label { | |
| display: none; | |
| } | |
| } | |
| @media (max-width: 640px) { | |
| .stats { | |
| grid-template-columns: 1fr; | |
| } | |
| .hero__title { | |
| font-size: 3.2rem; | |
| } | |
| } | |
| /* ========================================================================== | |
| LIGHT THEME — Component-level overrides | |
| Accent colour tokens cascade, but any component that hardcoded | |
| rgba() with the old dark-palette values needs explicit overrides. | |
| ========================================================================== */ | |
| /* ── Glass surface dark variant (used on dark cards) ── */ | |
| [data-theme="light"] .glass--dark { | |
| background: rgba(15, 23, 42, 0.06); | |
| border-color: rgba(15, 23, 42, 0.14); | |
| } | |
| /* ── Badges — replace rgba() tints built from dark-theme accents ── */ | |
| [data-theme="light"] .glass-badge--aqua { | |
| background: rgba(8, 145, 178, 0.12); | |
| border-color: rgba(8, 145, 178, 0.35); | |
| } | |
| [data-theme="light"] .glass-badge--violet { | |
| background: rgba(109, 40, 217, 0.1); | |
| border-color: rgba(109, 40, 217, 0.3); | |
| box-shadow: none; | |
| } | |
| [data-theme="light"] .glass-badge--rose { | |
| background: rgba(190, 24, 93, 0.1); | |
| border-color: rgba(190, 24, 93, 0.3); | |
| } | |
| [data-theme="light"] .glass-badge--amber { | |
| background: rgba(180, 83, 9, 0.1); | |
| border-color: rgba(180, 83, 9, 0.3); | |
| } | |
| [data-theme="light"] .glass-badge--lime { | |
| background: rgba(21, 128, 61, 0.1); | |
| border-color: rgba(21, 128, 61, 0.3); | |
| } | |
| [data-theme="light"] .glass-badge--error { | |
| background: rgba(185, 28, 28, 0.1); | |
| border-color: rgba(185, 28, 28, 0.3); | |
| color: #b91c1c; | |
| } | |
| /* ── Tags ── */ | |
| [data-theme="light"] .glass-tag { | |
| background: rgba(255, 255, 255, 0.7); | |
| border-color: rgba(15, 23, 42, 0.14); | |
| color: #0f172a; | |
| } | |
| [data-theme="light"] .glass-tag:hover { | |
| background: rgba(255, 255, 255, 0.88); | |
| } | |
| /* ── Nav + Tabs ── */ | |
| [data-theme="light"] .glass-nav__item--active, | |
| [data-theme="light"] .glass-tab[aria-selected="true"] { | |
| background: rgba(255, 255, 255, 0.78); | |
| border-color: rgba(15, 23, 42, 0.16); | |
| box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); | |
| } | |
| [data-theme="light"] .glass-tab:hover { | |
| background: rgba(255, 255, 255, 0.55); | |
| } | |
| [data-theme="light"] .glass-toggle__opt.is-on { | |
| background: rgba(255, 255, 255, 0.78); | |
| box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08); | |
| } | |
| /* ── Inputs ── */ | |
| [data-theme="light"] .glass-input, | |
| [data-theme="light"] .glass-textarea, | |
| [data-theme="light"] .glass-select { | |
| background: rgba(255, 255, 255, 0.72); | |
| border-color: rgba(15, 23, 42, 0.16); | |
| color: #0f172a; | |
| } | |
| [data-theme="light"] .glass-input:focus, | |
| [data-theme="light"] .glass-textarea:focus, | |
| [data-theme="light"] .glass-select:focus { | |
| border-color: rgba(8, 145, 178, 0.55); | |
| box-shadow: 0 0 0 3px rgba(8, 145, 178, 0.14), var(--shadow-glass); | |
| } | |
| [data-theme="light"] .glass-input::placeholder, | |
| [data-theme="light"] .glass-textarea::placeholder { | |
| color: rgba(15, 23, 42, 0.38); | |
| opacity: 1; | |
| } | |
| /* ── Checkboxes / Radios ── */ | |
| [data-theme="light"] .glass-check-box, | |
| [data-theme="light"] .glass-radio-box { | |
| background: rgba(255, 255, 255, 0.72); | |
| border-color: rgba(15, 23, 42, 0.22); | |
| } | |
| [data-theme="light"] .glass-check-label input:checked + .glass-check-box, | |
| [data-theme="light"] .glass-check-label input:checked + .glass-radio-box { | |
| background: rgba(8, 145, 178, 0.15); | |
| border-color: rgba(8, 145, 178, 0.55); | |
| box-shadow: 0 0 10px rgba(8, 145, 178, 0.18); | |
| } | |
| [data-theme="light"] .glass-check-box::after { | |
| color: #0891b2; | |
| } | |
| /* ── Switch ── */ | |
| [data-theme="light"] .glass-switch-track { | |
| background: rgba(255, 255, 255, 0.72); | |
| border-color: rgba(15, 23, 42, 0.22); | |
| } | |
| [data-theme="light"] .glass-switch-thumb { | |
| background: rgba(15, 23, 42, 0.45); | |
| } | |
| [data-theme="light"] .glass-switch-label input:checked + .glass-switch-track { | |
| background: rgba(8, 145, 178, 0.2); | |
| border-color: rgba(8, 145, 178, 0.55); | |
| box-shadow: 0 0 10px rgba(8, 145, 178, 0.15); | |
| } | |
| [data-theme="light"] | |
| .glass-switch-label | |
| input:checked | |
| + .glass-switch-track | |
| .glass-switch-thumb { | |
| background: #0891b2; | |
| } | |
| /* ── Buttons ── */ | |
| [data-theme="light"] .glass-btn--ghost { | |
| background: rgba(255, 255, 255, 0.65); | |
| border-color: rgba(15, 23, 42, 0.18); | |
| } | |
| [data-theme="light"] .glass-btn--ghost:hover { | |
| background: rgba(255, 255, 255, 0.84); | |
| border-color: rgba(15, 23, 42, 0.25); | |
| } | |
| [data-theme="light"] .glass-btn--primary { | |
| background: linear-gradient( | |
| 135deg, | |
| rgba(8, 145, 178, 0.55) 0%, | |
| rgba(37, 99, 235, 0.55) 100% | |
| ); | |
| border-color: rgba(8, 145, 178, 0.5); | |
| color: #fff; | |
| } | |
| [data-theme="light"] .glass-btn--primary:hover { | |
| background: linear-gradient( | |
| 135deg, | |
| rgba(8, 145, 178, 0.75) 0%, | |
| rgba(37, 99, 235, 0.75) 100% | |
| ); | |
| } | |
| [data-theme="light"] .glass-btn--accent { | |
| background: linear-gradient( | |
| 135deg, | |
| rgba(109, 40, 217, 0.45) 0%, | |
| rgba(190, 24, 93, 0.45) 100% | |
| ); | |
| border-color: rgba(109, 40, 217, 0.4); | |
| color: #fff; | |
| } | |
| /* ── Modal ── */ | |
| /* ── Modal dismiss (handled in modal block above) ── */ | |
| /* ── Accordion ── */ | |
| [data-theme="light"] .glass-accordion-trigger:hover { | |
| background: rgba(255, 255, 255, 0.55); | |
| } | |
| [data-theme="light"] .glass-accordion-icon { | |
| background: rgba(255, 255, 255, 0.65); | |
| border-color: rgba(15, 23, 42, 0.18); | |
| } | |
| [data-theme="light"] .glass-accordion-item.is-open .glass-accordion-icon { | |
| background: rgba(8, 145, 178, 0.15); | |
| border-color: rgba(8, 145, 178, 0.45); | |
| } | |
| /* ── Dropdown ── */ | |
| [data-theme="light"] .glass-dropdown-item:hover { | |
| background: rgba(15, 23, 42, 0.06); | |
| } | |
| [data-theme="light"] .glass-dropdown-item.is-active { | |
| background: rgba(8, 145, 178, 0.12); | |
| color: #0891b2; | |
| } | |
| [data-theme="light"] .glass-dropdown-item--danger { | |
| color: #b91c1c; | |
| } | |
| [data-theme="light"] .glass-dropdown-item--danger:hover { | |
| background: rgba(185, 28, 28, 0.08); | |
| } | |
| [data-theme="light"] .glass-dropdown-divider { | |
| background: rgba(15, 23, 42, 0.1); | |
| } | |
| /* ── Toast ── */ | |
| [data-theme="light"] .glass-toast--success { | |
| border-color: rgba(21, 128, 61, 0.35) !important; | |
| box-shadow: 0 4px 24px rgba(21, 128, 61, 0.14) !important; | |
| } | |
| [data-theme="light"] .glass-toast--error { | |
| border-color: rgba(185, 28, 28, 0.35) !important; | |
| box-shadow: 0 4px 24px rgba(185, 28, 28, 0.14) !important; | |
| } | |
| [data-theme="light"] .glass-toast--warning { | |
| border-color: rgba(180, 83, 9, 0.35) !important; | |
| box-shadow: 0 4px 24px rgba(180, 83, 9, 0.14) !important; | |
| } | |
| [data-theme="light"] .glass-toast--info { | |
| border-color: rgba(8, 145, 178, 0.35) !important; | |
| box-shadow: 0 4px 24px rgba(8, 145, 178, 0.14) !important; | |
| } | |
| /* ── Chips ── */ | |
| [data-theme="light"] .glass-chip { | |
| color: #0f172a; | |
| } | |
| [data-theme="light"] .glass-chip:hover { | |
| background: rgba(255, 255, 255, 0.75); | |
| } | |
| [data-theme="light"] .glass-chip--aqua { | |
| background: rgba(8, 145, 178, 0.1); | |
| border-color: rgba(8, 145, 178, 0.3); | |
| color: #0891b2; | |
| } | |
| [data-theme="light"] .glass-chip--violet { | |
| background: rgba(109, 40, 217, 0.1); | |
| border-color: rgba(109, 40, 217, 0.28); | |
| color: #6d28d9; | |
| } | |
| [data-theme="light"] .glass-chip--rose { | |
| background: rgba(190, 24, 93, 0.1); | |
| border-color: rgba(190, 24, 93, 0.28); | |
| color: #be185d; | |
| } | |
| [data-theme="light"] .glass-chip--amber { | |
| background: rgba(180, 83, 9, 0.1); | |
| border-color: rgba(180, 83, 9, 0.28); | |
| color: #b45309; | |
| } | |
| [data-theme="light"] .glass-chip__dismiss { | |
| background: rgba(15, 23, 42, 0.1); | |
| border-color: rgba(15, 23, 42, 0.18); | |
| } | |
| [data-theme="light"] .glass-chip__dismiss:hover { | |
| background: rgba(15, 23, 42, 0.2); | |
| } | |
| [data-theme="light"] .glass-chip.is-selected { | |
| background: linear-gradient( | |
| 135deg, | |
| rgba(8, 145, 178, 0.18) 0%, | |
| rgba(109, 40, 217, 0.18) 100% | |
| ); | |
| border-color: rgba(8, 145, 178, 0.45); | |
| } | |
| [data-theme="light"] .glass-chip-input-wrap { | |
| border-color: rgba(8, 145, 178, 0.45); | |
| background: rgba(8, 145, 178, 0.08); | |
| } | |
| [data-theme="light"] .glass-chip-add { | |
| border-color: rgba(15, 23, 42, 0.25); | |
| color: #0f172a; | |
| } | |
| [data-theme="light"] .glass-chip-add:hover { | |
| background: rgba(255, 255, 255, 0.65); | |
| } | |
| [data-theme="light"] .glass-chip-input { | |
| color: #0f172a; | |
| } | |
| [data-theme="light"] .glass-chip-input-confirm { | |
| background: rgba(8, 145, 178, 0.2); | |
| border-color: rgba(8, 145, 178, 0.4); | |
| color: #0891b2; | |
| } | |
| /* ── Table ── */ | |
| [data-theme="light"] .glass-table thead th { | |
| color: #0f172a; | |
| } | |
| [data-theme="light"] .glass-table thead th.is-sorted { | |
| color: #0891b2; | |
| } | |
| [data-theme="light"] .glass-table tbody tr:hover { | |
| background: rgba(255, 255, 255, 0.55); | |
| } | |
| [data-theme="light"] .glass-table tbody td, | |
| [data-theme="light"] .glass-table thead th { | |
| border-color: rgba(15, 23, 42, 0.1); | |
| } | |
| /* ── Stepper ── */ | |
| [data-theme="light"] .glass-step__node { | |
| background: rgba(255, 255, 255, 0.72); | |
| border-color: rgba(15, 23, 42, 0.22); | |
| color: #0f172a; | |
| } | |
| [data-theme="light"] .glass-step.is-complete .glass-step__node { | |
| background: rgba(8, 145, 178, 0.18); | |
| border-color: rgba(8, 145, 178, 0.55); | |
| box-shadow: 0 0 12px rgba(8, 145, 178, 0.18); | |
| } | |
| [data-theme="light"] .glass-step.is-active .glass-step__node { | |
| background: rgba(109, 40, 217, 0.18); | |
| border-color: rgba(109, 40, 217, 0.55); | |
| box-shadow: 0 0 16px rgba(109, 40, 217, 0.2); | |
| } | |
| [data-theme="light"] .glass-step__connector { | |
| background: rgba(15, 23, 42, 0.15); | |
| } | |
| [data-theme="light"] .glass-step.is-complete .glass-step__connector { | |
| background: rgba(8, 145, 178, 0.4); | |
| } | |
| /* ── Skeleton ── */ | |
| [data-theme="light"] .skeleton { | |
| background: rgba(15, 23, 42, 0.08); | |
| } | |
| [data-theme="light"] .skeleton::after { | |
| background: linear-gradient( | |
| 90deg, | |
| rgba(255, 255, 255, 0) 0%, | |
| rgba(255, 255, 255, 0.55) 40%, | |
| rgba(255, 255, 255, 0.75) 50%, | |
| rgba(255, 255, 255, 0.55) 60%, | |
| rgba(255, 255, 255, 0) 100% | |
| ); | |
| } | |
| /* ── Breadcrumb ── */ | |
| [data-theme="light"] .glass-breadcrumb__crumb:hover { | |
| background: rgba(15, 23, 42, 0.07); | |
| } | |
| [data-theme="light"] .glass-breadcrumb__crumb--active { | |
| background: rgba(255, 255, 255, 0.78); | |
| border-color: rgba(15, 23, 42, 0.16); | |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); | |
| } | |
| [data-theme="light"] .glass-breadcrumb-dropdown__item:hover { | |
| background: rgba(15, 23, 42, 0.07); | |
| } | |
| /* ── Avatar ── */ | |
| [data-theme="light"] .glass-avatar { | |
| border-color: rgba(255, 255, 255, 0.8); | |
| } | |
| [data-theme="light"] .glass-avatar--overflow { | |
| background: rgba(255, 255, 255, 0.7); | |
| border-color: rgba(15, 23, 42, 0.18); | |
| color: rgba(15, 23, 42, 0.65); | |
| } | |
| [data-theme="light"] .glass-avatar-popover { | |
| color: #0f172a; | |
| } | |
| /* ── Notifications ── */ | |
| [data-theme="light"] .notif__icon--aqua { | |
| background: rgba(8, 145, 178, 0.14); | |
| } | |
| [data-theme="light"] .notif__icon--violet { | |
| background: rgba(109, 40, 217, 0.12); | |
| } | |
| [data-theme="light"] .notif__icon--amber { | |
| background: rgba(180, 83, 9, 0.12); | |
| } | |
| /* ── Range slider ── */ | |
| [data-theme="light"] .glass-range { | |
| background: rgba(15, 23, 42, 0.15); | |
| } | |
| /* ── Tooltip ── */ | |
| [data-theme="light"] .glass-tooltip { | |
| color: #0f172a; | |
| } | |
| [data-theme="light"] .glass-tooltip::after { | |
| border-top-color: rgba(15, 23, 42, 0.18); | |
| } | |
| /* ── Divider ── */ | |
| [data-theme="light"] .divider { | |
| background: linear-gradient( | |
| 90deg, | |
| transparent, | |
| rgba(15, 23, 42, 0.14), | |
| transparent | |
| ); | |
| } | |
| /* ── Modal backdrop (handled in modal block above) ── */ | |
| /* ── Section subtitle ── */ | |
| [data-theme="light"] .section__subtitle { | |
| color: rgba(15, 23, 42, 0.45); | |
| opacity: 1; | |
| } | |
| [data-theme="light"] .glass-card__label { | |
| color: rgba(15, 23, 42, 0.45); | |
| opacity: 1; | |
| } | |
| [data-theme="light"] .glass-card__body { | |
| color: rgba(15, 23, 42, 0.75); | |
| opacity: 1; | |
| } | |
| [data-theme="light"] .footer__text { | |
| color: rgba(15, 23, 42, 0.38); | |
| opacity: 1; | |
| } | |
| [data-theme="light"] .hero__sub { | |
| color: rgba(15, 23, 42, 0.65); | |
| opacity: 1; | |
| } | |
| [data-theme="light"] .stats__desc { | |
| color: rgba(15, 23, 42, 0.52); | |
| opacity: 1; | |
| } | |
| /* ── Profile card ── */ | |
| [data-theme="light"] .profile-card__role { | |
| color: rgba(15, 23, 42, 0.52); | |
| opacity: 1; | |
| } | |
| [data-theme="light"] .progress-bar { | |
| background: rgba(15, 23, 42, 0.12); | |
| } | |
| [data-theme="light"] .progress-label { | |
| color: rgba(15, 23, 42, 0.55); | |
| opacity: 1; | |
| } | |
| [data-theme="light"] .notif__desc { | |
| color: rgba(15, 23, 42, 0.55); | |
| opacity: 1; | |
| } | |
| [data-theme="light"] .notif__time { | |
| color: rgba(15, 23, 42, 0.4); | |
| opacity: 1; | |
| } | |
| [data-theme="light"] .glass-avatar-group__meta { | |
| color: rgba(15, 23, 42, 0.5); | |
| opacity: 1; | |
| } | |
| [data-theme="light"] .glass-accordion-content { | |
| color: rgba(15, 23, 42, 0.72); | |
| opacity: 1; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment