diff --git a/CHANGELOG.md b/CHANGELOG.md index 91a9a94..b0e2e95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,13 @@ version with its date and start a fresh empty `[Unreleased]` above it. ## [Unreleased] +### Changed + +- The ribbon icon, view header logo, and model selector rows now use the new + Qoder mark introduced with the new Qoder app, replacing the old QoderIDE + glyph. Like the new app icon, the monochrome mark follows the theme's ink + tone — dark on light themes, light on dark themes — instead of brand green. + ## [1.0.15] - 2026-09-21 ### Added diff --git a/src/assets/qoder-icon.svg b/src/assets/qoder-icon.svg index 829ec79..942cb84 100644 --- a/src/assets/qoder-icon.svg +++ b/src/assets/qoder-icon.svg @@ -1 +1,4 @@ - \ No newline at end of file + + + + diff --git a/src/shared/icons.ts b/src/shared/icons.ts index 998a7ca..5a6060b 100644 --- a/src/shared/icons.ts +++ b/src/shared/icons.ts @@ -61,7 +61,7 @@ export function appendCheckIcon(container: HTMLElement): void { export const QODER_ICON: IconSvg = { kind: 'raw', - viewBox: '0 0 180 180', + viewBox: '0 0 100 100', svg: qoderIconSvg, }; @@ -96,29 +96,22 @@ export const CHECK_ICON: IconSvg = { export const QODERIAN_ICON_ID = 'qoderian-logo'; -// Obsidian draws registered icons inside a `0 0 100 100` viewport, so the mark -// of the 180x180 app artwork (bounding box 32.4..147.6 x 30.2..148.4) is -// re-centered and scaled down to leave the padding ribbon icons expect. -const QODER_MARK_TRANSFORM = 'translate(-14.8 -14.29) scale(0.72)'; +// Obsidian draws registered icons inside a `0 0 100 100` viewport. The mark +// fills 15.2..84.8 x 15.0..86.1 of the 100x100 artwork, so it is scaled up +// and re-centered to leave the padding ribbon icons expect. +const QODER_MARK_TRANSFORM = 'translate(-9.61 -10.31) scale(1.19)'; // `addIcon` takes markup as a string and the artwork is a build-time asset, so // the mark paths are lifted textually instead of through a DOM round-trip. -const QODER_MARK_PATH_PATTERN = /]*\sd="([^"]+)"/g; -// Ribbon and tab icons follow a single theme color, so the artwork's white -// front ring and green extruded body both become `currentColor`. Drawing them -// at the same strength collapses the mark into one blob, so the body behind -// the ring is dimmed to keep the depth the two-tone original conveys. -const QODER_MARK_BODY_OPACITY = '0.32'; +const QODER_MARK_PATH_PATTERN = /]*>/g; export function createQoderianIconContent(sourceSvg: string = qoderIconSvg): string { - // The artwork draws the extruded body first and the front ring second; that - // order is preserved so the ring stays on top of the dimmed body. + // The artwork fills the outline body first and strokes the contour second; + // that order is preserved so the stroke stays on top of the fill. const paths = Array.from(sourceSvg.matchAll(QODER_MARK_PATH_PATTERN)) - .map(([, pathData], index) => (index === 0 - ? `` - : ``)) + .map(([path]) => path) .join(''); - return `${paths}`; + return `${paths}`; } export interface CreateIconSvgOptions { @@ -158,12 +151,6 @@ export function createIconSvg( if (sourceSvg) { for (const child of Array.from(sourceSvg.children)) { const clone = ownerDocument.importNode(child, true) as SVGElement; - // The source artwork is an app-icon variant with a dark rounded-square - // backdrop. In a compact model selector that backdrop turns into the - // grey block seen on every option, so keep only the Qoder mark. - if (clone.tagName.toLowerCase() === 'g') { - clone.querySelector(':scope > rect')?.remove(); - } svg.appendChild(clone); } } diff --git a/src/style/base/variables.css b/src/style/base/variables.css index 147c81e..a5bd694 100644 --- a/src/style/base/variables.css +++ b/src/style/base/variables.css @@ -11,10 +11,3 @@ body.theme-light .qoderian-container { --qoderian-brand: #35CC5F; --qoderian-brand-rgb: 53, 204, 95; } - -/* The Qoder glyph has a white half for dark surfaces. On light Obsidian - themes that half disappears into the canvas, so use the same ink tone as - the plugin title while retaining the native green half. */ -body.theme-light .qoderian-icon path[fill="#FFFFFF"] { - fill: #202124; -} diff --git a/src/style/components/header.css b/src/style/components/header.css index 5b65bc0..614fde4 100644 --- a/src/style/components/header.css +++ b/src/style/components/header.css @@ -23,7 +23,9 @@ .qoderian-logo { display: flex; align-items: center; - color: var(--qoderian-brand); + /* The Qoder mark is monochrome, so it follows the theme's ink tone like the + app icon does: dark on light themes, light on dark themes. */ + color: var(--text-normal); } /* Update badge: an icon button in the header actions, shown only while a newer diff --git a/src/style/toolbar/model-selector.css b/src/style/toolbar/model-selector.css index 432dfe5..7c07fae 100644 --- a/src/style/toolbar/model-selector.css +++ b/src/style/toolbar/model-selector.css @@ -214,10 +214,11 @@ opacity: 0.7; } -/* Qoder's dual-green mark stays branded on the active option, while inactive - options inherit the muted selector text just like PR #973. */ -.qoderian-model-option:not(.selected) .qoderian-model-icon * { - fill: currentColor; +/* The mark is monochrome and follows the theme's ink tone, so the selected + row's green label must not bleed into the icon. */ +.qoderian-model-option.selected .qoderian-model-icon { + color: var(--text-normal); + opacity: 1; } .qoderian-model-option { diff --git a/tests/unit/shared/icons.test.ts b/tests/unit/shared/icons.test.ts index efa4c43..e75f6ce 100644 --- a/tests/unit/shared/icons.test.ts +++ b/tests/unit/shared/icons.test.ts @@ -1,31 +1,28 @@ import { createQoderianIconContent, QODERIAN_ICON_ID } from '@/shared/icons'; -const SOURCE_SVG = `` - + `` - + `` - + ``; +const SOURCE_SVG = `` + + `` + + ``; describe('createQoderianIconContent', () => { - it('keeps only the mark paths, dropping the artwork backdrop and clip ids', () => { + it('lifts the mark paths verbatim, keeping their paint attributes', () => { const content = createQoderianIconContent(SOURCE_SVG); - expect(content).toContain('d="M10 20 L30 40 Z"'); - expect(content).toContain(''); + expect(content).toContain(''); + expect(content).toContain(''); expect(content).not.toContain('rect'); expect(content).not.toContain('clip-path'); }); - it('renders the mark in one theme color, dimming the body behind the front ring', () => { + it('renders the mark in one theme color inside the ribbon viewport', () => { const content = createQoderianIconContent(SOURCE_SVG); expect(content).toContain('fill="currentColor"'); - expect(content).toContain(''); - expect(content).toMatch(/transform="translate\([-\d. ]+\) scale\(0?\.\d+\)"/); - expect(content).not.toContain('#2ADB5C'); - expect(content).not.toContain('#FFFFFF'); + expect(content).toContain('stroke="currentColor"'); + expect(content).toMatch(/^/); }); - it('draws the front ring after the body so it stays on top', () => { + it('draws the filled body before the outlined contour', () => { const content = createQoderianIconContent(SOURCE_SVG); expect(content.indexOf('M10 20')).toBeLessThan(content.indexOf('M50 60'));