From f5212b62057c2d0f36be86e32ad094f3ccdc83b9 Mon Sep 17 00:00:00 2001 From: holo Date: Sat, 27 Jun 2026 21:34:09 +0800 Subject: [PATCH 1/2] fix(ui): center AI-translate dual columns, consolidate MB-Web tokens, sync DESIGN.md fonts (0.8.3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bilingual reader's root carries `markdown-body bilingual-cols`, so it inherited the single-column `max-width: 72ch` and crushed both columns to the left, leaving the pane's right half empty. `.bilingual-cols` now overrides the cap: it fills the reader pane (each column gets its own measure) and, past two measures on an ultra-wide pane, caps and centers. Guarded by a new bilingual.spec.ts e2e asserting the columns fill ≥95% of the pane. MB-Web kept a parallel light+dark literal palette that duplicated the workbench tokens value-for-value. Differently-named locals now alias the shared token (`--ac: var(--accent)`, `--border: var(--line)`, …), same-named ones inherit the global values, and the parallel dark block is gone — only the genuinely MB-specific literals (answer-blue, on-accent fg, AA-darkened faint, hover wash) remain. Off-scale "magic" radii snapped to the 4/6/7/8/999 scale (cards 8px; badge/chip/filter/toast/sync pills fully round); the note highlighter yellows became named --mb-mark / --mb-mark-staged tokens. Zero-visual-change for colors; verified in the browser across library, reader and notes in light + dark. DESIGN.md's typography frontmatter still named the pre-v0.8.0 families (Source Serif 4 / Inter Tight / JetBrains Mono) with swapped weights (display 500, label 600), contradicting the shipped IBM Plex superfamily and the §3 table — now corrected, along with a stray §3 "JetBrains Mono" mention and a ui-checklist item for the dual-column fill. Co-Authored-By: Claude Opus 4.8 --- .gitignore | 3 ++ CHANGELOG.md | 32 ++++++++++++++ DESIGN.md | 41 +++++++++-------- docs/ui-checklist.md | 11 ++++- package.json | 2 +- src/mb/mb.css | 87 +++++++++++++++++-------------------- src/styles.css | 9 +++- tests/e2e/bilingual.spec.ts | 22 ++++++++++ 8 files changed, 138 insertions(+), 69 deletions(-) diff --git a/.gitignore b/.gitignore index a09d30c..8bd827d 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ vite*.log # impeccable design-skill detector cache (transient; the design system itself # lives in DESIGN.md + .impeccable/design.json, which ARE tracked). .impeccable/hook.cache.json + +# impeccable critique snapshots (local evaluation history, per-run) +.impeccable/critique/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 35d8f57..ba390bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,38 @@ file format introduced in `[0.0.1.0]` was dropped. ## [Unreleased] +## [0.8.3] - 2026-06-27 + +### Fixed + +- **AI-translate dual columns fill the reader pane.** The bilingual view's root + inherits `.markdown-body`'s single-column `max-width: 72ch`, so the source / + translation columns crushed into one measure on the left and left the pane's + right half empty. `.bilingual-cols` now overrides that cap — it fills the reader + width (each column gets its own measure) and, past two measures on an ultra-wide + pane, caps and centers. _e2e guard: `bilingual.spec.ts` "dual-column view fills + the reader pane…"._ +- **DESIGN.md typography frontmatter matched to the implementation.** The + machine-readable `typography:` block still named the pre-v0.8.0 families (Source + Serif 4 / Inter Tight / JetBrains Mono) and carried swapped weights (display 500, + label 600) — contradicting the shipped IBM Plex superfamily and the §3 prose + table (display 600, label 500). Frontmatter families, weights, and the stray + §3 "JetBrains Mono" mention now read IBM Plex. + +### Changed + +- **MB-Web radii and colors consolidated onto the shared tokens.** `src/mb/mb.css` + kept a parallel light + dark palette whose values already duplicated the + workbench tokens. Differently-named locals (`--ac` / `--acb` / `--border` / …) + now alias their `styles.css` equivalent and same-named ones (`--bg` / `--surface` + / `--text` / …) inherit the global values, so the parallel dark block is gone; + the off-scale radii snapped to the `4 / 6 / 7 / 8 / 999` scale (cards 8px; the + badge / chip / filter / toast / sync pills fully round); the note highlighter + yellows became named `--mb-mark` / `--mb-mark-staged` tokens. Only the few + genuinely MB-specific literals with no shared equivalent (answer-blue, on-accent + foreground, AA-darkened faint, hover wash) stay local. Zero-visual-change for + colors; verified in the browser across library, reader, and notes in light + dark. + ## [0.8.2] - 2026-06-27 ### Fixed diff --git a/DESIGN.md b/DESIGN.md index f76555d..0e41a7d 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -28,38 +28,38 @@ colors: accent-border-hover: "color-mix(in srgb, {colors.deep-petrol} 35%, {colors.line})" typography: # Six roles across three voices. Each maps to --type--{size,lh,ls} in styles.css. - display: # Source Serif 4 — reader article H1/H2 (the one editorial voice) - fontFamily: "Source Serif 4, ui-serif, Georgia, Source Han Serif CN, serif" + display: # IBM Plex Serif — reader article H1/H2 (the one editorial voice) + fontFamily: "IBM Plex Serif, ui-serif, Georgia, Songti SC, Source Han Serif CN, serif" fontSize: "32px" - fontWeight: 500 + fontWeight: 600 lineHeight: 1.18 letterSpacing: "-0.01em" - title-page: # Inter Tight — page-header H1 (NOTE: sans, not serif) - fontFamily: "Inter Tight, Inter, ui-sans-serif, system-ui, sans-serif" + title-page: # IBM Plex Sans — page-header H1 (NOTE: sans, not serif) + fontFamily: "IBM Plex Sans, ui-sans-serif, system-ui, PingFang SC, Microsoft YaHei, sans-serif" fontSize: "30px" fontWeight: 600 lineHeight: 1.1 letterSpacing: "-0.01em" - title: # Inter Tight — panel / card / session headings - fontFamily: "Inter Tight, Inter, ui-sans-serif, system-ui, sans-serif" + title: # IBM Plex Sans — panel / card / session headings + fontFamily: "IBM Plex Sans, ui-sans-serif, system-ui, PingFang SC, Microsoft YaHei, sans-serif" fontSize: "17px" fontWeight: 600 lineHeight: 1.3 letterSpacing: "-0.005em" - body: # Inter Tight — interface text, descriptions, agent replies - fontFamily: "Inter Tight, Inter, ui-sans-serif, system-ui, sans-serif" + body: # IBM Plex Sans — interface text, descriptions, agent replies + fontFamily: "IBM Plex Sans, ui-sans-serif, system-ui, PingFang SC, Microsoft YaHei, sans-serif" fontSize: "15px" fontWeight: 400 lineHeight: 1.55 - body-sm: # Inter Tight — dense metadata / secondary UI text - fontFamily: "Inter Tight, Inter, ui-sans-serif, system-ui, sans-serif" + body-sm: # IBM Plex Sans — dense metadata / secondary UI text + fontFamily: "IBM Plex Sans, ui-sans-serif, system-ui, PingFang SC, Microsoft YaHei, sans-serif" fontSize: "13px" fontWeight: 400 lineHeight: 1.45 - label: # JetBrains Mono UPPERCASE — field labels, table heads, IDs - fontFamily: "JetBrains Mono, ui-monospace, SFMono-Regular, Consolas, monospace" + label: # IBM Plex Mono UPPERCASE — field labels, table heads, IDs + fontFamily: "IBM Plex Mono, ui-monospace, SFMono-Regular, Consolas, Sarasa Mono SC, monospace" fontSize: "11px" - fontWeight: 600 + fontWeight: 500 lineHeight: 1.2 letterSpacing: "0.04em" rounded: @@ -361,8 +361,15 @@ half-step scale (11.5 / 12.5 / 13.5 / 14.5 …) collapsed onto label / body-sm / `main.tsx`) makes the tokens available inside the MB-Web tree. The one deliberate literal is `.mb-r-title` (22px) — the paper-reader title, the reader-h2 editorial step, kept compact rather than the workbench reader's 32. MB-Web's **radii and -colors** are still off the documented scale (a separate `detect.mjs` finding) and -remain a tracked follow-up; only type was migrated here. +colors now ride the shared tokens too**: every differently-named local token +(`--ac` / `--acb` / `--border` / …) aliases its `styles.css` equivalent and the +same-named ones (`--bg` / `--surface` / `--text` / …) inherit the global light + +dark values, so the parallel dark block is gone; its former off-scale radii +snapped to the `4 / 6 / 7 / 8 / 999` scale (cards 8px, the badge / chip / filter / +toast / sync pills fully round). The few genuinely MB-specific literals with no +shared equivalent stay named and local: the answer-blue pair (`--ans-*`), the +on-accent foreground (`--acfg`), the AA-darkened `--faint`, the hover wash +(`--hover-bp`), and the note highlighter (`--mb-mark` / `--mb-mark-staged`). **The agent surface rides the roles.** Assistant and user messages use the body role (15px) for prose, the editorial serif for the three markdown heading levels @@ -601,7 +608,7 @@ layer. Rules below are tagged **[EN]**, **[ZH]**, or **[both]**. is structural, not floated. - **Do** snap spacing to the `--space-*` grid and radii to the `4 / 6 / 7 / 8 / 999` scale; controls at 7px, cards at 8px, pills fully round. -- **Do** confine uppercase + letter-spacing to the JetBrains Mono `label` role, +- **Do** confine uppercase + letter-spacing to the IBM Plex Mono `label` role, and keep ≤2 font weights per view. - **Do** put a focus ring on every interactive element (`--focus-ring` for text fields, `--focus-outline` elsewhere — already single-sourced), and drive diff --git a/docs/ui-checklist.md b/docs/ui-checklist.md index 755fdfd..dd5282b 100644 --- a/docs/ui-checklist.md +++ b/docs/ui-checklist.md @@ -98,8 +98,15 @@ no e2e are the ones the manual pass exists for. ## Bilingual reader (`#base` EN→中) > On an English Base page with the translator enabled, toggle **AI 翻译** and -> watch the dual column fill. These guard the two regressions found in review. - +> watch the dual column fill. These guard the layout and translation regressions +> found in review. + +- [ ] **Dual columns fill the reader pane.** The source / translation columns + span the full reader width with the centered hairline between them — not capped + at the single-column 72ch measure that left-hugged the pane and emptied the + right half (the 0.8.3 layout fix). On an ultra-wide pane the pair caps at two + reading measures and centers. _e2e: `bilingual.spec.ts` "dual-column view fills + the reader pane…"._ - [ ] **Figures render once, centered.** A standalone image / figure (`![[…]]` or `![](…)` on its own line) appears **once**, centered across both columns — never duplicated in the left and right column. (Captions, being prose, are a diff --git a/package.json b/package.json index 12cbf1c..f22f1c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dikw-web", - "version": "0.8.2", + "version": "0.8.3", "private": true, "type": "module", "engines": { diff --git a/src/mb/mb.css b/src/mb/mb.css index 101a31f..6c30720 100644 --- a/src/mb/mb.css +++ b/src/mb/mb.css @@ -5,34 +5,35 @@ * panel splitters. */ .mb-app { - /* Aligned to the workbench warm-stone + petrol identity (One Indicator Rule): - MB-Web shares the same neutrals and accent as src/styles.css, not its own - green palette. --ans-* (answer blue) stays a distinct functional semantic. */ - --bg: #f5f3ee; - --surface: #ffffff; - --surface2: #f0eee8; - --border: #e2dfd6; - --border2: #cdc9bd; - --text: #1a1d1c; - --muted: #6c6f6a; - /* Darkened from #95968f (~2.7:1) to clear AA (~4.5:1) for the meta text that - uses --faint; still the faintest neutral, just readable. */ + /* MB-Web rides the workbench design tokens (src/styles.css) directly — its + neutrals, accent, radius and font voices ARE the shared, theme-aware tokens, + not a parallel palette. Same-named tokens (--bg / --surface / --text / + --muted / --danger / --radius) inherit the global :root and dark values; + differently-named ones alias the shared token so they track the theme with + no parallel dark block to maintain. Only the genuinely MB-specific semantics + stay literal — the answer-blue pair, the on-accent foreground, the + AA-darkened faint, the hover wash, and the note highlighter — none of which + has a shared equivalent. */ + --surface2: var(--surface-2); + --border: var(--line); + --border2: var(--line-strong); + --ac: var(--accent); + --acb: var(--accent-soft); + --act: var(--accent-strong); + --anno-bg: var(--accent-soft); + /* Darkened from #95968f (~2.7:1) to clear AA (~4.5:1) for --faint meta text. */ --faint: #767771; - --ac: #0d5e57; - --acb: #e2eeec; - --act: #094540; --acfg: #ffffff; --hover-bp: #faf9f5; - --anno-bg: #e2eeec; --ans-bg: #e6f1fb; --ans-tx: #0c447c; - --danger: #a8362c; - --radius: 8px; - --radius-lg: 12px; - --sans: - "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", - "Microsoft YaHei", sans-serif; - --serif: "IBM Plex Serif", Georgia, "Songti SC", serif; + /* Note highlighter: live selection (yellow) + staged range (warmer amber). + Theme-agnostic — always dark text on a bright mark, on both light and dark + reader/chat surfaces. */ + --mb-mark: #fef08a; + --mb-mark-staged: #fcd34d; + --sans: var(--font-ui); + --serif: var(--font-serif); position: fixed; inset: 0; @@ -47,23 +48,13 @@ overflow: hidden; } html[data-theme="dark"] .mb-app { - --bg: #14140f; - --surface: #1b1b15; - --surface2: #25241d; - --border: #2f2d24; - --border2: #4e4a3c; - --text: #f0eee7; - --muted: #b0aca0; + /* Only the MB-specific literals need a dark value; every shared-token alias + and same-named token tracks the global dark block automatically. */ --faint: #87826f; - --ac: #4eb8a6; - --acb: #142a26; - --act: #92e3d4; --acfg: #06231f; --hover-bp: #1f1e17; - --anno-bg: #142a26; --ans-bg: #12304e; --ans-tx: #b5d4f4; - --danger: #ef7f75; } .mb-app *, .mb-app *::before, @@ -137,7 +128,7 @@ html[data-theme="dark"] .mb-app { background: var(--acb); color: var(--act); font-size: var(--type-label-size); - border-radius: 20px; + border-radius: 999px; padding: 0 6px; min-width: 16px; text-align: center; @@ -572,7 +563,7 @@ body.mb-col-resizing * { font-size: var(--type-label-size); background: var(--acb); color: var(--act); - border-radius: 12px; + border-radius: 999px; padding: 1px 8px; } .mb-reader-empty { @@ -827,7 +818,7 @@ body.mb-col-resizing * { .mb-f { font-size: var(--type-body-sm-size); padding: 5px 13px; - border-radius: 20px; + border-radius: 999px; border: 0.5px solid var(--border2); background: transparent; color: var(--muted); @@ -842,7 +833,7 @@ body.mb-col-resizing * { .mb-note { background: var(--surface); border: 0.5px solid var(--border); - border-radius: var(--radius-lg); + border-radius: var(--radius); padding: 16px 18px; margin-bottom: 12px; } @@ -861,7 +852,7 @@ body.mb-col-resizing * { color: var(--text); background: var(--surface); border: 0.5px solid var(--border); - border-radius: var(--radius-lg); + border-radius: var(--radius); padding: 14px 16px; cursor: pointer; display: flex; @@ -1101,7 +1092,7 @@ body.mb-col-resizing * { background: var(--ac); color: var(--acfg); border: 0; - border-radius: 22px; + border-radius: 999px; font-size: var(--type-body-sm-size); padding: 7px 14px; cursor: pointer; @@ -1117,7 +1108,7 @@ body.mb-col-resizing * { width: 300px; background: var(--surface); border: 0.5px solid var(--border2); - border-radius: var(--radius-lg); + border-radius: var(--radius); padding: 15px; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2); } @@ -1229,7 +1220,7 @@ body.mb-col-resizing * { width: min(380px, 100%); background: var(--surface); border: 0.5px solid var(--border2); - border-radius: var(--radius-lg); + border-radius: var(--radius); box-shadow: 0 12px 40px rgba(0, 0, 0, 0.22); padding: 18px 18px 16px; } @@ -1276,7 +1267,7 @@ body.mb-col-resizing * { color: var(--acfg); font-size: var(--type-body-sm-size); padding: 9px 16px; - border-radius: 24px; + border-radius: 999px; display: flex; align-items: center; gap: 7px; @@ -1292,7 +1283,7 @@ body.mb-col-resizing * { border: 0.5px solid var(--border); background: var(--surface2); color: var(--muted); - border-radius: 20px; + border-radius: 999px; padding: 2px 9px; cursor: default; } @@ -1347,12 +1338,12 @@ body.mb-col-resizing * { paragraph-hover highlight and the user sees exactly what will be saved. */ .mb-reader ::selection, .mb-msgs ::selection { - background: #fef08a; + background: var(--mb-mark); color: #1a1a1a; } .mb-reader ::-moz-selection, .mb-msgs ::-moz-selection { - background: #fef08a; + background: var(--mb-mark); color: #1a1a1a; } /* "Staged" highlight: once text is selected we register its range as a CSS @@ -1360,7 +1351,7 @@ body.mb-col-resizing * { marked — in a slightly warmer amber than the live selection — while the user moves to the chip/popover, even after the native selection is gone. */ ::highlight(mb-staged) { - background: #fcd34d; + background: var(--mb-mark-staged); color: #1a1a1a; } diff --git a/src/styles.css b/src/styles.css index 3d75612..03930ad 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1895,9 +1895,16 @@ html[data-theme="dark"] .wiki-reader-tabs .wrt-switch__thumb { } } -/* Paragraph-aligned dual columns. */ +/* Paragraph-aligned dual columns. The dual view is two reading measures side by + side, so it must override the single-column 72ch cap it inherits from + `.markdown-body` — otherwise both columns crush into one measure on the left + and leave the pane's right half empty. Fill the reader pane on normal widths; + past two measures, cap and center so columns never run past a comfortable + line length on an ultra-wide monitor. */ .bilingual-cols { position: relative; + max-width: calc(72ch * 2 + 40px); + margin-inline: auto; } .bilingual-cols::before { content: ""; diff --git a/tests/e2e/bilingual.spec.ts b/tests/e2e/bilingual.spec.ts index 723985e..8552852 100644 --- a/tests/e2e/bilingual.spec.ts +++ b/tests/e2e/bilingual.spec.ts @@ -109,6 +109,28 @@ test("translates the preview card when a translated-column wikilink is clicked", await expect(page.locator(".wiki-preview-card__ai")).toHaveCount(0); }); +test("dual-column view fills the reader pane, not the single-column measure", async ({ page }) => { + // The dual view must use the full reader width — each column gets its own + // reading measure — instead of inheriting `.markdown-body`'s single-column + // 72ch cap, which left-hugged the pane and left the right half empty + // (the pre-0.8.3 layout bug). Pin a wide pane (under the 2-measure cap, so it + // fills rather than centering) and assert the columns span nearly all of it. + await page.setViewportSize({ width: 1400, height: 900 }); + await page.goto("/#base"); + const reader = page.getByRole("main", { name: "Wiki reader" }); + await expect(reader.getByText(/Layered DIKW notes/)).toBeVisible(); + + await page.getByRole("switch", { name: TOGGLE }).click(); + const cols = page.locator(".bilingual-cols"); + await expect(cols).toBeVisible(); + + const ratio = await cols.evaluate((el) => { + const parent = el.parentElement as HTMLElement; + return el.getBoundingClientRect().width / parent.getBoundingClientRect().width; + }); + expect(ratio).toBeGreaterThan(0.95); +}); + test("keeps the dual-column view readable in dark mode", async ({ page }) => { await page.addInitScript(() => { localStorage.setItem("dikw-web.theme", "dark"); From 95776e0cdb6e72c62df933af8e6dfade583f8736 Mon Sep 17 00:00:00 2001 From: holo Date: Sat, 27 Jun 2026 21:44:12 +0800 Subject: [PATCH 2/2] fix(ui): restore single-column 72ch cap for stacked bilingual columns (Codex P2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Below 1100px the bilingual pairs collapse to one stacked column, but the new `.bilingual-cols` two-measure cap left a stacked paragraph free to run the full panel width — wider than the mono reader's 72ch, regressing the readability the cap protects. Restore `max-width: 72ch` inside the existing stack media query. Guarded by a new bilingual.spec.ts test asserting the resolved max-width reverts to the single measure at a narrow viewport. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 7 +++++-- src/styles.css | 6 ++++++ tests/e2e/bilingual.spec.ts | 21 +++++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba390bd..ff55f4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,8 +18,11 @@ file format introduced in `[0.0.1.0]` was dropped. translation columns crushed into one measure on the left and left the pane's right half empty. `.bilingual-cols` now overrides that cap — it fills the reader width (each column gets its own measure) and, past two measures on an ultra-wide - pane, caps and centers. _e2e guard: `bilingual.spec.ts` "dual-column view fills - the reader pane…"._ + pane, caps and centers. Below 1100px, where the pairs collapse to one stacked + column, the cap reverts to the single-column 72ch so a stacked paragraph never + runs past a comfortable line length. _e2e guards: `bilingual.spec.ts` + "dual-column view fills the reader pane…" + "…restores the single-column measure + when columns stack…"._ - **DESIGN.md typography frontmatter matched to the implementation.** The machine-readable `typography:` block still named the pre-v0.8.0 families (Source Serif 4 / Inter Tight / JetBrains Mono) and carried swapped weights (display 500, diff --git a/src/styles.css b/src/styles.css index 03930ad..560c253 100644 --- a/src/styles.css +++ b/src/styles.css @@ -2046,6 +2046,12 @@ html[data-theme="dark"] .wiki-reader-tabs .wrt-switch__thumb { /* Narrow screens: collapse the two columns into a stacked single column. */ @media (max-width: 1100px) { + /* Stacked = a single column again, so restore the single-column reading + measure — the two-measure cap above is only for the side-by-side layout and + would let a stacked paragraph run far past a comfortable line length. */ + .bilingual-cols { + max-width: 72ch; + } .bilingual-cols::before { display: none; } diff --git a/tests/e2e/bilingual.spec.ts b/tests/e2e/bilingual.spec.ts index 8552852..4863b4e 100644 --- a/tests/e2e/bilingual.spec.ts +++ b/tests/e2e/bilingual.spec.ts @@ -131,6 +131,27 @@ test("dual-column view fills the reader pane, not the single-column measure", as expect(ratio).toBeGreaterThan(0.95); }); +test("dual view restores the single-column measure when columns stack (narrow)", async ({ + page, +}) => { + // Below 1100px the pairs collapse to one stacked column, so the two-measure + // cap must revert to the single-column 72ch — otherwise a stacked paragraph + // runs the full panel width, wider than the mono reader (the Codex P2 catch). + // Assert the resolved max-width itself (independent of panel width): ~72ch + // (≈648px) after the fix, vs the 2-measure ~1336px without it. + await page.setViewportSize({ width: 1000, height: 900 }); + await page.goto("/#base"); + const reader = page.getByRole("main", { name: "Wiki reader" }); + await expect(reader.getByText(/Layered DIKW notes/)).toBeVisible(); + + await page.getByRole("switch", { name: TOGGLE }).click(); + const cols = page.locator(".bilingual-cols"); + await expect(cols).toBeVisible(); + + const maxWidthPx = await cols.evaluate((el) => parseFloat(getComputedStyle(el).maxWidth)); + expect(maxWidthPx).toBeLessThan(800); +}); + test("keeps the dual-column view readable in dark mode", async ({ page }) => { await page.addInitScript(() => { localStorage.setItem("dikw-web.theme", "dark");