From 6a6f9ba4f7159133bdfd2fbcdc243607caad7156 Mon Sep 17 00:00:00 2001 From: Joel Brock Date: Thu, 23 Apr 2026 09:39:49 -0700 Subject: [PATCH] fix: keyboard multi-select uses stable IDs, improve Swiss theme, add Brutalist theme --- content/keyboard.js | 269 +++++++++++++++++++++++-------------------- popup/popup.html | 1 + themes/brutalist.css | 253 ++++++++++++++++++++++++++++++++++++++++ themes/swiss.css | 192 +++++++++++++++++++----------- 4 files changed, 517 insertions(+), 198 deletions(-) create mode 100644 themes/brutalist.css diff --git a/content/keyboard.js b/content/keyboard.js index 04a9f2c..330581a 100644 --- a/content/keyboard.js +++ b/content/keyboard.js @@ -1,6 +1,9 @@ // Outlook Relook — Gmail-Style Keyboard Navigation & Multi-Select // Adds keyboard focus cursor and multi-select to OWA's message list. // Gated by the keyboardMultiSelect setting. +// +// Tracks selections by stable message ID (data attribute or aria-label) +// rather than DOM element reference, since OWA frequently re-renders rows. window.OutlookRelook = window.OutlookRelook || {}; @@ -11,26 +14,42 @@ window.OutlookRelook.Keyboard = (function () { var currentSettings = {}; var cleanupFns = []; - // State - var focusedIndex = -1; // Index of the focused message in the list - var selectedSet = new Set(); // Set of selected message DOM elements - var countBadge = null; // Selection count badge element + // State — track by ID strings, not DOM references + var focusedId = null; // ID of the focused message + var selectedIds = new Set(); // Set of selected message IDs + var countBadge = null; - // --- Helpers --- + // --- Message ID extraction --- + // OWA messages have various attributes we can use as stable IDs. + // Try data-convid, data-itemid, id, or fall back to aria-label. + + function getMessageId(el) { + return el.getAttribute('data-convid') + || el.getAttribute('data-itemid') + || el.getAttribute('data-tid') + || el.getAttribute('id') + || el.getAttribute('aria-label') + || null; + } function getMessageItems() { - // Get all message items from the message list var items = document.querySelectorAll( '[role="listbox"] [role="option"], [role="list"] [role="listitem"]' ); return Array.from(items); } + function findItemById(items, id) { + if (!id) return -1; + for (var i = 0; i < items.length; i++) { + if (getMessageId(items[i]) === id) return i; + } + return -1; + } + function isComposeOrDialogActive() { var active = document.activeElement; if (!active) return false; - - // Check if focus is in a compose area, search bar, or dialog var tag = active.tagName.toLowerCase(); if (tag === 'input' || tag === 'textarea') return true; if (active.getAttribute('contenteditable') === 'true') return true; @@ -38,53 +57,90 @@ window.OutlookRelook.Keyboard = (function () { if (active.closest('[role="search"]')) return true; if (active.closest('[aria-label*="compose" i]')) return true; if (active.closest('[aria-label*="New message" i]')) return true; - return false; } - function setFocus(items, index) { - // Remove old focus - var oldFocused = document.querySelector('.or-kb-focused'); - if (oldFocused) oldFocused.classList.remove('or-kb-focused'); + // --- Visual state application --- + // Re-applies focus and selection classes to current DOM elements + // based on the ID-based state. Called after every key action and + // by the MutationObserver when OWA re-renders. - if (index < 0 || index >= items.length) return; + function applyVisualState(items) { + // Clear all visual markers first + var oldFocused = document.querySelectorAll('.or-kb-focused'); + for (var f = 0; f < oldFocused.length; f++) oldFocused[f].classList.remove('or-kb-focused'); + var oldSelected = document.querySelectorAll('.or-kb-selected'); + for (var s = 0; s < oldSelected.length; s++) oldSelected[s].classList.remove('or-kb-selected'); - focusedIndex = index; - var item = items[index]; - item.classList.add('or-kb-focused'); + if (!items) items = getMessageItems(); - // Scroll into view if needed - item.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); - } - - function toggleSelect(item) { - if (selectedSet.has(item)) { - selectedSet.delete(item); - item.classList.remove('or-kb-selected'); - } else { - selectedSet.add(item); - item.classList.add('or-kb-selected'); + // Apply focus + if (focusedId) { + var focusIdx = findItemById(items, focusedId); + if (focusIdx >= 0) { + items[focusIdx].classList.add('or-kb-focused'); + } } + + // Apply selections + selectedIds.forEach(function (id) { + var idx = findItemById(items, id); + if (idx >= 0) { + items[idx].classList.add('or-kb-selected'); + } + }); + updateCountBadge(); } + // --- Focus management --- + + function getFocusedIndex(items) { + if (!focusedId) return -1; + return findItemById(items, focusedId); + } + + function setFocus(items, index) { + if (index < 0 || index >= items.length) return; + focusedId = getMessageId(items[index]); + applyVisualState(items); + items[index].scrollIntoView({ block: 'nearest', behavior: 'smooth' }); + } + + // --- Selection management --- + + function toggleSelect(items, index) { + if (index < 0 || index >= items.length) return; + var id = getMessageId(items[index]); + if (!id) return; + + if (selectedIds.has(id)) { + selectedIds.delete(id); + } else { + selectedIds.add(id); + } + applyVisualState(items); + } + function clearSelection() { - selectedSet.forEach(function (item) { - item.classList.remove('or-kb-selected'); - }); - selectedSet.clear(); + selectedIds.clear(); + var oldSelected = document.querySelectorAll('.or-kb-selected'); + for (var i = 0; i < oldSelected.length; i++) oldSelected[i].classList.remove('or-kb-selected'); updateCountBadge(); } function getActionTargets(items) { - // If messages are selected, return those. Otherwise return the focused message. - if (selectedSet.size > 0) { - return Array.from(selectedSet); + var targets = []; + if (selectedIds.size > 0) { + selectedIds.forEach(function (id) { + var idx = findItemById(items, id); + if (idx >= 0) targets.push(items[idx]); + }); + } else { + var fi = getFocusedIndex(items); + if (fi >= 0) targets.push(items[fi]); } - if (focusedIndex >= 0 && focusedIndex < items.length) { - return [items[focusedIndex]]; - } - return []; + return targets; } // --- Selection count badge --- @@ -101,7 +157,7 @@ window.OutlookRelook.Keyboard = (function () { function updateCountBadge() { if (!countBadge) countBadge = createCountBadge(); if (!countBadge) return; - var count = selectedSet.size; + var count = selectedIds.size; if (count === 0) { countBadge.style.opacity = '0'; } else { @@ -111,30 +167,20 @@ window.OutlookRelook.Keyboard = (function () { } // --- Actions --- - // Each action simulates what a user would do in OWA to perform the operation. - // We click the message to select it in OWA, then trigger the toolbar action. function performAction(targets, actionFn) { if (targets.length === 0) return; - - // Process targets one at a time with a small delay between each var i = 0; function processNext() { if (i >= targets.length) { - // After all targets processed, clear selection clearSelection(); return; } var target = targets[i]; i++; - - // Click the message to make it OWA-selected target.click(); - - // Small delay to let OWA register the selection, then perform action setTimeout(function () { actionFn(target); - // Delay before next target setTimeout(processNext, 150); }, 100); } @@ -143,19 +189,14 @@ window.OutlookRelook.Keyboard = (function () { function actionDelete(targets) { performAction(targets, function () { - // Find and click the delete button in the toolbar var deleteBtn = document.querySelector( '[aria-label*="Delete" i][role="button"], [aria-label*="delete" i][role="menuitem"]' ); if (deleteBtn) { deleteBtn.click(); } else { - // Fallback: try dispatching Delete key var deleteEvent = new KeyboardEvent('keydown', { - key: 'Delete', - code: 'Delete', - bubbles: true, - cancelable: true + key: 'Delete', code: 'Delete', bubbles: true, cancelable: true }); document.activeElement.dispatchEvent(deleteEvent); } @@ -183,7 +224,6 @@ window.OutlookRelook.Keyboard = (function () { if (readBtn) { readBtn.click(); } else { - // Try context menu approach triggerContextMenuAction(/mark as read/i); } }); @@ -203,11 +243,8 @@ window.OutlookRelook.Keyboard = (function () { } function actionMove(targets) { - // For move, we just need to trigger OWA's move dialog on the current selection if (targets.length > 0) { - // Click the first target to ensure something is selected targets[0].click(); - setTimeout(function () { var moveBtn = document.querySelector( '[aria-label*="Move to" i][role="button"], [aria-label*="Move" i][role="menuitem"]' @@ -222,19 +259,14 @@ window.OutlookRelook.Keyboard = (function () { } function triggerContextMenuAction(pattern) { - // Open context menu on the focused/selected element var focused = document.querySelector('.or-kb-focused'); if (!focused) return; - var rect = focused.getBoundingClientRect(); var contextEvent = new MouseEvent('contextmenu', { - bubbles: true, - cancelable: true, - clientX: rect.x + 10, - clientY: rect.y + 10, + bubbles: true, cancelable: true, + clientX: rect.x + 10, clientY: rect.y + 10, }); focused.dispatchEvent(contextEvent); - setTimeout(function () { var menuItems = document.querySelectorAll('[role="menuitem"]'); for (var j = 0; j < menuItems.length; j++) { @@ -243,7 +275,6 @@ window.OutlookRelook.Keyboard = (function () { return; } } - // Close context menu if not found document.body.click(); }, 300); } @@ -251,7 +282,6 @@ window.OutlookRelook.Keyboard = (function () { // --- Key handler --- function handleKeydown(e) { - // Skip if keyboard mode is off or compose/dialog is active if (!currentSettings.keyboardMultiSelect) return; if (isComposeOrDialogActive()) return; @@ -262,15 +292,21 @@ window.OutlookRelook.Keyboard = (function () { var shift = e.shiftKey; // Initialize focus if not set - if (focusedIndex < 0 || focusedIndex >= items.length) { - // Find the currently OWA-selected item, or start at 0 + var currentIdx = getFocusedIndex(items); + if (currentIdx < 0) { for (var f = 0; f < items.length; f++) { - if (items[f].getAttribute('aria-selected') === 'true') { - focusedIndex = f; + if (items[f].getAttribute('aria-selected') === 'true' || + items[f].classList.contains('is-selected')) { + currentIdx = f; + focusedId = getMessageId(items[f]); break; } } - if (focusedIndex < 0) focusedIndex = 0; + if (currentIdx < 0) { + currentIdx = 0; + focusedId = getMessageId(items[0]); + } + applyVisualState(items); } var handled = true; @@ -280,19 +316,14 @@ window.OutlookRelook.Keyboard = (function () { case 'j': case 'ArrowDown': if (shift) { - // Select current and move down - if (focusedIndex >= 0 && focusedIndex < items.length) { - if (!selectedSet.has(items[focusedIndex])) { - toggleSelect(items[focusedIndex]); - } - } - if (focusedIndex < items.length - 1) { - setFocus(items, focusedIndex + 1); - toggleSelect(items[focusedIndex]); + toggleSelect(items, currentIdx); + if (currentIdx < items.length - 1) { + setFocus(items, currentIdx + 1); + toggleSelect(items, currentIdx + 1); } } else { - if (focusedIndex < items.length - 1) { - setFocus(items, focusedIndex + 1); + if (currentIdx < items.length - 1) { + setFocus(items, currentIdx + 1); } } break; @@ -300,46 +331,35 @@ window.OutlookRelook.Keyboard = (function () { case 'k': case 'ArrowUp': if (shift) { - // Select current and move up - if (focusedIndex >= 0 && focusedIndex < items.length) { - if (!selectedSet.has(items[focusedIndex])) { - toggleSelect(items[focusedIndex]); - } - } - if (focusedIndex > 0) { - setFocus(items, focusedIndex - 1); - toggleSelect(items[focusedIndex]); + toggleSelect(items, currentIdx); + if (currentIdx > 0) { + setFocus(items, currentIdx - 1); + toggleSelect(items, currentIdx - 1); } } else { - if (focusedIndex > 0) { - setFocus(items, focusedIndex - 1); + if (currentIdx > 0) { + setFocus(items, currentIdx - 1); } } break; case 'x': case ' ': - // Toggle select on focused message - e.preventDefault(); // Prevent page scroll on Space - if (focusedIndex >= 0 && focusedIndex < items.length) { - toggleSelect(items[focusedIndex]); - } + e.preventDefault(); + toggleSelect(items, currentIdx); break; case '#': - // Delete selected/focused messages targets = getActionTargets(items); actionDelete(targets); break; case 'e': - // Archive selected/focused messages targets = getActionTargets(items); actionArchive(targets); break; case 'I': - // Shift+i — Mark as read if (shift) { targets = getActionTargets(items); actionMarkRead(targets); @@ -349,7 +369,6 @@ window.OutlookRelook.Keyboard = (function () { break; case 'U': - // Shift+u — Mark as unread if (shift) { targets = getActionTargets(items); actionMarkUnread(targets); @@ -359,21 +378,18 @@ window.OutlookRelook.Keyboard = (function () { break; case 'v': - // Move selected messages targets = getActionTargets(items); actionMove(targets); break; case 'Escape': - // Deselect all clearSelection(); break; case 'Enter': case 'o': - // Open focused message in reading pane - if (focusedIndex >= 0 && focusedIndex < items.length) { - items[focusedIndex].click(); + if (currentIdx >= 0 && currentIdx < items.length) { + items[currentIdx].click(); } break; @@ -383,33 +399,32 @@ window.OutlookRelook.Keyboard = (function () { if (handled) { e.stopPropagation(); - // Only preventDefault for keys we handle (except Enter which OWA should also process) if (key !== 'Enter' && key !== 'o') { e.preventDefault(); } } } - // --- Cleanup stale selections when message list re-renders --- + // --- Re-apply visual state when OWA re-renders the message list --- function setupListObserver() { var listObserver = new MutationObserver(function () { - // Remove stale entries from selectedSet (elements no longer in DOM) - selectedSet.forEach(function (item) { - if (!document.contains(item)) { - selectedSet.delete(item); - } - }); - updateCountBadge(); - - // Reset focusedIndex if the focused item is gone - var focusedEl = document.querySelector('.or-kb-focused'); - if (!focusedEl || !document.contains(focusedEl)) { - focusedIndex = -1; + // Prune IDs that no longer exist in DOM + var items = getMessageItems(); + var currentIds = new Set(); + for (var i = 0; i < items.length; i++) { + var id = getMessageId(items[i]); + if (id) currentIds.add(id); } + selectedIds.forEach(function (id) { + if (!currentIds.has(id)) selectedIds.delete(id); + }); + if (focusedId && !currentIds.has(focusedId)) focusedId = null; + + // Re-apply classes to new DOM elements + applyVisualState(items); }); - // Watch the message list container for child changes var checkInterval = setInterval(function () { var list = document.querySelector('[role="listbox"], [role="list"]'); if (list) { @@ -444,7 +459,6 @@ window.OutlookRelook.Keyboard = (function () { var isEnabled = settings.keyboardMultiSelect; currentSettings = settings; - // Only tear down and restart if the keyboard setting itself changed if (wasEnabled !== isEnabled) { stop(); start(settings); @@ -457,11 +471,10 @@ window.OutlookRelook.Keyboard = (function () { } cleanupFns = []; - // Clean up DOM state clearSelection(); var focused = document.querySelector('.or-kb-focused'); if (focused) focused.classList.remove('or-kb-focused'); - focusedIndex = -1; + focusedId = null; if (countBadge) { countBadge.remove(); diff --git a/popup/popup.html b/popup/popup.html index ec8cf44..eaf0bf5 100644 --- a/popup/popup.html +++ b/popup/popup.html @@ -20,6 +20,7 @@
diff --git a/themes/brutalist.css b/themes/brutalist.css new file mode 100644 index 0000000..86e1ee7 --- /dev/null +++ b/themes/brutalist.css @@ -0,0 +1,253 @@ +/* + * Outlook Relook — Brutalist Theme + * Stark minimalism. Monospace. Borders only, almost no fills. + * High contrast. Maximum information density. No comfort. + * Inspired by terminal UIs, tax forms, and concrete architecture. + */ + +/* ============================================================ + LIGHT VARIANT + ============================================================ */ +html[data-outlook-relook-scheme="light"] { + --or-bg-primary: #ffffff; + --or-bg-secondary: #ffffff; + --or-bg-tertiary: #ffffff; + --or-bg-hover: transparent; + --or-bg-selected: transparent; + --or-text-primary: #000000; + --or-text-secondary: #000000; + --or-text-tertiary: #555555; + --or-text-disabled: #999999; + --or-border: #000000; + --or-border-light: #000000; + --or-accent: var(--or-accent-override, #000000); + --or-accent-hover: var(--or-accent-override, #333333); + --or-shadow: none; +} + +/* ============================================================ + DARK VARIANT + ============================================================ */ +html[data-outlook-relook-scheme="dark"] { + --or-bg-primary: #000000; + --or-bg-secondary: #000000; + --or-bg-tertiary: #000000; + --or-bg-hover: transparent; + --or-bg-selected: transparent; + --or-text-primary: #ffffff; + --or-text-secondary: #ffffff; + --or-text-tertiary: #aaaaaa; + --or-text-disabled: #555555; + --or-border: #ffffff; + --or-border-light: #ffffff; + --or-accent: var(--or-accent-override, #ffffff); + --or-accent-hover: var(--or-accent-override, #cccccc); + --or-shadow: none; +} + +/* ============================================================ + TYPOGRAPHY — Monospace, tight, utilitarian + ============================================================ */ +html[data-outlook-relook-scheme] *, +html[data-outlook-relook-scheme] *::before, +html[data-outlook-relook-scheme] *::after { + font-family: 'SF Mono', 'Menlo', 'Monaco', 'Consolas', 'Liberation Mono', monospace !important; + letter-spacing: -0.03em !important; + -webkit-font-smoothing: none !important; + font-smooth: never !important; +} + +html[data-outlook-relook-scheme] body { + font-size: 12px !important; +} + +/* ============================================================ + STRIP EVERYTHING — more aggressive than Swiss + ============================================================ */ +html[data-outlook-relook-scheme] * { + border-radius: 0 !important; + text-shadow: none !important; + box-shadow: none !important; + background-image: none !important; + transition: none !important; + animation: none !important; +} + +/* ============================================================ + SURFACES — pure white/black, no distinction between panes + ============================================================ */ +html[data-outlook-relook-scheme] body { + background-color: var(--or-bg-primary) !important; + color: var(--or-text-primary) !important; +} + +/* Top bar — just a bottom border */ +html[data-outlook-relook-scheme] [role="banner"], +html[data-outlook-relook-scheme] header { + background-color: var(--or-bg-primary) !important; + border-bottom: 2px solid var(--or-border) !important; +} + +/* Folder pane — border only, no background change */ +html[data-outlook-relook-scheme] [role="navigation"], +html[data-outlook-relook-scheme] [role="complementary"] { + background-color: var(--or-bg-primary) !important; + border-right: 2px solid var(--or-border) !important; +} + +/* Message list — no background */ +html[data-outlook-relook-scheme] [role="listbox"], +html[data-outlook-relook-scheme] [role="list"] { + background-color: var(--or-bg-primary) !important; +} + +/* Message list items — borders only, no fills */ +html[data-outlook-relook-scheme] [role="option"], +html[data-outlook-relook-scheme] [role="listitem"] { + background-color: transparent !important; + border-bottom: 1px solid var(--or-border) !important; + color: var(--or-text-primary) !important; + padding: 3px 8px !important; +} + +/* Hover: invert colors instead of fill */ +html[data-outlook-relook-scheme="light"] [role="option"]:hover, +html[data-outlook-relook-scheme="light"] [role="listitem"]:hover { + background-color: #000000 !important; + color: #ffffff !important; +} + +html[data-outlook-relook-scheme="dark"] [role="option"]:hover, +html[data-outlook-relook-scheme="dark"] [role="listitem"]:hover { + background-color: #ffffff !important; + color: #000000 !important; +} + +/* Selected: invert */ +html[data-outlook-relook-scheme="light"] [role="option"][aria-selected="true"], +html[data-outlook-relook-scheme="light"] [role="listitem"][aria-selected="true"] { + background-color: #000000 !important; + color: #ffffff !important; +} + +html[data-outlook-relook-scheme="dark"] [role="option"][aria-selected="true"], +html[data-outlook-relook-scheme="dark"] [role="listitem"][aria-selected="true"] { + background-color: #ffffff !important; + color: #000000 !important; +} + +/* Reading pane */ +html[data-outlook-relook-scheme] [role="main"] { + background-color: var(--or-bg-primary) !important; +} + +/* ============================================================ + TOOLBAR — stark, no visual weight + ============================================================ */ +html[data-outlook-relook-scheme] [role="toolbar"] { + background-color: var(--or-bg-primary) !important; + border-bottom: 1px solid var(--or-border) !important; +} + +html[data-outlook-relook-scheme] [role="toolbar"] button, +html[data-outlook-relook-scheme] [role="toolbar"] [role="button"] { + background-color: transparent !important; + border: 1px solid transparent !important; + color: var(--or-text-primary) !important; + text-transform: uppercase !important; + font-size: 10px !important; + letter-spacing: 0.05em !important; +} + +html[data-outlook-relook-scheme] [role="toolbar"] button:hover, +html[data-outlook-relook-scheme] [role="toolbar"] [role="button"]:hover { + border-color: var(--or-border) !important; + background-color: transparent !important; +} + +/* ============================================================ + BUTTONS — outlined only, never filled + ============================================================ */ +html[data-outlook-relook-scheme] button, +html[data-outlook-relook-scheme] [role="button"] { + background-color: transparent !important; + color: var(--or-text-primary) !important; + border: 1px solid transparent !important; +} + +html[data-outlook-relook-scheme] button:hover, +html[data-outlook-relook-scheme] [role="button"]:hover { + border-color: var(--or-border) !important; +} + +/* Links: same color as text, underlined */ +html[data-outlook-relook-scheme] a { + color: var(--or-text-primary) !important; + text-decoration: underline !important; +} + +html[data-outlook-relook-scheme] a:hover { + text-decoration: none !important; +} + +/* ============================================================ + FOLDER PANE — all caps, no fills + ============================================================ */ +html[data-outlook-relook-scheme] [role="treeitem"] { + color: var(--or-text-primary) !important; + background-color: transparent !important; + text-transform: uppercase !important; + font-size: 10px !important; + letter-spacing: 0.06em !important; + border-bottom: 1px solid var(--or-border-light) !important; +} + +html[data-outlook-relook-scheme] [role="treeitem"]:hover { + text-decoration: underline !important; + background-color: transparent !important; +} + +html[data-outlook-relook-scheme] [role="treeitem"][aria-selected="true"] { + font-weight: 900 !important; + background-color: transparent !important; +} + +/* ============================================================ + SECONDARY TEXT + ============================================================ */ +html[data-outlook-relook-scheme] [role="option"] span, +html[data-outlook-relook-scheme] [role="listitem"] span { + color: var(--or-text-tertiary) !important; + font-size: 11px !important; +} + +html[data-outlook-relook-scheme] time { + color: var(--or-text-tertiary) !important; + font-size: 10px !important; + text-transform: uppercase !important; +} + +/* ============================================================ + DIALOGS — bordered box, no fill + ============================================================ */ +html[data-outlook-relook-scheme] [role="dialog"], +html[data-outlook-relook-scheme] [role="alertdialog"] { + background-color: var(--or-bg-primary) !important; + border: 2px solid var(--or-border) !important; +} + +/* ============================================================ + SCROLLBARS — hairline + ============================================================ */ +html[data-outlook-relook-scheme] ::-webkit-scrollbar { + width: 2px !important; + height: 2px !important; +} + +html[data-outlook-relook-scheme] ::-webkit-scrollbar-track { + background: transparent !important; +} + +html[data-outlook-relook-scheme] ::-webkit-scrollbar-thumb { + background: var(--or-text-primary) !important; +} diff --git a/themes/swiss.css b/themes/swiss.css index 71b58ea..a2fe681 100644 --- a/themes/swiss.css +++ b/themes/swiss.css @@ -1,6 +1,8 @@ /* * Outlook Relook — Swiss / Helvetica Theme - * Monochrome, tight grid, no decoration. Content density is king. + * Ruthlessly minimal. Black, white, grey, accent. Nothing else. + * Helvetica Neue everywhere. Zero decoration. Grid discipline. + * If an element doesn't serve the content, it should be invisible. */ /* ============================================================ @@ -8,18 +10,18 @@ ============================================================ */ html[data-outlook-relook-scheme="light"] { --or-bg-primary: #ffffff; - --or-bg-secondary: #fafafa; - --or-bg-tertiary: #f0f0f0; - --or-bg-hover: #e8e8e8; - --or-bg-selected: #e0e0e0; + --or-bg-secondary: #ffffff; + --or-bg-tertiary: #f7f7f7; + --or-bg-hover: #f0f0f0; + --or-bg-selected: #f0f0f0; --or-text-primary: #000000; - --or-text-secondary: #333333; - --or-text-tertiary: #666666; - --or-text-disabled: #999999; - --or-border: #d0d0d0; - --or-border-light: #e5e5e5; - --or-accent: var(--or-accent-override, #000000); - --or-accent-hover: var(--or-accent-override, #333333); + --or-text-secondary: #555555; + --or-text-tertiary: #888888; + --or-text-disabled: #bbbbbb; + --or-border: #000000; + --or-border-light: #e0e0e0; + --or-accent: var(--or-accent-override, #ff0000); + --or-accent-hover: var(--or-accent-override, #cc0000); --or-shadow: none; } @@ -27,71 +29,86 @@ html[data-outlook-relook-scheme="light"] { DARK VARIANT ============================================================ */ html[data-outlook-relook-scheme="dark"] { - --or-bg-primary: #1a1a1a; - --or-bg-secondary: #222222; - --or-bg-tertiary: #2a2a2a; - --or-bg-hover: #333333; - --or-bg-selected: #3a3a3a; - --or-text-primary: #e8e8e8; - --or-text-secondary: #cccccc; - --or-text-tertiary: #999999; - --or-text-disabled: #666666; - --or-border: #3a3a3a; - --or-border-light: #2f2f2f; - --or-accent: var(--or-accent-override, #e8e8e8); - --or-accent-hover: var(--or-accent-override, #ffffff); + --or-bg-primary: #0a0a0a; + --or-bg-secondary: #0a0a0a; + --or-bg-tertiary: #141414; + --or-bg-hover: #1a1a1a; + --or-bg-selected: #1a1a1a; + --or-text-primary: #ffffff; + --or-text-secondary: #aaaaaa; + --or-text-tertiary: #666666; + --or-text-disabled: #444444; + --or-border: #ffffff; + --or-border-light: #222222; + --or-accent: var(--or-accent-override, #ff3333); + --or-accent-hover: var(--or-accent-override, #ff6666); --or-shadow: none; } /* ============================================================ - TYPOGRAPHY + TYPOGRAPHY — Helvetica Neue, aggressively applied ============================================================ */ -html[data-outlook-relook-scheme] body, -html[data-outlook-relook-scheme] [role="main"], -html[data-outlook-relook-scheme] [role="navigation"], -html[data-outlook-relook-scheme] [role="complementary"], -html[data-outlook-relook-scheme] [role="banner"], -html[data-outlook-relook-scheme] input, -html[data-outlook-relook-scheme] button, -html[data-outlook-relook-scheme] select, -html[data-outlook-relook-scheme] textarea { +html[data-outlook-relook-scheme] *, +html[data-outlook-relook-scheme] *::before, +html[data-outlook-relook-scheme] *::after { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif !important; - letter-spacing: -0.01em !important; + letter-spacing: -0.02em !important; + -webkit-font-smoothing: antialiased !important; + -moz-osx-font-smoothing: grayscale !important; } /* ============================================================ - SURFACES & BACKGROUNDS + STRIP ALL DECORATION — the core Swiss principle + ============================================================ */ +html[data-outlook-relook-scheme] * { + border-radius: 0 !important; + text-shadow: none !important; + box-shadow: none !important; + background-image: none !important; + text-decoration-color: var(--or-text-primary) !important; + outline-color: var(--or-accent) !important; +} + +/* Remove ALL gradients, transitions, and transforms from non-essential elements */ +html[data-outlook-relook-scheme] button, +html[data-outlook-relook-scheme] [role="button"], +html[data-outlook-relook-scheme] [role="tab"], +html[data-outlook-relook-scheme] [role="menuitem"], +html[data-outlook-relook-scheme] [role="option"], +html[data-outlook-relook-scheme] [role="listitem"], +html[data-outlook-relook-scheme] [role="treeitem"] { + transition: none !important; + animation: none !important; +} + +/* ============================================================ + SURFACES — flatten everything to white (or black in dark) ============================================================ */ html[data-outlook-relook-scheme] body { background-color: var(--or-bg-primary) !important; color: var(--or-text-primary) !important; } -/* Top bar */ html[data-outlook-relook-scheme] [role="banner"], html[data-outlook-relook-scheme] header { background-color: var(--or-bg-primary) !important; border-bottom: 1px solid var(--or-border) !important; - box-shadow: var(--or-shadow) !important; } -/* Folder pane */ html[data-outlook-relook-scheme] [role="navigation"], html[data-outlook-relook-scheme] [role="complementary"] { - background-color: var(--or-bg-secondary) !important; + background-color: var(--or-bg-primary) !important; border-right: 1px solid var(--or-border-light) !important; } -/* Message list */ html[data-outlook-relook-scheme] [role="listbox"], html[data-outlook-relook-scheme] [role="list"] { background-color: var(--or-bg-primary) !important; } -/* Message list items */ html[data-outlook-relook-scheme] [role="option"], html[data-outlook-relook-scheme] [role="listitem"] { - background-color: var(--or-bg-primary) !important; + background-color: transparent !important; border-bottom: 1px solid var(--or-border-light) !important; color: var(--or-text-primary) !important; } @@ -104,74 +121,81 @@ html[data-outlook-relook-scheme] [role="listitem"]:hover { html[data-outlook-relook-scheme] [role="option"][aria-selected="true"], html[data-outlook-relook-scheme] [role="listitem"][aria-selected="true"] { background-color: var(--or-bg-selected) !important; + border-left: 3px solid var(--or-accent) !important; } -/* Reading pane */ html[data-outlook-relook-scheme] [role="main"] { background-color: var(--or-bg-primary) !important; } /* ============================================================ - DECORATIVE REMOVAL + TOOLBAR — minimal, no background distinction ============================================================ */ -html[data-outlook-relook-scheme] * { - border-radius: 0 !important; - text-shadow: none !important; +html[data-outlook-relook-scheme] [role="toolbar"] { + background-color: var(--or-bg-primary) !important; + border-bottom: 1px solid var(--or-border-light) !important; } -html[data-outlook-relook-scheme] button, -html[data-outlook-relook-scheme] [role="button"], -html[data-outlook-relook-scheme] [role="tab"], -html[data-outlook-relook-scheme] [role="menuitem"] { - box-shadow: none !important; - background-image: none !important; +/* Toolbar buttons: text only, no background */ +html[data-outlook-relook-scheme] [role="toolbar"] button, +html[data-outlook-relook-scheme] [role="toolbar"] [role="button"] { + background-color: transparent !important; + border: none !important; + color: var(--or-text-primary) !important; +} + +html[data-outlook-relook-scheme] [role="toolbar"] button:hover, +html[data-outlook-relook-scheme] [role="toolbar"] [role="button"]:hover { + background-color: var(--or-bg-hover) !important; } /* ============================================================ - BUTTONS & INTERACTIVE + BUTTONS — understate everything ============================================================ */ +html[data-outlook-relook-scheme] button, +html[data-outlook-relook-scheme] [role="button"] { + background-color: transparent !important; + color: var(--or-text-primary) !important; +} + html[data-outlook-relook-scheme] button:hover, html[data-outlook-relook-scheme] [role="button"]:hover { background-color: var(--or-bg-hover) !important; } +/* Links: accent color, no underline by default */ html[data-outlook-relook-scheme] a { color: var(--or-accent) !important; + text-decoration: none !important; } html[data-outlook-relook-scheme] a:hover { + text-decoration: underline !important; color: var(--or-accent-hover) !important; } /* ============================================================ - TOOLBAR / COMMAND BAR - ============================================================ */ -html[data-outlook-relook-scheme] [role="toolbar"] { - background-color: var(--or-bg-primary) !important; - border-bottom: 1px solid var(--or-border-light) !important; - box-shadow: none !important; -} - -/* ============================================================ - FOLDER PANE TREE ITEMS + FOLDER PANE — text hierarchy only, no visual chrome ============================================================ */ html[data-outlook-relook-scheme] [role="treeitem"] { color: var(--or-text-secondary) !important; + background-color: transparent !important; } html[data-outlook-relook-scheme] [role="treeitem"]:hover { - background-color: var(--or-bg-hover) !important; color: var(--or-text-primary) !important; + background-color: transparent !important; + text-decoration: underline !important; } html[data-outlook-relook-scheme] [role="treeitem"][aria-selected="true"] { - background-color: var(--or-bg-selected) !important; color: var(--or-text-primary) !important; - font-weight: 600 !important; + font-weight: 700 !important; + background-color: transparent !important; } /* ============================================================ - SECONDARY TEXT + SECONDARY TEXT — clear hierarchy ============================================================ */ html[data-outlook-relook-scheme] [role="option"] span, html[data-outlook-relook-scheme] [role="listitem"] span { @@ -180,4 +204,32 @@ html[data-outlook-relook-scheme] [role="listitem"] span { html[data-outlook-relook-scheme] time { color: var(--or-text-tertiary) !important; + font-size: 11px !important; + text-transform: uppercase !important; + letter-spacing: 0.03em !important; +} + +/* ============================================================ + DIALOGS & OVERLAYS + ============================================================ */ +html[data-outlook-relook-scheme] [role="dialog"], +html[data-outlook-relook-scheme] [role="alertdialog"] { + background-color: var(--or-bg-primary) !important; + border: 1px solid var(--or-border) !important; +} + +/* ============================================================ + SCROLLBARS — thin, monochrome + ============================================================ */ +html[data-outlook-relook-scheme] ::-webkit-scrollbar { + width: 4px !important; + height: 4px !important; +} + +html[data-outlook-relook-scheme] ::-webkit-scrollbar-track { + background: transparent !important; +} + +html[data-outlook-relook-scheme] ::-webkit-scrollbar-thumb { + background: var(--or-text-tertiary) !important; }