// Shared building blocks for the Demenskalender Family-app // Light, modern, calm Scandinavian aesthetic. const TOKENS = { bg: '#FAF8F4', // warm off-white surface: '#FFFFFF', surfaceAlt: '#F2EEE7', // soft sand border: '#E5DFD4', borderStrong: '#D4CCBC', ink: '#1F2733', // ink near-black with cool tilt inkMuted: '#5C6573', inkSoft: '#8A8F99', primary: '#3F7A82', // dusty teal primaryHover: '#346168', primarySoft: '#E3EEF0', accent: '#C98A3A', // warm amber accentSoft: '#F6E8D2', danger: '#B5483F', success: '#5C8B5C', fontDisplay: '"Inter Tight", "Inter", system-ui, sans-serif', fontBody: '"Inter", system-ui, sans-serif', fontMono: '"JetBrains Mono", ui-monospace, monospace', }; // Global font import + base CSS injection if (typeof document !== 'undefined' && !document.getElementById('app-base-styles')) { const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Inter+Tight:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap'; document.head.appendChild(link); const s = document.createElement('style'); s.id = 'app-base-styles'; s.textContent = ` .app-root { font-family: ${TOKENS.fontBody}; color: ${TOKENS.ink}; -webkit-font-smoothing: antialiased; } .app-root *, .app-root *::before, .app-root *::after { box-sizing: border-box; } .app-display { font-family: ${TOKENS.fontDisplay}; letter-spacing: -0.01em; } .app-mono { font-family: ${TOKENS.fontMono}; } .app-btn { font-family: inherit; cursor: pointer; border: none; } .app-btn:focus-visible { outline: 2px solid ${TOKENS.primary}; outline-offset: 2px; } .app-input { font-family: inherit; font-size: 14px; padding: 10px 12px; border-radius: 8px; border: 1px solid ${TOKENS.border}; background: ${TOKENS.surface}; color: ${TOKENS.ink}; width: 100%; } .app-input:focus { outline: none; border-color: ${TOKENS.primary}; box-shadow: 0 0 0 3px ${TOKENS.primarySoft}; } /* Demo-mode: alle inputs er låste, viser pegefinger ved hover så de stadig føles klikbare */ .app-root input, .app-root textarea { caret-color: transparent !important; } .app-root input:focus, .app-root textarea:focus { outline: none !important; box-shadow: none !important; border-color: ${TOKENS.border} !important; } @keyframes app-pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.4; transform: scale(0.85); } } .app-card { background: ${TOKENS.surface}; border: 1px solid ${TOKENS.border}; border-radius: 14px; } .app-divider { height: 1px; background: ${TOKENS.border}; } .app-scroll::-webkit-scrollbar { width: 8px; height: 8px; } .app-scroll::-webkit-scrollbar-thumb { background: ${TOKENS.borderStrong}; border-radius: 8px; } .app-scroll::-webkit-scrollbar-track { background: transparent; } /* ── Mobile (≤760px) ──────────────────────────────────────────── */ @media (max-width: 760px) { /* Mobile: drop fixed-viewport scroll lock — let page flow naturally */ html, body { height: auto !important; overflow: auto !important; } #root { height: auto !important; } .app-root { height: auto !important; min-height: 100vh; overflow: visible !important; } .app-root > div { overflow: visible !important; } .app-burger { display: flex !important; } .app-sidebar { position: fixed !important; top: 0; left: 0; bottom: 0; z-index: 60; width: 280px !important; max-width: 86vw; transform: translateX(-100%); transition: transform 0.25s ease; box-shadow: 0 8px 32px rgba(20,30,50,0.18); } .app-sidebar.is-open { transform: translateX(0); } .app-sidebar-close { display: flex !important; } /* Inner scroll containers: release fixed-height scroll, flow with body */ .app-scroll { overflow: visible !important; flex: none !important; } /* Tighter padding inside main content on mobile */ .app-main-pad { padding: 70px 16px 24px !important; } .app-main-pad-tight { padding: 70px 14px 18px !important; } .app-topbar { padding: 16px 16px 14px 64px !important; } .app-topbar-edit { padding: 16px 16px 14px 64px !important; } .app-topbar-edit .app-topbar-actions { gap: 6px !important; } .app-topbar-edit .app-topbar-actions .app-changes-count { display: none; } /* Stack two-column layouts */ .app-grid-main { grid-template-columns: 1fr !important; padding: 70px 14px 24px !important; gap: 16px !important; } .app-stats-3 { grid-template-columns: 1fr 1fr 1fr !important; gap: 8px !important; } /* Edit screen: smaller hero metrics */ .app-edit-scroll { padding: 18px 16px 32px !important; } /* Documents: stack folder list above file list */ .app-docs-grid { grid-template-columns: 1fr !important; padding: 70px 14px 24px !important; gap: 16px !important; } .app-docs-folders { flex-direction: row !important; gap: 10px !important; overflow-x: auto; padding: 4px 14px 14px; margin: 0 -14px; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; scrollbar-width: thin; position: relative; } .app-docs-folders::-webkit-scrollbar { height: 4px; } .app-docs-folders::-webkit-scrollbar-thumb { background: ${TOKENS.borderStrong}; border-radius: 4px; } .app-docs-folders::-webkit-scrollbar-track { background: transparent; } .app-docs-folders > div:first-child { display: flex !important; align-items: center; gap: 6px; flex: 0 0 auto !important; padding: 0 4px 0 0 !important; font-size: 11px !important; color: ${TOKENS.inkSoft}; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; white-space: nowrap; align-self: center; } .app-docs-folders .app-docs-label-desktop { display: none; } .app-docs-folders .app-docs-label-mobile { display: inline-flex !important; } .app-docs-folders button { flex: 0 0 78%; max-width: 280px; scroll-snap-align: start; } .app-docs-fade { display: block !important; position: sticky; right: 0; top: 0; bottom: 0; flex: 0 0 36px; align-self: stretch; margin-left: -36px; background: linear-gradient(to right, transparent, ${TOKENS.bg} 80%); pointer-events: none; } .app-docs-size { display: none; } } `; document.head.appendChild(s); // Demo-mode: gør alle inputs readonly så brugeren ikke kan skrive i felterne const lockInputs = (root) => { root.querySelectorAll('input, textarea').forEach(el => { const t = (el.getAttribute('type') || 'text').toLowerCase(); if (t === 'checkbox' || t === 'radio' || t === 'button' || t === 'submit') { el.addEventListener('click', e => e.preventDefault()); } else { el.readOnly = true; } }); }; const initLock = () => { lockInputs(document); new MutationObserver(muts => { muts.forEach(m => m.addedNodes.forEach(n => { if (n.nodeType === 1) lockInputs(n); })); }).observe(document.body, { childList: true, subtree: true }); }; if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initLock); else initLock(); } // ── Icons (minimal stroke set) ───────────────────────────────────── const Icon = ({ name, size = 18, color = 'currentColor', strokeWidth = 1.75 }) => { const paths = { home: 'M3 11l9-8 9 8M5 9.5V20a1 1 0 001 1h4v-6h4v6h4a1 1 0 001-1V9.5', calendar: 'M4 6h16v14H4zM4 10h16M8 3v4M16 3v4', note: 'M5 4h11l4 4v12H5zM16 4v4h4', tv: 'M3 5h18v12H3zM8 21h8M12 17v4', cake: 'M5 21h14v-8H5zM12 13V9M9 5a3 3 0 016 0M5 17h14', phone: 'M5 4h4l2 5-3 2a11 11 0 005 5l2-3 5 2v4a2 2 0 01-2 2A16 16 0 013 6a2 2 0 012-2z', plus: 'M12 5v14M5 12h14', edit: 'M4 20h4l10-10-4-4L4 16zM14 6l4 4', settings: 'M12 8a4 4 0 100 8 4 4 0 000-8zM19 12c0 .5-.05 1-.13 1.5l2.07 1.5-2 3.46-2.4-.97c-.78.62-1.66 1.1-2.6 1.4l-.34 2.6h-3l-.34-2.6c-.95-.3-1.83-.78-2.6-1.4l-2.4.97-2-3.46 2.07-1.5C5.05 13 5 12.5 5 12s.05-1 .13-1.5L3.06 9 5.06 5.54l2.4.97c.78-.62 1.66-1.1 2.6-1.4l.34-2.6h3l.34 2.6c.95.3 1.83.78 2.6 1.4l2.4-.97 2 3.46-2.07 1.5c.08.5.13 1 .13 1.5z', chevronRight: 'M9 6l6 6-6 6', chevronLeft: 'M15 6l-9 6 9 6', check: 'M5 12l4 4 10-10', x: 'M6 6l12 12M18 6L6 18', clock: 'M12 6v6l4 2M12 22a10 10 0 110-20 10 10 0 010 20z', user: 'M12 12a4 4 0 100-8 4 4 0 000 8zM4 21a8 8 0 0116 0', bell: 'M6 16V11a6 6 0 0112 0v5l2 2H4zM10 20a2 2 0 004 0', screen: 'M3 4h18v12H3zM8 20h8M12 16v4', history: 'M3 12a9 9 0 109-9 9 9 0 00-7 3.5M3 4v4h4M12 7v5l3 2', activity: 'M3 12h4l3-9 4 18 3-9h4', mic: 'M12 2a3 3 0 00-3 3v6a3 3 0 006 0V5a3 3 0 00-3-3zM5 11a7 7 0 0014 0M12 18v3', eye: 'M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7zM12 9a3 3 0 100 6 3 3 0 000-6z', arrowRight: 'M5 12h14M13 6l6 6-6 6', pill: 'M10.5 13.5l3-3M5.5 8.5a4 4 0 015.66-5.66l5.5 5.5a4 4 0 01-5.66 5.66l-5.5-5.5zM3 21l4-4', folder: 'M3 7a2 2 0 012-2h4l2 2h8a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V7z', file: 'M14 3H6a2 2 0 00-2 2v14a2 2 0 002 2h12a2 2 0 002-2V9zM14 3v6h6', download: 'M12 4v12M7 11l5 5 5-5M4 20h16', droplet: 'M12 3l5 7a6 6 0 11-10 0z', nurse: 'M9 10h6M12 7v6M5 21a7 7 0 0114 0M12 3a4 4 0 100 8 4 4 0 000-8z', search: 'M11 4a7 7 0 105 12l5 5M11 4a7 7 0 017 7', upload: 'M12 20V8M7 13l5-5 5 5M4 4h16', }; return ( ); }; // ── Avatar (initials) ───────────────────────────────────────────── const Avatar = ({ name, size = 28, tone = 'teal' }) => { const initials = name.split(' ').map(s => s[0]).slice(0, 2).join('').toUpperCase(); const tones = { teal: { bg: '#D8E7E9', fg: '#2F5C61' }, amber: { bg: '#F2DCB6', fg: '#7A5418' }, rose: { bg: '#EDD3CE', fg: '#7A3F37' }, olive: { bg: '#DCE2C7', fg: '#5C6638' }, slate: { bg: '#D9DCE3', fg: '#3F4858' }, }; const t = tones[tone] || tones.teal; return (