diff --git a/web/css/styles.css b/web/css/styles.css index a878f76..f194dd7 100644 --- a/web/css/styles.css +++ b/web/css/styles.css @@ -1,605 +1,901 @@ -/* MacroPad PWA Styles */ +/* ========================================================================== + MacroPad PWA — "Tactile Deck" + A macro deck that reads like a slab of physical keys: warm near-black + surfaces, an electric-violet signature accent, softly-raised cards with a + hairline top highlight, and a satisfying pressed state. + Self-contained (no external fonts/CDNs — CSP forbids them). + ========================================================================== */ +/* ---- Design tokens -------------------------------------------------------- + Everything downstream references these. Dark is the default; the + prefers-color-scheme: light block below overrides only the surface/text/ + border tokens so both themes stay deliberate and pass WCAG AA. */ :root { - --bg-color: #2e2e2e; - --fg-color: #ffffff; - --highlight-color: #3e3e3e; - --accent-color: #007acc; - --button-bg: #505050; - --button-hover: #606060; - --tab-bg: #404040; - --tab-selected: #007acc; - --danger-color: #dc3545; - --success-color: #28a745; - --warning-color: #ffc107; + color-scheme: dark; + + /* Surfaces — warm near-black, not a flat grey */ + --surface: #15161a; /* app background */ + --surface-raised: #1f2027; /* cards, header, sheets */ + --surface-sunken: #101116; /* inset wells: inputs, command list */ + --border: #2c2e38; /* hairline dividers */ + --border-strong: #3a3d4a; + + /* Text — muted meets 4.5:1 on --surface */ + --text: #f3f4f8; + --text-muted: #a9adbd; + + /* Signature accent — electric violet */ + --accent: #7c5cff; + --accent-hover: #8f73ff; + --accent-2: #ffb057; /* warm amber companion (wake glow, highlights) */ + --accent-contrast: #ffffff; + --accent-soft: rgba(124, 92, 255, 0.16); + + /* Status */ + --danger: #ff5d6c; + --success: #3ddc84; + --warning: #ffc861; + + /* Spacing scale — 4px base */ + --s-1: 4px; + --s-2: 8px; + --s-3: 12px; + --s-4: 16px; + --s-5: 24px; + --s-6: 32px; + + /* Radii */ + --r-sm: 8px; + --r-md: 12px; + --r-lg: 18px; + --r-card: 14px; + --r-pill: 999px; + + /* Elevation — soft, warm-tinted shadows */ + --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.35); + --shadow-2: 0 6px 16px rgba(0, 0, 0, 0.40); + --shadow-3: 0 18px 48px rgba(0, 0, 0, 0.55); + + /* Hairline top highlight that makes surfaces read as physical keys */ + --key-highlight: inset 0 1px 0 rgba(255, 255, 255, 0.06); + + /* Type scale */ + --fs-xs: 0.75rem; + --fs-sm: 0.85rem; + --fs-base: 1rem; + --fs-lg: 1.2rem; + --fs-xl: 1.6rem; + + --font-ui: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, + Ubuntu, Cantarell, 'Helvetica Neue', sans-serif; + --font-mono: ui-monospace, 'SF Mono', 'JetBrains Mono', 'Cascadia Code', + Menlo, Consolas, monospace; + + --tap: 44px; /* minimum comfortable touch target */ + --dur: 0.18s; + --ease: cubic-bezier(0.2, 0.7, 0.2, 1); } +/* ---- Light theme — clean, deliberate, AA-compliant --------------------- */ +@media (prefers-color-scheme: light) { + :root { + color-scheme: light; + + --surface: #eceef4; + --surface-raised: #ffffff; + --surface-sunken: #e3e6ef; + --border: #d7dbe6; + --border-strong: #c3c8d6; + + --text: #191b22; + --text-muted: #5a5f70; /* 4.5:1+ on white and on --surface */ + + --accent: #5b3ee6; /* darkened so white text clears AA */ + --accent-hover: #4d31d6; + --accent-2: #c9720a; + --accent-soft: rgba(91, 62, 230, 0.12); + + --danger: #d13342; + --success: #17924f; + --warning: #a5730a; + + --shadow-1: 0 1px 2px rgba(20, 22, 40, 0.10); + --shadow-2: 0 6px 16px rgba(20, 22, 40, 0.12); + --shadow-3: 0 18px 48px rgba(20, 22, 40, 0.20); + --key-highlight: inset 0 1px 0 rgba(255, 255, 255, 0.9); + } +} + +/* ---- Reset ------------------------------------------------------------- */ * { box-sizing: border-box; margin: 0; padding: 0; } +/* [hidden] must always win over any display rule (keeps the modal closed). */ +[hidden] { + display: none !important; +} + +html { + -webkit-text-size-adjust: 100%; +} + body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; - background-color: var(--bg-color); - color: var(--fg-color); + font-family: var(--font-ui); + background-color: var(--surface); + color: var(--text); + font-size: var(--fs-base); + line-height: 1.45; min-height: 100vh; min-height: 100dvh; overflow-x: hidden; - /* Safe area for notched devices */ + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; padding-top: env(safe-area-inset-top); padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); } -/* Header */ +/* App-wide keyboard focus ring (the old CSS had none). */ +:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + border-radius: var(--r-sm); +} +:focus:not(:focus-visible) { + outline: none; +} + +/* ---- Header ------------------------------------------------------------ */ .header { - background-color: var(--highlight-color); - padding: 1rem; + background: + linear-gradient(180deg, rgba(124, 92, 255, 0.10), transparent 62%), + var(--surface-raised); + padding: var(--s-4); display: flex; justify-content: space-between; align-items: center; + gap: var(--s-3); position: sticky; top: 0; z-index: 100; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); + border-bottom: 1px solid var(--border); + box-shadow: var(--shadow-1); } .header h1 { - font-size: 1.5rem; - font-weight: 600; + font-size: var(--fs-xl); + font-weight: 800; + letter-spacing: -0.03em; + line-height: 1; + background: linear-gradient(105deg, var(--text) 30%, var(--accent)); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + color: var(--accent); /* fallback if clip unsupported */ } .header-actions { display: flex; - gap: 0.5rem; + align-items: center; + gap: var(--s-2); } +/* Header buttons */ .header-btn { - background: var(--accent-color); - color: white; + background: var(--accent); + color: var(--accent-contrast); border: none; - padding: 0.5rem 1rem; - border-radius: 4px; + padding: 0 var(--s-4); + min-height: var(--tap); + border-radius: var(--r-sm); cursor: pointer; - font-size: 0.9rem; - transition: background 0.2s; -} - -.header-btn:hover { - background: #0096ff; + font-size: var(--fs-sm); + font-weight: 600; + box-shadow: var(--shadow-1), var(--key-highlight); + transition: background var(--dur) var(--ease), transform var(--dur) var(--ease); } +.header-btn:hover { background: var(--accent-hover); } +.header-btn:active { transform: translateY(1px); } .header-btn.secondary { - background: var(--button-bg); + background: var(--surface-sunken); + color: var(--text); + border: 1px solid var(--border-strong); +} +.header-btn.secondary:hover { border-color: var(--accent); color: var(--text); } + +/* Square icon controls (fullscreen) — meet 44px tap target */ +.header-btn.icon-btn { + padding: 0; + width: var(--tap); + height: var(--tap); + display: inline-flex; + align-items: center; + justify-content: center; + font-size: var(--fs-lg); + line-height: 1; } -.header-btn.secondary:hover { - background: var(--button-hover); +/* ---- Connection status ------------------------------------------------- */ +.connection-status { + display: flex; + align-items: center; + gap: var(--s-2); + font-size: var(--fs-xs); + font-weight: 600; + color: var(--text-muted); + padding: 0 var(--s-2); + white-space: nowrap; } -/* Tabs */ +.status-dot { + width: 9px; + height: 9px; + border-radius: 50%; + background: var(--danger); + box-shadow: 0 0 0 0 rgba(255, 93, 108, 0.5); + transition: background var(--dur) var(--ease); +} +.status-dot.connected { + background: var(--success); + animation: pulse-ring 2.4s ease-out infinite; +} +@keyframes pulse-ring { + 0% { box-shadow: 0 0 0 0 rgba(61, 220, 132, 0.5); } + 70% { box-shadow: 0 0 0 7px rgba(61, 220, 132, 0); } + 100% { box-shadow: 0 0 0 0 rgba(61, 220, 132, 0); } +} + +/* ---- Wake-lock toggle -------------------------------------------------- */ +.wake-lock-status { + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--tap); + height: var(--tap); + background: var(--surface-sunken); + border: 1px solid var(--border); + border-radius: var(--r-sm); + font-size: var(--fs-base); + cursor: pointer; + opacity: 0.55; + transition: opacity var(--dur) var(--ease), border-color var(--dur) var(--ease); +} +.wake-lock-status:hover { opacity: 0.85; } +.wake-lock-status.active { + opacity: 1; + border-color: var(--accent-2); +} +.wake-lock-status .wake-icon { color: var(--accent-2); line-height: 1; } +.wake-lock-status.active .wake-icon { + animation: wake-glow 2s ease-in-out infinite; +} +.wake-lock-status.unsupported { opacity: 0.35; cursor: not-allowed; } +.wake-lock-status.unsupported .wake-icon { + color: var(--text-muted); + text-decoration: line-through; +} +@keyframes wake-glow { + 0%, 100% { filter: drop-shadow(0 0 0 rgba(255, 176, 87, 0)); opacity: 1; } + 50% { filter: drop-shadow(0 0 6px rgba(255, 176, 87, 0.7)); opacity: 0.8; } +} + +/* ---- Tabs -------------------------------------------------------------- */ .tabs { display: flex; - gap: 0.25rem; - padding: 0.5rem 1rem; - background: var(--bg-color); + gap: var(--s-2); + padding: var(--s-3) var(--s-4); + background: var(--surface); overflow-x: auto; -webkit-overflow-scrolling: touch; + scrollbar-width: none; } +.tabs::-webkit-scrollbar { display: none; } .tab { - background: var(--tab-bg); - color: var(--fg-color); - border: none; - padding: 0.5rem 1rem; - border-radius: 4px; + background: var(--surface-raised); + color: var(--text-muted); + border: 1px solid var(--border); + padding: 0 var(--s-4); + min-height: var(--tap); + border-radius: var(--r-pill); cursor: pointer; white-space: nowrap; - transition: background 0.2s; + font-size: var(--fs-sm); + font-weight: 600; + transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease), + background var(--dur) var(--ease); } - -.tab:hover { - background: var(--button-hover); -} - +.tab:hover { color: var(--text); border-color: var(--border-strong); } .tab.active { - background: var(--tab-selected); + background: var(--accent-soft); + color: var(--text); + border-color: var(--accent); } -/* Macro Grid */ +/* ---- Macro grid -------------------------------------------------------- */ .macro-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); - gap: 1rem; - padding: 1rem; - padding-bottom: calc(1rem + env(safe-area-inset-bottom)); + grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); + gap: var(--s-4); + padding: var(--s-4); + padding-bottom: calc(var(--s-6) + env(safe-area-inset-bottom)); } +/* Cards read as physical keys: raised slab, hairline top highlight, + satisfying press. */ .macro-card { - background: var(--button-bg); - border-radius: 8px; - padding: 1rem; + position: relative; + background: var(--surface-raised); + border: 1px solid var(--border); + border-radius: var(--r-card); + padding: var(--s-4) var(--s-3) var(--s-3); display: flex; flex-direction: column; align-items: center; + gap: var(--s-3); cursor: pointer; - transition: transform 0.1s, background 0.2s; - min-height: 120px; + min-height: 148px; + color: var(--text); + box-shadow: var(--shadow-2), var(--key-highlight); + transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease), + border-color var(--dur) var(--ease); +} +/* Faint wash of the macro's own color across the top of the key */ +.macro-card::before { + content: ""; + position: absolute; + inset: 0; + border-radius: inherit; + background: linear-gradient(180deg, + color-mix(in srgb, var(--macro-color, var(--accent)) 22%, transparent), + transparent 45%); + opacity: 0.55; + pointer-events: none; } - .macro-card:hover { - background: var(--button-hover); - transform: translateY(-2px); + transform: translateY(-3px); + box-shadow: var(--shadow-3), var(--key-highlight); + border-color: var(--border-strong); } - .macro-card:active { - transform: translateY(0); + transform: translateY(1px) scale(0.985); + box-shadow: var(--shadow-1), var(--key-highlight); +} +.macro-card:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; } +/* Pressed / executing — accent ring + brief success flash */ .macro-card.executing { - animation: pulse 0.3s ease-in-out; + animation: card-press 0.32s var(--ease); + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-soft), var(--shadow-2); } - -@keyframes pulse { - 0% { transform: scale(1); } - 50% { transform: scale(0.95); background: var(--accent-color); } +@keyframes card-press { + 0% { transform: scale(1); } + 35% { transform: scale(0.94); } + 55% { box-shadow: 0 0 0 4px rgba(61, 220, 132, 0.35), var(--shadow-2); } 100% { transform: scale(1); } } .macro-image { - width: 64px; - height: 64px; + width: 68px; + height: 68px; object-fit: contain; - margin-bottom: 0.5rem; - border-radius: 4px; -} - -.macro-image-placeholder { - width: 64px; - height: 64px; - background: var(--highlight-color); - border-radius: 4px; - display: flex; - align-items: center; - justify-content: center; - margin-bottom: 0.5rem; - font-size: 1.5rem; -} - -.macro-name { - text-align: center; - font-size: 0.9rem; - word-break: break-word; -} - -.macro-edit-btn { - position: absolute; - top: 4px; - right: 4px; - background: rgba(0, 0, 0, 0.5); - color: white; - border: none; - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - opacity: 0; - transition: opacity 0.2s; -} - -.macro-card { + border-radius: var(--r-md); position: relative; } -.macro-card:hover .macro-edit-btn { - opacity: 1; -} - -/* Modal */ -.modal-overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.7); +/* Placeholder background = the per-card --macro-color set by app.js */ +.macro-image-placeholder { + position: relative; + width: 68px; + height: 68px; + background: var(--macro-color, var(--accent)); + border-radius: var(--r-md); display: flex; align-items: center; justify-content: center; - z-index: 200; - padding: 1rem; + font-size: 1.9rem; + font-weight: 700; + color: #fff; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35); + box-shadow: var(--shadow-1), inset 0 1px 0 rgba(255, 255, 255, 0.25); } +.macro-name { + position: relative; + text-align: center; + font-size: var(--fs-sm); + font-weight: 600; + line-height: 1.3; + color: var(--text); + /* 2-line clamp with ellipsis — no mid-word breaks */ + display: -webkit-box; + -webkit-line-clamp: 2; + line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + overflow-wrap: break-word; +} + +/* Edit affordance — always faintly visible; solid on hover/focus */ +.macro-edit-btn { + position: absolute; + top: var(--s-2); + right: var(--s-2); + z-index: 1; + width: 30px; + height: 30px; + display: inline-flex; + align-items: center; + justify-content: center; + background: color-mix(in srgb, var(--surface-sunken) 82%, transparent); + color: var(--text-muted); + border: 1px solid var(--border); + border-radius: 50%; + cursor: pointer; + font-size: var(--fs-sm); + opacity: 0; + transition: opacity var(--dur) var(--ease), color var(--dur) var(--ease), + border-color var(--dur) var(--ease); +} +.macro-card:hover .macro-edit-btn, +.macro-card:focus-within .macro-edit-btn { opacity: 1; } +.macro-edit-btn:hover { color: var(--text); border-color: var(--accent); } +.macro-edit-btn:focus-visible { opacity: 1; } + +/* ---- Floating action button -------------------------------------------- */ +.fab { + position: fixed; + right: calc(var(--s-5) + env(safe-area-inset-right)); + bottom: calc(var(--s-5) + env(safe-area-inset-bottom)); + z-index: 150; + width: 60px; + height: 60px; + border: none; + border-radius: 50%; + background: var(--accent); + color: var(--accent-contrast); + font-size: 2rem; + font-weight: 300; + line-height: 1; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + box-shadow: 0 8px 24px rgba(124, 92, 255, 0.45), var(--key-highlight); + transition: transform var(--dur) var(--ease), background var(--dur) var(--ease), + box-shadow var(--dur) var(--ease); +} +.fab:hover { + background: var(--accent-hover); + transform: translateY(-2px) scale(1.04); + box-shadow: 0 12px 30px rgba(124, 92, 255, 0.55), var(--key-highlight); +} +.fab:active { transform: translateY(0) scale(0.96); } + +/* ---- Modal / editor sheet ---------------------------------------------- */ +/* .modal is the fixed scrim; .modal-content is the sheet. */ .modal { - background: var(--highlight-color); - border-radius: 8px; + position: fixed; + inset: 0; + z-index: 200; + display: flex; + align-items: center; + justify-content: center; + padding: var(--s-4); + background: rgba(8, 9, 14, 0.6); + -webkit-backdrop-filter: blur(6px); + backdrop-filter: blur(6px); + animation: fade-in var(--dur) var(--ease); +} +@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } } + +.modal-content { + background: var(--surface-raised); + border: 1px solid var(--border); + border-radius: var(--r-lg); width: 100%; - max-width: 500px; - max-height: 90vh; - overflow-y: auto; + max-width: 520px; + max-height: min(90vh, 780px); + display: flex; + flex-direction: column; + overflow: hidden; + box-shadow: var(--shadow-3), var(--key-highlight); + animation: sheet-rise var(--dur) var(--ease); +} +@keyframes sheet-rise { + from { transform: translateY(12px) scale(0.98); opacity: 0; } + to { transform: none; opacity: 1; } } .modal-header { display: flex; justify-content: space-between; align-items: center; - padding: 1rem; - border-bottom: 1px solid var(--bg-color); + padding: var(--s-4) var(--s-5); + border-bottom: 1px solid var(--border); + background: var(--surface-raised); + position: sticky; + top: 0; } - .modal-header h2 { - font-size: 1.2rem; + font-size: var(--fs-lg); + font-weight: 700; + letter-spacing: -0.01em; } .modal-close { + width: 36px; + height: 36px; + display: inline-flex; + align-items: center; + justify-content: center; background: none; border: none; - color: var(--fg-color); - font-size: 1.5rem; + color: var(--text-muted); + font-size: 1.6rem; + line-height: 1; cursor: pointer; - padding: 0.25rem; + border-radius: 50%; + transition: background var(--dur) var(--ease), color var(--dur) var(--ease); } +.modal-close:hover { background: var(--surface-sunken); color: var(--text); } .modal-body { - padding: 1rem; + padding: var(--s-5); + overflow-y: auto; + flex: 1; } .modal-footer { display: flex; justify-content: flex-end; - gap: 0.5rem; - padding: 1rem; - border-top: 1px solid var(--bg-color); + gap: var(--s-3); + padding: var(--s-4) var(--s-5); + border-top: 1px solid var(--border); + background: var(--surface-raised); + position: sticky; + bottom: 0; } +/* Push Delete to the far left when present */ +.modal-footer .btn-danger { margin-right: auto; } -/* Form Elements */ -.form-group { - margin-bottom: 1rem; -} +/* ---- Form elements ----------------------------------------------------- */ +.form-group { margin-bottom: var(--s-5); } +.form-group:last-child { margin-bottom: 0; } .form-group label { display: block; - margin-bottom: 0.25rem; - font-size: 0.9rem; - color: #aaa; + margin-bottom: var(--s-2); + font-size: var(--fs-sm); + font-weight: 600; + color: var(--text-muted); } .form-group input, .form-group select { width: 100%; - padding: 0.5rem; - background: var(--bg-color); - border: 1px solid var(--button-bg); - border-radius: 4px; - color: var(--fg-color); - font-size: 1rem; + padding: var(--s-3); + min-height: var(--tap); + background: var(--surface-sunken); + border: 1px solid var(--border-strong); + border-radius: var(--r-sm); + color: var(--text); + font-family: inherit; + font-size: var(--fs-base); + transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease); } - +.form-group input::placeholder { color: var(--text-muted); opacity: 0.8; } .form-group input:focus, .form-group select:focus { outline: none; - border-color: var(--accent-color); + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-soft); } -/* Command Builder */ +/* ---- Command builder --------------------------------------------------- */ .command-list { - background: var(--bg-color); - border-radius: 4px; - padding: 0.5rem; - min-height: 100px; - max-height: 300px; - overflow-y: auto; + background: var(--surface-sunken); + border: 1px solid var(--border); + border-radius: var(--r-md); + padding: var(--s-3); + min-height: 96px; + display: flex; + flex-direction: column; + gap: var(--s-2); } .command-item { - background: var(--button-bg); - border-radius: 4px; - padding: 0.5rem; - margin-bottom: 0.5rem; + background: var(--surface-raised); + border: 1px solid var(--border); + border-radius: var(--r-sm); + padding: var(--s-2); display: flex; align-items: center; - gap: 0.5rem; + gap: var(--s-2); } -.command-item:last-child { - margin-bottom: 0; +.command-type-select { + flex: 0 0 auto; + min-height: 40px; + padding: 0 var(--s-2); + background: var(--surface-sunken); + border: 1px solid var(--border-strong); + border-radius: var(--r-sm); + color: var(--text); + font-family: inherit; + font-size: var(--fs-sm); + font-weight: 600; + cursor: pointer; } - -.command-type { - background: var(--accent-color); - color: white; - padding: 0.25rem 0.5rem; - border-radius: 4px; - font-size: 0.75rem; - text-transform: uppercase; - min-width: 50px; - text-align: center; +.command-type-select:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-soft); } .command-value { - flex: 1; - font-family: monospace; - font-size: 0.9rem; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + flex: 1 1 auto; + min-width: 0; + min-height: 40px; + padding: 0 var(--s-3); + background: var(--surface-sunken); + border: 1px solid var(--border-strong); + border-radius: var(--r-sm); + color: var(--text); + font-family: var(--font-mono); + font-size: var(--fs-sm); } +.command-value:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-soft); +} +.command-value::placeholder { color: var(--text-muted); opacity: 0.7; } .command-actions { display: flex; - gap: 0.25rem; + gap: var(--s-1); + flex: 0 0 auto; } - .command-actions button { - background: var(--highlight-color); - border: none; - color: var(--fg-color); - padding: 0.25rem 0.5rem; - border-radius: 4px; + width: 34px; + height: 40px; + display: inline-flex; + align-items: center; + justify-content: center; + background: var(--surface-sunken); + border: 1px solid var(--border); + color: var(--text-muted); + border-radius: var(--r-sm); cursor: pointer; - font-size: 0.8rem; -} - -.command-actions button:hover { - background: var(--button-hover); -} - -.command-actions button.delete { - color: var(--danger-color); + font-size: var(--fs-sm); + transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease); } +.command-actions button:hover { color: var(--text); border-color: var(--border-strong); } +.command-actions button.delete:hover { color: var(--danger); border-color: var(--danger); } .add-command-btns { display: flex; flex-wrap: wrap; - gap: 0.5rem; - margin-top: 0.5rem; + gap: var(--s-2); + margin-top: var(--s-3); } - .add-command-btn { - background: var(--button-bg); - border: none; - color: var(--fg-color); - padding: 0.5rem 0.75rem; - border-radius: 4px; + background: transparent; + border: 1px dashed var(--border-strong); + color: var(--text-muted); + padding: var(--s-2) var(--s-3); + min-height: 40px; + border-radius: var(--r-sm); cursor: pointer; - font-size: 0.85rem; - transition: background 0.2s; + font-size: var(--fs-sm); + font-weight: 600; + transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease), + background var(--dur) var(--ease); } - .add-command-btn:hover { - background: var(--accent-color); + color: var(--accent); + border-color: var(--accent); + border-style: solid; + background: var(--accent-soft); } -/* Buttons */ +/* ---- Buttons ----------------------------------------------------------- */ .btn { - padding: 0.5rem 1rem; - border: none; - border-radius: 4px; + padding: 0 var(--s-5); + min-height: var(--tap); + border: 1px solid transparent; + border-radius: var(--r-sm); cursor: pointer; - font-size: 0.9rem; - transition: background 0.2s; + font-family: inherit; + font-size: var(--fs-sm); + font-weight: 600; + box-shadow: var(--key-highlight); + transition: background var(--dur) var(--ease), transform var(--dur) var(--ease), + border-color var(--dur) var(--ease); } +.btn:active { transform: translateY(1px); } +.btn:disabled { opacity: 0.5; cursor: not-allowed; } -.btn-primary { - background: var(--accent-color); - color: white; -} - -.btn-primary:hover { - background: #0096ff; -} +.btn-primary { background: var(--accent); color: var(--accent-contrast); } +.btn-primary:hover { background: var(--accent-hover); } .btn-secondary { - background: var(--button-bg); - color: white; + background: var(--surface-sunken); + color: var(--text); + border-color: var(--border-strong); } +.btn-secondary:hover { border-color: var(--accent); } -.btn-secondary:hover { - background: var(--button-hover); -} +.btn-danger { background: var(--danger); color: #fff; } +.btn-danger:hover { filter: brightness(1.08); } -.btn-danger { - background: var(--danger-color); - color: white; -} - -.btn-danger:hover { - background: #c82333; -} - -/* Status/Toast Messages */ +/* ---- Toasts ------------------------------------------------------------ */ .toast-container { position: fixed; - bottom: calc(1rem + env(safe-area-inset-bottom)); - right: 1rem; + bottom: calc(var(--s-4) + env(safe-area-inset-bottom)); + left: 50%; + transform: translateX(-50%); z-index: 300; + display: flex; + flex-direction: column; + align-items: center; + gap: var(--s-2); + width: max-content; + max-width: calc(100vw - 2 * var(--s-4)); + pointer-events: none; } .toast { - background: var(--highlight-color); - color: var(--fg-color); - padding: 0.75rem 1rem; - border-radius: 4px; - margin-top: 0.5rem; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); - animation: slideIn 0.3s ease-out; + pointer-events: auto; + background: var(--surface-raised); + color: var(--text); + padding: var(--s-3) var(--s-4); + border: 1px solid var(--border); + border-left-width: 4px; + border-radius: var(--r-sm); + font-size: var(--fs-sm); + font-weight: 500; + box-shadow: var(--shadow-2); + animation: toast-in var(--dur) var(--ease); +} +.toast.success { border-left-color: var(--success); } +.toast.error { border-left-color: var(--danger); } +@keyframes toast-in { + from { transform: translateY(12px); opacity: 0; } + to { transform: none; opacity: 1; } } -.toast.success { - border-left: 4px solid var(--success-color); -} - -.toast.error { - border-left: 4px solid var(--danger-color); -} - -@keyframes slideIn { - from { - transform: translateX(100%); - opacity: 0; - } - to { - transform: translateX(0); - opacity: 1; - } -} - -/* Connection Status */ -.connection-status { - display: flex; - align-items: center; - gap: 0.5rem; - font-size: 0.8rem; - color: #aaa; -} - -.status-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--danger-color); -} - -.status-dot.connected { - background: var(--success-color); -} - -/* Empty State */ +/* ---- Empty state ------------------------------------------------------- */ .empty-state { + grid-column: 1 / -1; text-align: center; - padding: 3rem 1rem; - color: #888; + padding: var(--s-6) var(--s-4); + display: flex; + flex-direction: column; + align-items: center; + gap: var(--s-3); +} +/* Large signature glyph */ +.empty-state::before { + content: "⌘"; + font-size: 4rem; + line-height: 1; + color: var(--accent); + opacity: 0.85; + margin-bottom: var(--s-2); } - .empty-state p { - margin-bottom: 1rem; + font-size: var(--fs-lg); + font-weight: 700; + color: var(--text); } +.empty-state p.hint { + font-size: var(--fs-sm); + font-weight: 400; + color: var(--text-muted); + max-width: 32ch; +} +.empty-state .btn { margin-top: var(--s-3); } -/* Loading */ +/* ---- Loading ----------------------------------------------------------- */ .loading { + grid-column: 1 / -1; display: flex; justify-content: center; - padding: 2rem; + padding: var(--s-6); } - .spinner { - width: 40px; - height: 40px; - border: 3px solid var(--button-bg); - border-top-color: var(--accent-color); + width: 42px; + height: 42px; + border: 3px solid var(--border-strong); + border-top-color: var(--accent); border-radius: 50%; - animation: spin 1s linear infinite; + animation: spin 0.9s linear infinite; } +@keyframes spin { to { transform: rotate(360deg); } } -@keyframes spin { - to { transform: rotate(360deg); } -} - -/* Mobile Optimizations */ -@media (max-width: 480px) { - .header h1 { - font-size: 1.2rem; - } - - .macro-grid { - grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); - gap: 0.75rem; - padding: 0.75rem; - } - - .macro-card { - padding: 0.75rem; - min-height: 100px; - } - - .macro-image, - .macro-image-placeholder { - width: 48px; - height: 48px; - } - - .modal { - max-width: 100%; - margin: 0.5rem; - } -} - -/* Install Banner */ +/* ---- Install banner ---------------------------------------------------- */ .install-banner { - background: var(--accent-color); - color: white; - padding: 0.75rem 1rem; display: flex; justify-content: space-between; align-items: center; + gap: var(--s-3); + padding: var(--s-3) var(--s-4); + background: var(--accent-soft); + color: var(--text); + border-bottom: 1px solid var(--accent); + font-size: var(--fs-sm); + font-weight: 500; } - +.install-banner > div { display: flex; gap: var(--s-2); align-items: center; } .install-banner button { - background: white; - color: var(--accent-color); + background: var(--accent); + color: var(--accent-contrast); border: none; - padding: 0.5rem 1rem; - border-radius: 4px; + padding: var(--s-2) var(--s-4); + min-height: 40px; + border-radius: var(--r-sm); cursor: pointer; font-weight: 600; } - +.install-banner button:hover { background: var(--accent-hover); } .install-banner .dismiss { background: transparent; - color: white; - padding: 0.5rem; + color: var(--text-muted); + padding: var(--s-2); + min-width: 40px; +} +.install-banner .dismiss:hover { color: var(--text); } + +/* ---- Fullscreen tweaks ------------------------------------------------- */ +:fullscreen .header { padding-top: var(--s-2); padding-bottom: var(--s-2); } +:fullscreen .macro-grid { padding-bottom: var(--s-4); } + +/* ---- Mobile ------------------------------------------------------------ */ +@media (max-width: 480px) { + .header { padding: var(--s-3); } + .header h1 { font-size: var(--fs-lg); } + /* Keep the status text from crowding; dot + label still fit */ + .connection-status { font-size: 0.7rem; padding: 0; } + + .macro-grid { + grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); + gap: var(--s-3); + padding: var(--s-3); + } + .macro-card { min-height: 128px; padding: var(--s-3) var(--s-2) var(--s-2); } + .macro-image, + .macro-image-placeholder { width: 56px; height: 56px; font-size: 1.6rem; } + + .modal { padding: var(--s-2); align-items: flex-end; } + .modal-content { max-width: 100%; max-height: 92vh; } + .modal-body { padding: var(--s-4); } + .modal-header, .modal-footer { padding: var(--s-4); } + + .fab { + right: calc(var(--s-4) + env(safe-area-inset-right)); + bottom: calc(var(--s-4) + env(safe-area-inset-bottom)); + } } -/* Fullscreen Button */ -.header-btn.icon-btn { - padding: 0.5rem 0.75rem; - font-size: 1.2rem; - line-height: 1; -} - -/* Wake Lock Status */ -.wake-lock-status { - display: flex; - align-items: center; - padding: 0.25rem 0.5rem; - border-radius: 4px; - font-size: 1rem; - opacity: 0.4; - transition: opacity 0.3s; -} - -.wake-lock-status.active { - opacity: 1; -} - -.wake-lock-status .wake-icon { - color: #ffc107; -} - -.wake-lock-status.active .wake-icon { - animation: pulse-glow 2s ease-in-out infinite; -} - -.wake-lock-status.unsupported { - opacity: 0.3; -} - -.wake-lock-status.unsupported .wake-icon { - color: #888; - text-decoration: line-through; -} - -@keyframes pulse-glow { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.6; } -} - -/* Fullscreen styles */ -:fullscreen .header { - padding-top: 0.5rem; -} - -:fullscreen .macro-grid { - padding-bottom: 1rem; +/* ---- Reduced motion ---------------------------------------------------- */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.001ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.001ms !important; + scroll-behavior: auto !important; + } + .macro-card:hover { transform: none; } } diff --git a/web/index.html b/web/index.html index 38e22f2..6d3fdfb 100644 --- a/web/index.html +++ b/web/index.html @@ -4,7 +4,8 @@ - + + @@ -13,7 +14,7 @@ - +