otter-dark-mode/preview.html
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

239 lines
12 KiB
HTML

<!doctype html>
<html lang="en" data-theme="dusk">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Otter dark theme - three tiers, preview</title>
<style>
:root {
--ink: #14161a; --paper: #f4f5f7; --rule: #dcdfe4; --muted: #6b7280;
--brand: #0b57d0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
* { box-sizing: border-box; }
body { margin: 0; padding: 34px 30px 46px; background: var(--paper); color: var(--ink); }
header.page { max-width: 1290px; margin: 0 auto 26px; }
header.page h1 { font-size: 21px; margin: 0 0 6px; letter-spacing: -.01em; }
header.page p { margin: 0; color: var(--muted); font-size: 13.5px; max-width: 74ch; line-height: 1.55; }
header.page .byline { margin-top: 10px; font-size: 11.5px; color: var(--muted); letter-spacing: .02em; }
.deck { display: flex; gap: 20px; justify-content: center; align-items: flex-start; flex-wrap: wrap; max-width: 1290px; margin: 0 auto; }
/* ---- tier frames ---- */
.tier { width: 396px; border-radius: 14px; overflow: hidden; box-shadow: 0 10px 26px rgba(15,20,30,.16); background: var(--canvas); }
.t-midnight { --canvas:#000000; --s1:#0B0D10; --s2:#12151A; --s3:#1A1E24; --bd:#22272E; --bd2:#303740;
--tx:#E8EBEF; --tx2:#A0A8B4; --tx3:#6B7480; --ac:#6FA8FF; --fill:#6FA8FF; --on:#0A1220; }
.t-dusk { --canvas:#101216; --s1:#16191E; --s2:#1C2027; --s3:#242931; --bd:#2A3038; --bd2:#3A424C;
--tx:#E6E9ED; --tx2:#A6ADB8; --tx3:#737B86; --ac:#7FAEFF; --fill:#7FAEFF; --on:#0A1220; }
.t-slate { --canvas:#1B1F24; --s1:#22272D; --s2:#282E35; --s3:#30373F; --bd:#363E47; --bd2:#48515C;
--tx:#ECEEF1; --tx2:#B4BAC4; --tx3:#8A919C; --ac:#8FB8FF; --fill:#8FB8FF; --on:#0A1220; }
.app { display: flex; height: 348px; color: var(--tx); }
.rail { width: 118px; background: var(--s1); border-right: 1px solid var(--bd); padding: 11px 9px; font-size: 10.5px; flex: none; }
.brandrow { display: flex; align-items: center; gap: 6px; margin-bottom: 13px; }
.logo { width: 15px; height: 15px; border-radius: 4px; background: var(--brand); }
.brandrow b { font-size: 11px; font-weight: 600; letter-spacing: -.01em; }
.nav { padding: 5px 7px; border-radius: 6px; color: var(--tx2); margin-bottom: 1px; }
.nav.on { background: var(--s3); color: var(--ac); font-weight: 600; }
.navdots { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--bd); }
.navdots .lbl { font-size: 9px; letter-spacing: .09em; text-transform: uppercase; color: var(--tx3); padding-left: 7px; margin-bottom: 5px; }
.pane { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.topbar { display: flex; align-items: center; gap: 6px; padding: 9px 11px; border-bottom: 1px solid var(--bd); background: var(--canvas); }
.search { flex: 1; background: var(--s2); border: 1px solid var(--bd); border-radius: 6px; padding: 4px 8px; font-size: 10px; color: var(--tx3); }
.rec { background: var(--fill); color: var(--on); font-size: 10px; font-weight: 600; padding: 4px 11px; border-radius: 12px; white-space: nowrap; }
.body { flex: 1; padding: 10px 11px; overflow: hidden; }
.when { font-size: 9.5px; color: var(--tx3); margin-bottom: 7px; }
.card { display: flex; gap: 8px; background: var(--s2); border: 1px solid var(--bd); border-radius: 9px; padding: 9px 10px; margin-bottom: 8px; }
.av { width: 21px; height: 21px; border-radius: 50%; flex: none; }
.a1 { background: linear-gradient(135deg, #f6b26b, #cc4125); }
.a2 { background: linear-gradient(135deg, #6fa8dc, #1c4587); }
.card h4 { margin: 0 0 2px; font-size: 11.5px; font-weight: 600; letter-spacing: -.005em; }
.card .meta { font-size: 9px; color: var(--tx3); margin-bottom: 4px; }
.card p { margin: 0; font-size: 10px; line-height: 1.45; color: var(--tx2); }
.hl { background: color-mix(in srgb, var(--ac) 22%, transparent); color: var(--tx); border-radius: 3px; padding: 0 2px; }
.who { font-size: 9px; font-weight: 600; color: var(--ac); margin-bottom: 2px; }
.bubble { background: var(--s3); border-radius: 8px; padding: 6px 8px; font-size: 10px; line-height: 1.45; color: var(--tx); }
.caption { background: var(--canvas); border-top: 1px solid var(--bd); padding: 12px 14px 14px; color: var(--tx); }
.caption .name { display: flex; align-items: baseline; gap: 8px; }
.caption .name b { font-size: 13.5px; }
.caption .name code { font-size: 10px; color: var(--tx2); }
.caption .use { font-size: 10.5px; color: var(--tx3); margin: 2px 0 9px; }
.sw { display: flex; gap: 4px; margin-bottom: 8px; }
.sw i { width: 22px; height: 12px; border-radius: 3px; border: 1px solid rgba(255,255,255,.08); }
.nums { display: flex; gap: 12px; font-size: 9.5px; color: var(--tx3); }
.nums b { color: var(--tx); font-weight: 600; }
footer.page { max-width: 1290px; margin: 30px auto 0; padding-top: 16px; border-top: 1px solid var(--rule);
font-size: 11.5px; color: var(--muted); line-height: 1.6; }
footer.page code { background: #e8ebef; padding: 1px 5px; border-radius: 3px; font-size: 11px; }
</style>
</head>
<body>
<header class="page">
<h1>Otter dark mode, three tiers</h1>
<p>Same component, same text sizes, three surface lightnesses. Nothing below inverts or filters
the page, so avatars and screen captures keep their true colour. Contrast figures are measured
from the token values with the WCAG 2.1 formula.</p>
<div class="byline">PREPARED BY CHRISTIAN SASS &middot; BINARY RANCH &middot; CSASS@BINARYRANCH.COM &middot; 2026-10-27</div>
</header>
<div class="deck">
<!-- MIDNIGHT -->
<section class="tier t-midnight">
<div class="app">
<div class="rail">
<div class="brandrow"><span class="logo"></span><b>Otter</b></div>
<div class="nav on">Home</div>
<div class="nav">AI Chat</div>
<div class="nav">Explore</div>
<div class="navdots">
<div class="lbl">Channels</div>
<div class="nav">Engineering</div>
<div class="nav">General</div>
</div>
</div>
<div class="pane">
<div class="topbar"><div class="search">Ask or search</div><div class="rec">Record</div></div>
<div class="body">
<div class="when">Today, Oct 27</div>
<div class="card">
<div class="av a1"></div>
<div>
<h4>Weekly product sync</h4>
<div class="meta">10:53 AM &middot; 38 min</div>
<p>The team walks through the revised onboarding flow and agrees to drop the second step, which was <span class="hl">tripping up first-time users</span> on mobile.</p>
</div>
</div>
<div class="card">
<div class="av a2"></div>
<div>
<h4>Design review</h4>
<div class="meta">9:15 AM &middot; 42 min</div>
<p>Shipping the client build Thursday. The remaining blocker is the mail server certificate.</p>
</div>
</div>
<div class="who">Priya</div>
<div class="bubble">The certificate is already staged in staging. I can rotate it before the demo if we get the window.</div>
</div>
</div>
</div>
<div class="caption">
<div class="name"><b>Midnight</b><code>#000000</code></div>
<div class="use">OLED panels and late-night reading</div>
<div class="sw"><i style="background:#000000"></i><i style="background:#0B0D10"></i><i style="background:#12151A"></i><i style="background:#1A1E24"></i><i style="background:#6FA8FF"></i></div>
<div class="nums"><span>Body <b>17.6:1</b></span><span>Secondary <b>8.8:1</b></span><span>Button <b>7.8:1</b></span></div>
</div>
</section>
<!-- DUSK -->
<section class="tier t-dusk">
<div class="app">
<div class="rail">
<div class="brandrow"><span class="logo"></span><b>Otter</b></div>
<div class="nav on">Home</div>
<div class="nav">AI Chat</div>
<div class="nav">Explore</div>
<div class="navdots">
<div class="lbl">Channels</div>
<div class="nav">Engineering</div>
<div class="nav">General</div>
</div>
</div>
<div class="pane">
<div class="topbar"><div class="search">Ask or search</div><div class="rec">Record</div></div>
<div class="body">
<div class="when">Today, Oct 27</div>
<div class="card">
<div class="av a1"></div>
<div>
<h4>Weekly product sync</h4>
<div class="meta">10:53 AM &middot; 38 min</div>
<p>The team walks through the revised onboarding flow and agrees to drop the second step, which was <span class="hl">tripping up first-time users</span> on mobile.</p>
</div>
</div>
<div class="card">
<div class="av a2"></div>
<div>
<h4>Design review</h4>
<div class="meta">9:15 AM &middot; 42 min</div>
<p>Shipping the client build Thursday. The remaining blocker is the mail server certificate.</p>
</div>
</div>
<div class="who">Priya</div>
<div class="bubble">The certificate is already staged in staging. I can rotate it before the demo if we get the window.</div>
</div>
</div>
</div>
<div class="caption">
<div class="name"><b>Dusk</b><code>#101216</code></div>
<div class="use">Default, mixed lighting</div>
<div class="sw"><i style="background:#101216"></i><i style="background:#16191E"></i><i style="background:#1C2027"></i><i style="background:#242931"></i><i style="background:#7FAEFF"></i></div>
<div class="nums"><span>Body <b>15.4:1</b></span><span>Secondary <b>8.3:1</b></span><span>Button <b>8.4:1</b></span></div>
</div>
</section>
<!-- SLATE -->
<section class="tier t-slate">
<div class="app">
<div class="rail">
<div class="brandrow"><span class="logo"></span><b>Otter</b></div>
<div class="nav on">Home</div>
<div class="nav">AI Chat</div>
<div class="nav">Explore</div>
<div class="navdots">
<div class="lbl">Channels</div>
<div class="nav">Engineering</div>
<div class="nav">General</div>
</div>
</div>
<div class="pane">
<div class="topbar"><div class="search">Ask or search</div><div class="rec">Record</div></div>
<div class="body">
<div class="when">Today, Oct 27</div>
<div class="card">
<div class="av a1"></div>
<div>
<h4>Weekly product sync</h4>
<div class="meta">10:53 AM &middot; 38 min</div>
<p>The team walks through the revised onboarding flow and agrees to drop the second step, which was <span class="hl">tripping up first-time users</span> on mobile.</p>
</div>
</div>
<div class="card">
<div class="av a2"></div>
<div>
<h4>Design review</h4>
<div class="meta">9:15 AM &middot; 42 min</div>
<p>Shipping the client build Thursday. The remaining blocker is the mail server certificate.</p>
</div>
</div>
<div class="who">Priya</div>
<div class="bubble">The certificate is already staged in staging. I can rotate it before the demo if we get the window.</div>
</div>
</div>
</div>
<div class="caption">
<div class="name"><b>Slate</b><code>#1B1F24</code></div>
<div class="use">Bright rooms and daytime</div>
<div class="sw"><i style="background:#1B1F24"></i><i style="background:#22272D"></i><i style="background:#282E35"></i><i style="background:#30373F"></i><i style="background:#8FB8FF"></i></div>
<div class="nums"><span>Body <b>14.3:1</b></span><span>Secondary <b>8.5:1</b></span><span>Button <b>9.3:1</b></span></div>
</div>
</section>
</div>
<footer class="page">
Token values live in <code>otter-dark.css</code>. Set <code>data-theme="midnight|dusk|slate"</code> on
<code>&lt;html&gt;</code> and read them with <code>var(--ot-*)</code>. The brand blue
<code>#0b57d0</code> is kept as a fill for the Otter mark and for the light theme; it measures
2.6:1 to 3.3:1 as text on these canvases, so links and accent text use the lightened accent tokens instead.
</footer>
</body>
</html>