otter-dark-mode/otter-dark.css
Christian Sass c10cf59e67 feat: three-tier dark theme proposal for Otter.ai
Otter's web app ships no dark mode, and heavy users read transcripts for hours
at night. This proposes three shades rather than one, so the choice survives
changing lighting instead of being wrong half the day.

Contents:
- otter-dark.css   token set, one block per tier, scoped to :root[data-theme]
- preview.html     self-contained demo, no build, no network
- PREVIEW.png      rendered screenshot of the demo
- userscript/      Tampermonkey stopgap that works today

Contrast figures in the README are measured from the token values with the
WCAG 2.1 formula, not estimated. The notable finding is that Otter's brand blue
#0b57d0 fails as accent text on every dark surface (2.59:1 to 3.29:1) while
remaining correct as a solid fill (6.39:1), so the tokens split accent from
accent-fill.

MIT licensed. No affiliation with AISense Inc.

Co-authored-by: CLU <clu@binaryranch.com>
2026-10-27 12:42:16 -05:00

102 lines
4.2 KiB
CSS

/* =============================================================================
Otter dark theme tokens
Proposal by Christian Sass (csass@binaryranch.com), Binary Ranch. 2026-10-27.
Three tiers, one token set. Nothing here filters, inverts or transforms the
page, so avatars, screen captures and video keep their exact original colour.
Usage: set data-theme on <html>. That is the whole contract.
<html data-theme="dusk">
Values in comments are the measured WCAG 2.1 contrast ratio against --ot-canvas
unless stated otherwise.
============================================================================= */
/* ---- Midnight: OLED panels and late-night reading ---- */
:root[data-theme="midnight"] {
--ot-canvas: #000000;
--ot-surface-1: #0B0D10; /* sidebars, nav rails, player bar */
--ot-surface-2: #12151A; /* conversation cards, list rows, panels */
--ot-surface-3: #1A1E24; /* hover, selected row, menus */
--ot-border: #22272E; /* hairlines, card outlines (decorative) */
--ot-border-2: #303740; /* focused input, split-pane divider */
--ot-text: #E8EBEF; /* 17.56:1 on canvas */
--ot-text-2: #A0A8B4; /* 8.75:1 on canvas */
--ot-text-3: #6B7480; /* 4.44:1 on canvas */
--ot-accent: #6FA8FF; /* 8.72:1, links and active nav */
--ot-accent-fill:#6FA8FF; /* solid button / active pill background */
--ot-on-accent: #0A1220; /* 7.78:1 on the fill */
--ot-danger: #FF6B6B; /* 7.57:1 */
--ot-warn: #FFB020;
--ot-success: #4ADE80;
color-scheme: dark;
}
/* ---- Dusk: default, mixed lighting ---- */
:root[data-theme="dusk"] {
--ot-canvas: #101216;
--ot-surface-1: #16191E; /* sidebars, nav rails, player bar */
--ot-surface-2: #1C2027; /* conversation cards, list rows, panels */
--ot-surface-3: #242931; /* hover, selected row, menus */
--ot-border: #2A3038; /* hairlines, card outlines (decorative) */
--ot-border-2: #3A424C; /* focused input, split-pane divider */
--ot-text: #E6E9ED; /* 15.39:1 on canvas */
--ot-text-2: #A6ADB8; /* 8.29:1 on canvas */
--ot-text-3: #737B86; /* 4.38:1 on canvas */
--ot-accent: #7FAEFF; /* 8.39:1, links and active nav */
--ot-accent-fill:#7FAEFF; /* solid button / active pill background */
--ot-on-accent: #0A1220; /* 8.39:1 on the fill */
--ot-danger: #FF7B72; /* 7.43:1 */
--ot-warn: #FFC14D;
--ot-success: #5BD98A;
color-scheme: dark;
}
/* ---- Slate: bright rooms and daytime ---- */
:root[data-theme="slate"] {
--ot-canvas: #1B1F24;
--ot-surface-1: #22272D; /* sidebars, nav rails, player bar */
--ot-surface-2: #282E35; /* conversation cards, list rows, panels */
--ot-surface-3: #30373F; /* hover, selected row, menus */
--ot-border: #363E47; /* hairlines, card outlines (decorative) */
--ot-border-2: #48515C; /* focused input, split-pane divider */
--ot-text: #ECEEF1; /* 14.25:1 on canvas */
--ot-text-2: #B4BAC4; /* 8.49:1 on canvas */
--ot-text-3: #8A919C; /* 5.21:1 on canvas */
--ot-accent: #8FB8FF; /* 8.26:1, links and active nav */
--ot-accent-fill:#8FB8FF; /* solid button / active pill background */
--ot-on-accent: #0A1220; /* 9.34:1 on the fill */
--ot-danger: #FF8A80; /* 7.25:1 */
--ot-warn: #FFCB66;
--ot-success: #6FE39B;
color-scheme: dark;
}
/* Opt in to the system preference, defaulting to Dusk. */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) {
--ot-canvas: #101216; --ot-surface-1: #16191E; --ot-surface-2: #1C2027; --ot-surface-3: #242931;
--ot-border: #2A3038; --ot-border-2: #3A424C;
--ot-text: #E6E9ED; --ot-text-2: #A6ADB8; --ot-text-3: #737B86;
--ot-accent: #7FAEFF; --ot-accent-fill: #7FAEFF; --ot-on-accent: #0A1220;
--ot-danger: #FF7B72; --ot-warn: #FFC14D; --ot-success: #5BD98A;
color-scheme: dark;
}
}
/* Reference wiring. Otter's own selectors replace the generic ones; the token
names are the part worth keeping. */
[data-theme] body { background: var(--ot-canvas); color: var(--ot-text); }
[data-theme] a { color: var(--ot-accent); }
[data-theme] ::selection { background: color-mix(in srgb, var(--ot-accent) 30%, transparent); }