feat: initial commit web-cad-neu with docker-compose, frontend and backend
This commit is contained in:
@@ -0,0 +1,340 @@
|
||||
// web-cad v6 – Interactions (theme, tabs, drawers, mobile right-tab-bar)
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// ===================== THEME TOGGLE =====================
|
||||
const themeBtn = document.getElementById('theme-toggle');
|
||||
const html = document.documentElement;
|
||||
if (themeBtn) {
|
||||
themeBtn.addEventListener('click', () => {
|
||||
const cur = html.getAttribute('data-theme');
|
||||
html.setAttribute('data-theme', cur === 'dark' ? 'light' : 'dark');
|
||||
});
|
||||
}
|
||||
|
||||
// ===================== RIGHTBAR (desktop) TAB SWITCHING =====================
|
||||
document.querySelectorAll('.rightbar-tab').forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
const panel = tab.dataset.panel;
|
||||
document.querySelectorAll('.rightbar-tab').forEach(t => { t.classList.remove('active'); t.setAttribute('aria-selected', 'false'); });
|
||||
document.querySelectorAll('.rightbar-panel').forEach(p => p.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
tab.setAttribute('aria-selected', 'true');
|
||||
const target = document.querySelector(`[data-panel-content="${panel}"]`);
|
||||
if (target) target.classList.add('active');
|
||||
// sync mobile drawer if open
|
||||
const dTab = document.querySelector(`[data-drawer-tab="${panel}"]`);
|
||||
if (dTab) dTab.click();
|
||||
});
|
||||
});
|
||||
|
||||
// ===================== DRAWER TAB SWITCHING (mobile) =====================
|
||||
document.querySelectorAll('.drawer-tab').forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
const panel = tab.dataset.drawerTab;
|
||||
document.querySelectorAll('.drawer-tab').forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
// Re-render drawer body for this panel
|
||||
renderDrawerRight(panel);
|
||||
// sync desktop rightbar
|
||||
const rTab = document.querySelector(`.rightbar-tab[data-panel="${panel}"]`);
|
||||
if (rTab) {
|
||||
document.querySelectorAll('.rightbar-tab').forEach(t => t.classList.remove('active'));
|
||||
rTab.classList.add('active');
|
||||
document.querySelectorAll('.rightbar-panel').forEach(p => p.classList.remove('active'));
|
||||
const target = document.querySelector(`[data-panel-content="${panel}"]`);
|
||||
if (target) target.classList.add('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ===================== RIGHT-TAB-BAR (mobile vertical) =====================
|
||||
document.querySelectorAll('.right-tab-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const which = btn.dataset.drawer;
|
||||
document.querySelectorAll('.right-tab-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
// activate drawer tab + open drawer
|
||||
const dTab = document.querySelector(`[data-drawer-tab="${which}"]`);
|
||||
if (dTab) dTab.click();
|
||||
openDrawer('right');
|
||||
});
|
||||
});
|
||||
|
||||
// ===================== DRAWER OPEN/CLOSE =====================
|
||||
const scrim = document.getElementById('scrim');
|
||||
const drawerLeft = document.getElementById('drawer-left');
|
||||
const drawerRight = document.getElementById('drawer-right');
|
||||
|
||||
function openDrawer(which) {
|
||||
const drawerLeftBody = document.getElementById('drawer-left-body');
|
||||
const drawerRightBody = document.getElementById('drawer-right-body');
|
||||
if (which === 'left' && drawerLeft) {
|
||||
drawerLeft.classList.add('open');
|
||||
drawerLeft.setAttribute('aria-hidden', 'false');
|
||||
// Use children.length instead of innerHTML.trim() because placeholder HTML comments would prevent re-render
|
||||
if (drawerLeftBody && drawerLeftBody.children.length === 0) {
|
||||
renderDrawerLeft();
|
||||
}
|
||||
}
|
||||
if (which === 'right' && drawerRight) {
|
||||
drawerRight.classList.add('open');
|
||||
drawerRight.setAttribute('aria-hidden', 'false');
|
||||
if (drawerRightBody && drawerRightBody.children.length === 0) {
|
||||
const active = document.querySelector('.drawer-tab.active');
|
||||
renderDrawerRight(active ? active.dataset.drawerTab : 'tool');
|
||||
}
|
||||
}
|
||||
if (scrim) scrim.classList.add('show');
|
||||
document.body.classList.add('drawer-open');
|
||||
}
|
||||
|
||||
function closeDrawers() {
|
||||
if (drawerLeft) { drawerLeft.classList.remove('open'); drawerLeft.setAttribute('aria-hidden', 'true'); }
|
||||
if (drawerRight) { drawerRight.classList.remove('open'); drawerRight.setAttribute('aria-hidden', 'true'); }
|
||||
if (scrim) scrim.classList.remove('show');
|
||||
document.body.classList.remove('drawer-open');
|
||||
}
|
||||
|
||||
// Hamburger opens left drawer
|
||||
const hamburger = document.getElementById('hamburger-btn');
|
||||
if (hamburger) {
|
||||
hamburger.addEventListener('click', () => openDrawer('left'));
|
||||
}
|
||||
|
||||
// Close buttons
|
||||
document.querySelectorAll('[data-close-drawer]').forEach(b => {
|
||||
b.addEventListener('click', closeDrawers);
|
||||
});
|
||||
|
||||
// Scrim click closes
|
||||
if (scrim) scrim.addEventListener('click', closeDrawers);
|
||||
|
||||
// ESC key closes
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') closeDrawers();
|
||||
});
|
||||
|
||||
// ===================== RENDER DRAWER LEFT (clone leftbar) =====================
|
||||
function renderDrawerLeft() {
|
||||
const src = document.querySelector('.leftbar');
|
||||
const dst = document.getElementById('drawer-left-body');
|
||||
if (!src || !dst) return;
|
||||
// Clone leftbar body (skip header which has toggle)
|
||||
const clone = src.cloneNode(true);
|
||||
// Remove the inner toggle button (drawer has its own close)
|
||||
const innerToggle = clone.querySelector('.leftbar-toggle');
|
||||
if (innerToggle) innerToggle.remove();
|
||||
// Remove header (drawer has its own header)
|
||||
const header = clone.querySelector('.leftbar-header');
|
||||
if (header) header.remove();
|
||||
// Remove footer (not needed in drawer)
|
||||
const footer = clone.querySelector('.leftbar-footer');
|
||||
if (footer) footer.remove();
|
||||
// Mark as drawer version + force display (the original .leftbar is hidden on mobile)
|
||||
clone.classList.add('drawer-content-left');
|
||||
clone.removeAttribute('id');
|
||||
clone.style.cssText = 'display: flex; flex-direction: column; flex: 1; min-height: 0; border: none; background: transparent;';
|
||||
dst.innerHTML = '';
|
||||
dst.appendChild(clone);
|
||||
// Wire up tool buttons inside drawer
|
||||
dst.querySelectorAll('.tool-btn').forEach(b => {
|
||||
b.addEventListener('click', () => {
|
||||
document.querySelectorAll('.tool-btn').forEach(x => x.classList.remove('active'));
|
||||
b.classList.add('active');
|
||||
// Close drawer on tool selection (mobile UX)
|
||||
setTimeout(() => closeDrawers(), 200);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ===================== RENDER DRAWER RIGHT (clone rightbar panel) =====================
|
||||
function renderDrawerRight(panel) {
|
||||
const src = document.querySelector(`[data-panel-content="${panel}"]`);
|
||||
const dst = document.getElementById('drawer-right-body');
|
||||
const titleEl = document.getElementById('drawer-right-title');
|
||||
if (!src || !dst) return;
|
||||
const labels = { tool: 'Werkzeug', layer: 'Layer', library: 'Bibliothek', ki: 'KI Copilot' };
|
||||
if (titleEl) titleEl.textContent = labels[panel] || 'Panel';
|
||||
dst.innerHTML = '';
|
||||
const clone = src.cloneNode(true);
|
||||
clone.classList.add('active');
|
||||
clone.style.cssText = 'display: block; width: 100%;';
|
||||
dst.appendChild(clone);
|
||||
// Re-wire KI chips and library categories inside drawer
|
||||
dst.querySelectorAll('.ki-chip').forEach(chip => {
|
||||
chip.addEventListener('click', () => {
|
||||
const input = dst.querySelector('.ki-input');
|
||||
if (input) { input.value = chip.textContent.trim(); input.focus(); }
|
||||
});
|
||||
});
|
||||
dst.querySelectorAll('.lib-cat').forEach(cat => {
|
||||
cat.addEventListener('click', () => {
|
||||
dst.querySelectorAll('.lib-cat').forEach(c => c.classList.remove('active'));
|
||||
cat.classList.add('active');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ===================== RIBBON TAB SWITCHING =====================
|
||||
document.querySelectorAll('.ribbon-tab').forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
document.querySelectorAll('.ribbon-tab').forEach(t => { t.classList.remove('active'); t.setAttribute('aria-selected', 'false'); });
|
||||
tab.classList.add('active');
|
||||
tab.setAttribute('aria-selected', 'true');
|
||||
});
|
||||
});
|
||||
|
||||
// ===================== TOOL SELECTION (desktop) =====================
|
||||
document.querySelectorAll('.leftbar .tool-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
document.querySelectorAll('.leftbar .tool-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// ===================== LEFTBAR TOGGLE (desktop) =====================
|
||||
const leftToggle = document.getElementById('leftbar-toggle');
|
||||
if (leftToggle) {
|
||||
leftToggle.addEventListener('click', () => {
|
||||
const main = document.querySelector('.main');
|
||||
if (main) main.classList.toggle('left-collapsed');
|
||||
});
|
||||
}
|
||||
|
||||
// ===================== CANVAS VIEW TABS =====================
|
||||
document.querySelectorAll('.canvas-view-tab').forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
document.querySelectorAll('.canvas-view-tab').forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// ===================== STATUS BAR TOGGLES =====================
|
||||
document.querySelectorAll('.status-item').forEach(item => {
|
||||
item.addEventListener('click', () => item.classList.toggle('active'));
|
||||
});
|
||||
|
||||
// ===================== CANVAS TOOLBAR (grid, ortho, snap) =====================
|
||||
['btn-grid', 'btn-ortho', 'btn-snap'].forEach(id => {
|
||||
const b = document.getElementById(id);
|
||||
if (b) {
|
||||
b.addEventListener('click', () => {
|
||||
b.classList.toggle('active');
|
||||
const pressed = b.classList.contains('active');
|
||||
b.setAttribute('aria-pressed', pressed ? 'true' : 'false');
|
||||
// Also toggle matching status bar item
|
||||
const map = { 'btn-grid': 'GRID', 'btn-ortho': 'ORTHO', 'btn-snap': 'SNAP' };
|
||||
const label = map[id];
|
||||
if (label) {
|
||||
document.querySelectorAll('.status-item').forEach(s => {
|
||||
if (s.textContent.trim().startsWith(label)) s.classList.toggle('active', pressed);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ===================== ZOOM =====================
|
||||
let zoom = 100;
|
||||
const zoomDisplay = document.getElementById('zoom-display');
|
||||
document.getElementById('btn-zoom-in')?.addEventListener('click', () => {
|
||||
zoom = Math.min(400, zoom + 25);
|
||||
if (zoomDisplay) zoomDisplay.textContent = zoom + '%';
|
||||
});
|
||||
document.getElementById('btn-zoom-out')?.addEventListener('click', () => {
|
||||
zoom = Math.max(10, zoom - 25);
|
||||
if (zoomDisplay) zoomDisplay.textContent = zoom + '%';
|
||||
});
|
||||
document.getElementById('btn-zoom-fit')?.addEventListener('click', () => {
|
||||
zoom = 100;
|
||||
if (zoomDisplay) zoomDisplay.textContent = zoom + '%';
|
||||
});
|
||||
|
||||
// ===================== LIBRARY CATEGORIES (desktop) =====================
|
||||
document.querySelectorAll('.rightbar .lib-cat').forEach(cat => {
|
||||
cat.addEventListener('click', () => {
|
||||
document.querySelectorAll('.rightbar .lib-cat').forEach(c => c.classList.remove('active'));
|
||||
cat.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// ===================== KI CHIPS (desktop) =====================
|
||||
document.querySelectorAll('.rightbar .ki-chip').forEach(chip => {
|
||||
chip.addEventListener('click', () => {
|
||||
const input = document.querySelector('.rightbar .ki-input');
|
||||
if (input) { input.value = chip.textContent.trim(); input.focus(); }
|
||||
});
|
||||
});
|
||||
|
||||
// ===================== KEYBOARD SHORTCUTS =====================
|
||||
const toolMap = { v: 'select', l: 'line', c: 'circle', m: 'move', p: 'pan', z: 'zoom-win', e: 'erase' };
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.target.matches('input, textarea, select')) return;
|
||||
if (e.key === 'Escape') return; // handled by drawer
|
||||
const k = e.key.toLowerCase();
|
||||
if (toolMap[k]) {
|
||||
const btn = document.querySelector(`.tool-btn[data-tool="${toolMap[k]}"]`);
|
||||
if (btn) btn.click();
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
// ===================== TREE COMPONENT TOGGLE =====================
|
||||
(function initTrees() {
|
||||
// Delegate click on [data-tree-toggle] rows (folder rows)
|
||||
document.addEventListener('click', (e) => {
|
||||
const toggleBtn = e.target.closest('.tree-toggle');
|
||||
if (toggleBtn) {
|
||||
const row = toggleBtn.closest('[data-tree-toggle]');
|
||||
if (!row) return;
|
||||
e.stopPropagation();
|
||||
const node = row.parentElement;
|
||||
toggleTreeNode(node);
|
||||
return;
|
||||
}
|
||||
// Click anywhere on folder row also toggles (except the count which is passive)
|
||||
const folderRow = e.target.closest('[data-tree-toggle]');
|
||||
if (folderRow && !e.target.closest('.tree-count')) {
|
||||
const node = folderRow.parentElement;
|
||||
toggleTreeNode(node);
|
||||
}
|
||||
});
|
||||
// Leaf selection (single-select within siblings)
|
||||
document.addEventListener('click', (e) => {
|
||||
const leaf = e.target.closest('[data-tree-select]');
|
||||
if (!leaf) return;
|
||||
e.stopPropagation();
|
||||
// Remove .active from siblings within same parent tree
|
||||
const tree = leaf.closest('.tree');
|
||||
if (tree) {
|
||||
tree.querySelectorAll('[data-tree-select].active').forEach(el => el.classList.remove('active'));
|
||||
}
|
||||
leaf.classList.add('active');
|
||||
});
|
||||
|
||||
function toggleTreeNode(node) {
|
||||
if (!node || !node.classList.contains('tree-node')) return;
|
||||
const expanded = node.getAttribute('aria-expanded') === 'true';
|
||||
node.setAttribute('aria-expanded', expanded ? 'false' : 'true');
|
||||
const children = node.querySelector(':scope > .tree-children');
|
||||
if (children) {
|
||||
if (expanded) { children.setAttribute('hidden', ''); }
|
||||
else { children.removeAttribute('hidden'); }
|
||||
}
|
||||
}
|
||||
|
||||
// Drag-and-drop for library leafs (visual only - mockup)
|
||||
document.addEventListener('dragstart', (e) => {
|
||||
const draggable = e.target.closest('.lib-draggable');
|
||||
if (!draggable) return;
|
||||
draggable.classList.add('dragging');
|
||||
const name = draggable.querySelector('.tree-name')?.textContent || 'Block';
|
||||
e.dataTransfer.setData('text/plain', name);
|
||||
e.dataTransfer.effectAllowed = 'copy';
|
||||
});
|
||||
document.addEventListener('dragend', (e) => {
|
||||
const draggable = e.target.closest('.lib-draggable');
|
||||
if (draggable) draggable.classList.remove('dragging');
|
||||
});
|
||||
})();
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,195 @@
|
||||
/* web-cad v6 – Design Tokens, Typography, Base (with Drawer tokens) */
|
||||
:root {
|
||||
--font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', ui-monospace, monospace;
|
||||
|
||||
--fs-xs: 11px;
|
||||
--fs-sm: 12px;
|
||||
--fs-md: 13px;
|
||||
--fs-lg: 14px;
|
||||
--fs-xl: 16px;
|
||||
--fs-2xl: 20px;
|
||||
|
||||
--color-primary: #2563eb;
|
||||
--color-primary-hover: #1d4ed8;
|
||||
--color-primary-light: #dbeafe;
|
||||
--color-primary-50: #eff6ff;
|
||||
|
||||
--color-bg: #f8fafc;
|
||||
--color-surface: #ffffff;
|
||||
--color-surface-2: #f1f5f9;
|
||||
--color-surface-3: #e2e8f0;
|
||||
--color-border: #e2e8f0;
|
||||
--color-border-strong: #cbd5e1;
|
||||
|
||||
--color-text: #0f172a;
|
||||
--color-text-muted: #64748b;
|
||||
--color-text-faint: #94a3b8;
|
||||
|
||||
--color-success: #10b981;
|
||||
--color-warning: #f59e0b;
|
||||
--color-error: #ef4444;
|
||||
--color-ki: #8b5cf6;
|
||||
--color-ki-light: #ede9fe;
|
||||
|
||||
--color-canvas-bg: #0f172a;
|
||||
--color-canvas-bg-2: #1e293b;
|
||||
--color-grid-minor: rgba(255,255,255,0.06);
|
||||
--color-grid-major: rgba(255,255,255,0.12);
|
||||
--color-grid-axis: rgba(96,165,250,0.28);
|
||||
--color-online: #10b981;
|
||||
|
||||
--radius-xs: 3px;
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 6px;
|
||||
--radius-lg: 8px;
|
||||
--radius-xl: 12px;
|
||||
|
||||
--spacing-xs: 4px;
|
||||
--spacing-sm: 6px;
|
||||
--spacing-md: 10px;
|
||||
--spacing-lg: 14px;
|
||||
--spacing-xl: 20px;
|
||||
|
||||
/* Layout */
|
||||
--topbar-h: 38px;
|
||||
--ribbon-h: 72px;
|
||||
--leftbar-w: 200px;
|
||||
--rightbar-w: 300px;
|
||||
--cmdline-h: 56px;
|
||||
--status-h: 26px;
|
||||
--canvas-toolbar-h: 38px;
|
||||
|
||||
/* Mobile-specific */
|
||||
--mobile-right-tab-w: 56px;
|
||||
--mobile-topbar-h: 44px;
|
||||
--mobile-ribbon-h: 48px;
|
||||
--drawer-w: min(320px, 88vw);
|
||||
|
||||
--shadow-sm: 0 1px 2px 0 rgba(15,23,42,0.04);
|
||||
--shadow-md: 0 2px 6px -1px rgba(15,23,42,0.08), 0 1px 3px -1px rgba(15,23,42,0.06);
|
||||
--shadow-lg: 0 10px 20px -4px rgba(15,23,42,0.10), 0 4px 8px -2px rgba(15,23,42,0.06);
|
||||
--shadow-drawer: -8px 0 24px -4px rgba(15,23,42,0.18);
|
||||
|
||||
--t-fast: 120ms cubic-bezier(.2,.0,.2,1);
|
||||
--t-base: 200ms cubic-bezier(.2,.0,.2,1);
|
||||
--t-drawer: 280ms cubic-bezier(.32, .72, 0, 1);
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--color-bg: #0b0e14;
|
||||
--color-surface: #11151c;
|
||||
--color-surface-2: #161b24;
|
||||
--color-surface-3: #1d2330;
|
||||
--color-border: #232a36;
|
||||
--color-border-strong: #2f3849;
|
||||
--color-text: #e2e8f0;
|
||||
--color-text-muted: #94a3b8;
|
||||
--color-text-faint: #64748b;
|
||||
--color-primary-light: #1e3a8a;
|
||||
--color-primary-50: #172554;
|
||||
--color-ki-light: #2e1065;
|
||||
--shadow-sm: 0 1px 2px 0 rgba(0,0,0,0.3);
|
||||
--shadow-md: 0 2px 6px -1px rgba(0,0,0,0.4);
|
||||
--shadow-lg: 0 10px 20px -4px rgba(0,0,0,0.5);
|
||||
--shadow-drawer: -8px 0 24px -4px rgba(0,0,0,0.6);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
*::before, *::after { box-sizing: border-box; }
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--fs-md);
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
line-height: 1.5;
|
||||
overflow: hidden;
|
||||
/* iOS safe areas */
|
||||
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
|
||||
}
|
||||
|
||||
body {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
input, select, textarea {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
a { color: var(--color-primary); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
::-webkit-scrollbar { width: 8px; height: 8px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--color-border-strong);
|
||||
border-radius: 10px;
|
||||
border: 2px solid var(--color-bg);
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover { background: var(--color-text-faint); }
|
||||
|
||||
*:focus-visible {
|
||||
outline: 2px solid var(--color-primary);
|
||||
outline-offset: 2px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0,0,0,0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
top: -40px;
|
||||
left: 8px;
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
padding: 8px 12px;
|
||||
border-radius: var(--radius-md);
|
||||
z-index: 1000;
|
||||
font-weight: 500;
|
||||
}
|
||||
.skip-link:focus { top: 8px; }
|
||||
|
||||
/* Scrim (drawer backdrop) */
|
||||
.scrim {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.5);
|
||||
z-index: 90;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity var(--t-drawer), visibility 0s linear var(--t-drawer);
|
||||
}
|
||||
.scrim.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: opacity var(--t-drawer), visibility 0s linear 0s;
|
||||
}
|
||||
Reference in New Issue
Block a user