devcoach 2.7.0-next.600.g5b3bf2c → 2.7.0-next.604.g26d3007

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -86,9 +86,10 @@ under 900 px the menu becomes a strip above the content. Routing by `location.ha
86
86
  (`#step-N`, no hash → step 1): on `hashchange` show that section, scroll to the top. Every
87
87
  section ends with *← Previous · Next →* buttons and the line *"Stuck? Ask in the chat — that's
88
88
  where the course is being taught."* Mark `:root[data-embedded] nav.steps { display: none }`:
89
- the dashboard injects `data-embedded`, has its own step list, makes the canvas transparent and
90
- lifts the `max-width` of `.course`, `section` and `header.intro` to use the width it has — so
91
- keep those class names.
89
+ the dashboard injects `data-embedded`, has its own step list, makes the canvas transparent,
90
+ lifts the `max-width` of `.course`, `section` and `header.intro` to use the width it has, and
91
+ sets `data-theme` from its own light/dark toggle — so keep those class names and the
92
+ `data-theme` selectors.
92
93
 
93
94
  **Each section**, in order: **Goal** (one line) → **The idea** (short, one concept, an analogy
94
95
  where it helps) → **Worked example** → **Try it** (see the ladder below) → **Check yourself**
@@ -109,35 +110,56 @@ translated into JS just to make it runnable:
109
110
  output** (typed or picked, then Reveal), **fill the blank**, or **spot the bug** (3–4
110
111
  candidates). No button may pretend to execute Java, Rust or Go.
111
112
 
112
- **Design.** One measure (~68ch), generous spacing, quiet Tufte-like typography; light + dark
113
- using the dashboard's palette (greys, indigo `#4f46e5` accents, rose for warnings). It must
114
- also pass as a Claude artifact page: `<title>` is a 2–4 word name (the long title is the
115
- `<h1>`), colour tokens live on `:root`, dark tokens are set under
116
- `@media (prefers-color-scheme: dark)` guarded by `:root:not([data-theme="light"])` and again
117
- under `:root[data-theme="dark"]`, `body` has an explicit background, 16 px side gutters at
118
- phone width, no horizontal page scroll.
119
-
120
- Skeleton (adapt, keep the ids and the class names):
113
+ **Design — conform to the dashboard, do not copy it.** One measure (~68ch), generous spacing,
114
+ quiet Tufte-like typography, light + dark. Every colour is a **token** on `:root`; nothing is
115
+ hard-coded further down (no stray `#fff` on a button, no grey typed by hand in a rule):
116
+
117
+ - **Neutrals are the dashboard's** — `--bg --panel --text --muted --line --code-bg`, with the
118
+ values of the skeleton. They are what makes an embedded course sit on the dashboard's canvas
119
+ without a seam (embedded, the viewer sets these six itself, so keep the names).
120
+ - **The accent is yours** — the brand teal of the skeleton by default, or ONE accent that suits
121
+ the subject (Rust's orange, Swift's orange-red, a database blue…), as long as `--accent` reads
122
+ at 4.5:1 or better on `--panel` in both modes and `--on-accent` reads on `--accent`.
123
+ - `--ok` / `--warn` keep their meaning (right / wrong, safe / dangerous). Colours inside a
124
+ diagram or an illustration are free.
125
+
126
+ It must also pass as a Claude artifact page: `<title>` is a 2–4 word name (the long title is
127
+ the `<h1>`), dark tokens are set under `@media (prefers-color-scheme: dark)` guarded by
128
+ `:root:not([data-theme="light"])` and again under `:root[data-theme="dark"]`, `body` has an
129
+ explicit background, 16 px side gutters at phone width, no horizontal page scroll.
130
+
131
+ **Theme switch.** Opened on its own (the file, the artifact) the document follows the OS until
132
+ the reader uses its switch: a `<button class="theme-switch">` in the step menu that sets
133
+ `data-theme` on the root to `dark` or `light`, shows the **current** scheme, carries an
134
+ `aria-label` saying what a click does, and remembers the choice with `localStorage` inside
135
+ `try/catch` (storage is unavailable in a sandboxed open — the switch must still work for the
136
+ visit). Embedded in the dashboard the switch is hidden and the stored choice is NOT applied:
137
+ the dashboard's own toggle drives `data-theme` there.
138
+
139
+ Skeleton (adapt, keep the ids, the class names and the token names):
121
140
 
122
141
  ```html
123
142
  <!doctype html><html lang="en"><head><meta charset="utf-8">
124
143
  <meta name="viewport" content="width=device-width, initial-scale=1">
125
144
  <title>Node error events</title>
126
145
  <style>
127
- :root { --bg:#f8fafc; --panel:#fff; --text:#0f172a; --muted:#64748b; --line:#e2e8f0; --accent:#4f46e5; --accent-soft:#eef2ff; --warn:#e11d48; }
128
- @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --bg:#0b1220; --panel:#111a2e; --text:#e2e8f0; --muted:#94a3b8; --line:#1e293b; --accent:#818cf8; --accent-soft:#1e1b4b; --warn:#fb7185; } }
129
- :root[data-theme="dark"] { --bg:#0b1220; --panel:#111a2e; --text:#e2e8f0; --muted:#94a3b8; --line:#1e293b; --accent:#818cf8; --accent-soft:#1e1b4b; --warn:#fb7185; }
146
+ :root { --bg:#f9fafb; --panel:#ffffff; --text:#111827; --muted:#6b7280; --line:#e5e7eb; --code-bg:#f3f4f6; --accent:#0f766e; --accent-soft:#f0fdfa; --on-accent:#ffffff; --ok:#047857; --ok-soft:#d1fae5; --warn:#be123c; --warn-soft:#ffe4e6; --log-bg:#1e1f22; --log-text:#dfe1e5; }
147
+ @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --bg:#1e1f22; --panel:#2b2d30; --text:#dfe1e5; --muted:#9da0a8; --line:#43454a; --code-bg:#1e1f22; --accent:#5eead4; --accent-soft:#134e4a; --on-accent:#0b2b28; --ok:#5fad65; --ok-soft:#1f3a24; --warn:#f97583; --warn-soft:#4a1f26; --log-bg:#18191b; --log-text:#dfe1e5; } }
148
+ :root[data-theme="dark"] { --bg:#1e1f22; --panel:#2b2d30; --text:#dfe1e5; --muted:#9da0a8; --line:#43454a; --code-bg:#1e1f22; --accent:#5eead4; --accent-soft:#134e4a; --on-accent:#0b2b28; --ok:#5fad65; --ok-soft:#1f3a24; --warn:#f97583; --warn-soft:#4a1f26; --log-bg:#18191b; --log-text:#dfe1e5; }
130
149
  body { margin:0; background:var(--bg); color:var(--text); font:16px/1.6 system-ui, sans-serif; }
131
150
  .course { display:grid; grid-template-columns:16rem minmax(0,1fr); gap:24px; max-width:1100px; margin:0 auto; padding:24px 16px; }
132
151
  nav.steps { position:sticky; top:16px; align-self:start; } nav.steps a { display:block; padding:6px 10px; border-radius:8px; color:var(--muted); text-decoration:none; }
133
152
  nav.steps a.current { background:var(--accent-soft); color:var(--accent); }
134
- :root[data-embedded] nav.steps { display:none } :root[data-embedded] .course { grid-template-columns:minmax(0,1fr); }
135
- section { display:none; max-width:68ch; } section.current { display:block; }
153
+ .theme-switch { margin-top:12px; background:transparent; color:var(--muted); border:1px solid var(--line); border-radius:8px; padding:4px 10px; cursor:pointer; }
154
+ :root[data-embedded] nav.steps, :root[data-embedded] .theme-switch { display:none } :root[data-embedded] .course { grid-template-columns:minmax(0,1fr); }
155
+ section { display:none; max-width:68ch; background:var(--panel); border:1px solid var(--line); border-radius:12px; padding:22px 24px; } section.current { display:block; }
156
+ button.primary { background:var(--accent); color:var(--on-accent); border:1px solid var(--accent); }
136
157
  @media (max-width: 900px) { .course { grid-template-columns:minmax(0,1fr); } nav.steps { position:static; display:flex; flex-wrap:wrap; gap:4px; } }
137
- /* .goal, .try, .check, .pager, buttons … */
158
+ /* .goal, .try, .check, .pager … — tokens only */
138
159
  </style></head><body>
139
160
  <div class="course">
140
- <nav class="steps"><a href="#step-1">1 · Sums</a><a href="#step-2">2 · Powers</a></nav>
161
+ <nav class="steps"><a href="#step-1">1 · Sums</a><a href="#step-2">2 · Powers</a>
162
+ <button class="theme-switch" type="button"></button></nav>
141
163
  <main>
142
164
  <section id="step-1"><h2>1 · Sums</h2><p class="goal">…</p> … <div class="check">…<button>Reveal</button></div>
143
165
  <div class="pager"><a href="#step-1">← Previous</a><a href="#step-2">Next →</a></div>
@@ -148,17 +170,55 @@ Skeleton (adapt, keep the ids and the class names):
148
170
  <script>
149
171
  // show one section: on load + hashchange, toggle .current on the section and its nav link, scrollTo(0,0)
150
172
  // reveal buttons; the live examples (new Function inside try/catch) or the simulations
173
+ // theme switch — standalone only; embedded, the dashboard sets data-theme:
174
+ (function () {
175
+ var root = document.documentElement, btn = document.querySelector(".theme-switch"), KEY = "course-theme";
176
+ var isDark = function () {
177
+ var t = root.getAttribute("data-theme");
178
+ return t ? t === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
179
+ };
180
+ var paint = function () {
181
+ btn.textContent = isDark() ? "🌙 Dark" : "☀️ Light";
182
+ btn.setAttribute("aria-label", isDark() ? "Dark theme — switch to light" : "Light theme — switch to dark");
183
+ };
184
+ if (window.parent === window) {
185
+ try { var saved = localStorage.getItem(KEY); if (saved === "dark" || saved === "light") root.setAttribute("data-theme", saved); } catch (e) {}
186
+ }
187
+ btn.addEventListener("click", function () {
188
+ var next = isDark() ? "light" : "dark";
189
+ root.setAttribute("data-theme", next);
190
+ try { localStorage.setItem(KEY, next); } catch (e) {}
191
+ paint();
192
+ });
193
+ matchMedia("(prefers-color-scheme: dark)").addEventListener("change", paint);
194
+ paint();
195
+ })();
151
196
  </script></body></html>
152
197
  ```
153
198
 
154
- ### 4b. Share as an artifact — an offer, nothing more
199
+ ### 4b. Share as an artifact — an offer, with the disclosure
200
+
201
+ Publishing is the one moment course content **leaves the machine**: the Artifact tool uploads
202
+ a copy of the document to claude.ai, under the user's account. The offer must say so — a
203
+ "private" link describes who can open it, not where the file lives.
155
204
 
156
205
  After the last `add_course_step`, **if the Artifact tool is available**, offer ONCE, as a
157
- choice: *"Publish this course as a Claude artifact too? (private until you share the
158
- link)"* — **Yes** → publish `document_path` as it is (`icon: "course"`, `description` = the
159
- goal) and reply with the link in one line; **No**, or no Artifact tool → nothing. The local
160
- file stays the course; the artifact is a copy for sharing, and the user can ask for it again
161
- any time ("publish the course as an artifact"). Never store the link anywhere.
206
+ choice, in these terms:
207
+
208
+ > *Publish this course as a Claude artifact too? That **uploads a copy to claude.ai** — it
209
+ > leaves this machine. Only you can open it until you share the link.*
210
+
211
+ - A course usually carries the user's own work (examples come from `seed_context`: project and
212
+ repository names, file paths, snippets, the bug that started it). When this one does, name
213
+ what it contains **in the same question** — *"It mentions lolcoach and the hang you
214
+ debugged."* — so the yes is an informed one. Never publish without that yes.
215
+ - **Yes** → publish `document_path` as it is (`icon: "course"`, `description` = the goal) and
216
+ reply with the link in one line. **No**, or no Artifact tool → nothing, and do not ask again.
217
+ - The local file stays the course; the artifact is a copy for sharing. The user can ask for it
218
+ any time ("publish the course as an artifact") — the same disclosure applies each time, and
219
+ to every republish after an edit. devcoach stores nothing about the artifact, so it cannot
220
+ delete it either: if asked to take it down, use the Artifact tool or point the user to their
221
+ artifacts on claude.ai.
162
222
 
163
223
  ## 5. Teach — one step per message
164
224
 
@@ -1,8 +1,10 @@
1
1
  // Injected by the dashboard at the end of every course document it serves (never part of the
2
2
  // file on disk). Inside the sandboxed frame the parent cannot measure the document, so the
3
3
  // document reports its own height and the step it is showing; the parent grows the frame to fit
4
- // (the page scrolls, never the frame) and moves its step highlight. Standalone opens (a pasted
5
- // URL, the artifact copy) never run this: `parent === window` exits at once.
4
+ // (the page scrolls, never the frame) and moves its step highlight. The dashboard in turn tells
5
+ // the document which theme it is in: framed, the document only sees the OS preference, not the
6
+ // dashboard's toggle. Standalone opens (a pasted URL, the artifact copy) never run this:
7
+ // `parent === window` exits at once, and the document's own theme switch is in charge.
6
8
  (function () {
7
9
  if (window.parent === window) return;
8
10
  var root = document.documentElement;
@@ -10,12 +12,19 @@
10
12
  // Embedded, the page canvas is the dashboard's: let it show through (sections keep their own
11
13
  // panel colours), so the frame reads as part of the page instead of a box inside it.
12
14
  root.style.background = "transparent";
13
- // …and use the width the dashboard gives it: the reading-column caps of the contract's
14
- // `.course` / `section` / `header.intro` are for the standalone page.
15
+ // …use the width the dashboard gives it (the reading-column caps of the contract's `.course` /
16
+ // `section` / `header.intro` are for the standalone page), and take the NEUTRALS from the
17
+ // dashboard palette so panels and text sit on its canvas without a seam. Accent and semantic
18
+ // tokens stay the document's own: a course conforms to the palette, it does not copy it.
15
19
  var style = document.createElement("style");
16
20
  style.textContent =
17
21
  ":root[data-embedded] .course{max-width:none;padding:4px 0 32px}" +
18
- ":root[data-embedded] section,:root[data-embedded] header.intro{max-width:none}";
22
+ ":root[data-embedded] section,:root[data-embedded] header.intro{max-width:none}" +
23
+ ":root[data-embedded] .theme-switch{display:none}" +
24
+ ':root[data-embedded][data-theme="light"]{--bg:#f9fafb;--panel:#ffffff;--text:#111827;' +
25
+ "--muted:#6b7280;--line:#e5e7eb;--code-bg:#f3f4f6}" +
26
+ ':root[data-embedded][data-theme="dark"]{--bg:#1e1f22;--panel:#2b2d30;--text:#dfe1e5;' +
27
+ "--muted:#9da0a8;--line:#43454a;--code-bg:#1e1f22}";
19
28
  (document.head || root).appendChild(style);
20
29
  function clearBody() { document.body.style.background = "transparent"; }
21
30
  if (document.body) clearBody(); else document.addEventListener("DOMContentLoaded", clearBody);
@@ -27,6 +36,15 @@
27
36
  "*",
28
37
  );
29
38
  }
39
+ // The dashboard answers every report with its theme (the first report is the handshake).
40
+ window.addEventListener("message", function (event) {
41
+ var data = event.data;
42
+ if (event.source !== window.parent || !data || data.type !== "devcoach:theme") return;
43
+ if (data.theme !== "dark" && data.theme !== "light") return;
44
+ if (root.getAttribute("data-theme") === data.theme) return;
45
+ root.setAttribute("data-theme", data.theme);
46
+ report();
47
+ });
30
48
  window.addEventListener("load", report);
31
49
  window.addEventListener("hashchange", report);
32
50
  if (typeof ResizeObserver === "function") new ResizeObserver(report).observe(root);
@@ -1,12 +1,13 @@
1
1
  // Course page: the sandboxed frame reports its height and current step (course-frame.js, injected
2
2
  // by the server); this side sizes the frame to the document so the page scrolls, keeps the step
3
3
  // list in sync, and turns step clicks into in-frame navigation (a fragment navigation of the
4
- // cross-origin frame) instead of a page reload.
4
+ // cross-origin frame) instead of a page reload. It also tells the frame which theme the
5
+ // dashboard is in: the framed document cannot see the toggle, only the OS preference.
5
6
  (function () {
6
7
  var frame = document.getElementById("course-frame");
7
8
  var list = document.getElementById("course-steps");
8
9
  if (!frame || !list) return;
9
- var CURRENT = ["bg-indigo-50", "dark:bg-indigo-900/30"];
10
+ var CURRENT = ["bg-accent-50", "dark:bg-accent-900/30"];
10
11
  var shown = null;
11
12
 
12
13
  function select(anchor) {
@@ -22,6 +23,15 @@
22
23
  });
23
24
  }
24
25
 
26
+ // The frame's origin is opaque (sandbox without allow-same-origin): "*" is the only target
27
+ // that reaches it, and the payload is one word.
28
+ function sendTheme() {
29
+ if (!frame.contentWindow) return;
30
+ var dark = document.documentElement.classList.contains("dark");
31
+ frame.contentWindow.postMessage({ type: "devcoach:theme", theme: dark ? "dark" : "light" }, "*");
32
+ }
33
+ document.addEventListener("dc:theme", sendTheme);
34
+
25
35
  function scrollToFrame() {
26
36
  var top = frame.getBoundingClientRect().top + window.scrollY - 16;
27
37
  window.scrollTo({ top: Math.max(0, top), behavior: "smooth" });
@@ -35,6 +45,9 @@
35
45
  frame.style.height = Math.min(50000, Math.max(320, Math.ceil(height))) + "px";
36
46
  }
37
47
  if (typeof data.anchor === "string") select(data.anchor);
48
+ // Every report is answered with the theme: the first one is the handshake, so the message
49
+ // can never arrive before the frame listens.
50
+ sendTheme();
38
51
  });
39
52
 
40
53
  list.addEventListener("click", function (event) {
@@ -1,5 +1,56 @@
1
1
  /* devcoach — style overrides with light/dark theme support */
2
2
 
3
+ /* ── Palette tokens ─────────────────────────────────────────────────────────
4
+ The one place colours are decided. Values are space-separated RGB triplets so Tailwind can
5
+ apply its opacity modifiers: layout() in src/web/views.ts points Tailwind's `gray` and
6
+ `accent` scales at these variables (`rgb(var(--dc-gray-900) / <alpha-value>)`), which is how
7
+ every `dark:bg-gray-900` in the views picks the palette up without being rewritten.
8
+ - Light neutrals: Tailwind's own gray.
9
+ - Dark neutrals: an IDE ramp (JetBrains New UI dark) — page → panel → raised → border.
10
+ - Accent: the brand teal of the wordmark and the docs site, one step darker from 500 up so
11
+ white text on accent-600 and accent-600 text on white both clear 4.5:1.
12
+ tests/theme.test.ts holds the contrast floor for the pairs the UI relies on. */
13
+
14
+ :root {
15
+ --dc-gray-50: 249 250 251;
16
+ --dc-gray-100: 243 244 246;
17
+ --dc-gray-200: 229 231 235;
18
+ --dc-gray-300: 209 213 219;
19
+ --dc-gray-400: 156 163 175;
20
+ --dc-gray-500: 107 114 128;
21
+ --dc-gray-600: 75 85 99;
22
+ --dc-gray-700: 55 65 81;
23
+ --dc-gray-800: 31 41 55;
24
+ --dc-gray-900: 17 24 39;
25
+ --dc-gray-950: 3 7 18;
26
+
27
+ --dc-accent-50: 240 253 250;
28
+ --dc-accent-100: 204 251 241;
29
+ --dc-accent-200: 153 246 228;
30
+ --dc-accent-300: 94 234 212;
31
+ --dc-accent-400: 45 212 191;
32
+ --dc-accent-500: 13 148 136;
33
+ --dc-accent-600: 15 118 110;
34
+ --dc-accent-700: 17 94 89;
35
+ --dc-accent-800: 19 78 74;
36
+ --dc-accent-900: 15 61 58;
37
+ --dc-accent-950: 4 47 46;
38
+ }
39
+
40
+ .dark {
41
+ --dc-gray-50: 245 246 247;
42
+ --dc-gray-100: 236 238 241;
43
+ --dc-gray-200: 223 225 229; /* text */
44
+ --dc-gray-300: 188 190 196;
45
+ --dc-gray-400: 157 160 168; /* muted text */
46
+ --dc-gray-500: 144 150 160; /* secondary text */
47
+ --dc-gray-600: 111 115 122; /* disabled, faint */
48
+ --dc-gray-700: 67 69 74; /* borders */
49
+ --dc-gray-800: 57 59 64; /* raised: inputs, hover rows */
50
+ --dc-gray-900: 43 45 48; /* panels, nav, cards */
51
+ --dc-gray-950: 30 31 34; /* page canvas */
52
+ }
53
+
3
54
  /* ── Theme variables ────────────────────────────────────────────────────── */
4
55
 
5
56
  :root {
@@ -17,10 +68,10 @@
17
68
  --md-pre-bg: #f8fafc;
18
69
  --md-pre-border: #e2e8f0;
19
70
  --md-blockquote-color: #6b7280;
20
- --md-blockquote-border: #6366f1;
71
+ --md-blockquote-border: rgb(var(--dc-accent-500));
21
72
  --md-hr: #e5e7eb;
22
- --md-link: #4f46e5;
23
- --md-link-hover: #6366f1;
73
+ --md-link: rgb(var(--dc-accent-600));
74
+ --md-link-hover: rgb(var(--dc-accent-500));
24
75
  --md-table-th-bg: #f9fafb;
25
76
  --md-table-th-color: #374151;
26
77
  --md-table-border: #e5e7eb;
@@ -34,36 +85,40 @@
34
85
  }
35
86
 
36
87
  .dark {
37
- --scrollbar-track: #111827;
38
- --scrollbar-thumb: #374151;
39
- --scrollbar-thumb-hover: #4b5563;
40
-
41
- --md-text: #e5e7eb;
42
- --md-heading: #f9fafb;
43
- --md-heading-h3: #d1d5db;
44
- --md-h2-border: #374151;
45
- --md-code-bg: #1f2937;
88
+ --scrollbar-track: rgb(var(--dc-gray-950));
89
+ --scrollbar-thumb: rgb(var(--dc-gray-700));
90
+ --scrollbar-thumb-hover: rgb(var(--dc-gray-600));
91
+
92
+ --md-text: rgb(var(--dc-gray-200));
93
+ --md-heading: rgb(var(--dc-gray-100));
94
+ --md-heading-h3: rgb(var(--dc-gray-300));
95
+ --md-h2-border: rgb(var(--dc-gray-700));
96
+ --md-code-bg: rgb(var(--dc-gray-800));
46
97
  --md-code-color: #f472b6;
47
- --md-code-border: #374151;
48
- --md-pre-bg: #282c34;
49
- --md-pre-border: #374151;
50
- --md-blockquote-color: #9ca3af;
51
- --md-blockquote-border: #6366f1;
52
- --md-hr: #374151;
53
- --md-link: #818cf8;
54
- --md-link-hover: #a5b4fc;
55
- --md-table-th-bg: #1f2937;
56
- --md-table-th-color: #d1d5db;
57
- --md-table-border: #374151;
58
- --md-table-td-color: #e5e7eb;
59
- --md-table-even-bg: #111827;
60
- --md-strong: #f3f4f6;
98
+ --md-code-border: rgb(var(--dc-gray-700));
99
+ --md-pre-bg: rgb(var(--dc-gray-950));
100
+ --md-pre-border: rgb(var(--dc-gray-700));
101
+ --md-blockquote-color: rgb(var(--dc-gray-400));
102
+ --md-blockquote-border: rgb(var(--dc-accent-400));
103
+ --md-hr: rgb(var(--dc-gray-700));
104
+ --md-link: rgb(var(--dc-accent-300));
105
+ --md-link-hover: rgb(var(--dc-accent-200));
106
+ --md-table-th-bg: rgb(var(--dc-gray-800));
107
+ --md-table-th-color: rgb(var(--dc-gray-300));
108
+ --md-table-border: rgb(var(--dc-gray-700));
109
+ --md-table-td-color: rgb(var(--dc-gray-200));
110
+ --md-table-even-bg: rgb(var(--dc-gray-800) / 0.4);
111
+ --md-strong: rgb(var(--dc-gray-100));
61
112
  --md-em: #c4b5fd;
62
113
 
63
- --check-border: #4b5563;
64
- --check-bg: #111827;
114
+ --check-border: rgb(var(--dc-gray-600));
115
+ --check-bg: rgb(var(--dc-gray-900));
65
116
  }
66
117
 
118
+ /* Code blocks: keep the highlighter's token colours, sit them on the editor background. */
119
+ .dark .markdown-body pre code.hljs,
120
+ .dark .hljs { background: var(--md-pre-bg); }
121
+
67
122
  /* ── Scrollbar ──────────────────────────────────────────────────────────── */
68
123
 
69
124
  ::-webkit-scrollbar { width: 6px; height: 6px; }
@@ -184,15 +239,15 @@
184
239
  transition: background-color 0.12s, border-color 0.12s;
185
240
  }
186
241
  .dc-check:checked {
187
- background-color: #4f46e5;
188
- border-color: #4f46e5;
242
+ background-color: rgb(var(--dc-accent-600));
243
+ border-color: rgb(var(--dc-accent-600));
189
244
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 10.5l3.5 3.5L15 7'/%3E%3C/svg%3E");
190
245
  background-size: 72%;
191
246
  background-position: center;
192
247
  background-repeat: no-repeat;
193
248
  }
194
249
  .dc-check:focus-visible {
195
- outline: 2px solid #6366f1;
250
+ outline: 2px solid rgb(var(--dc-accent-500));
196
251
  outline-offset: 2px;
197
252
  }
198
253
 
@@ -220,7 +275,7 @@
220
275
  }
221
276
  .dc-resize-handle:hover::after,
222
277
  .dc-resizing .dc-resize-handle:active::after {
223
- background-color: rgba(99, 102, 241, 0.85);
278
+ background-color: rgb(var(--dc-accent-500) / 0.85);
224
279
  }
225
280
  .dc-resizing {
226
281
  user-select: none;