diff --git a/src/ui/railPanelChrome.test.mjs b/src/ui/railPanelChrome.test.mjs index bfcd7845e..3b79bbcc5 100644 --- a/src/ui/railPanelChrome.test.mjs +++ b/src/ui/railPanelChrome.test.mjs @@ -9,7 +9,10 @@ const clean = (selector) => .replace(/\s+/g, ' ') .trim(); const rules = [...css.matchAll(/([^{}]+)\{([^{}]*)\}/g)].map( - ([, selector, body]) => ({ selector: clean(selector), body }), + ([, selector, body]) => ({ + selector: clean(selector), + body: body.replace(/\/\*[\s\S]*?\*\//g, ''), + }), ); const railRules = rules.filter(({ selector }) => selector.includes('#left-panel-stack, #right-context-rail'), @@ -94,6 +97,47 @@ test('collapsed launchers on both rails keep titles inside the card', () => { ); assert.ok(translated, 'translated collapsed titles must fit on both rails'); assert.match(translated.body, /font-size:\s*8px/); + // Chinese and Japanese titles fit at full size; 8px would put them under + // the 9px CJK floor in cjk.css. + assert.match(translated.selector, /:not\(:lang\(zh\)\):not\(:lang\(ja\)\)/); + // Only the rail's own launchers: Radio, nested in Context, keeps its type. + assert.match( + translated.selector, + /#right-context-rail\) > \.panel-collapsible\.collapsed > \[class\$='-panel-inner'\] > \.panel-header > \.panel-title$/, + ); +}); + +test('a collapsed launcher drops its divider so English titles fit whole', () => { + const launcher = (tail) => + railRules.filter(({ selector }) => + selector + .split(/,(?![^(]*\))/) + .some((part) => + part + .trim() + .endsWith( + `.collapsed > [class$='-panel-inner'] > .panel-header${tail}`, + ), + ), + ); + const [header] = launcher(''); + const [divider] = launcher(' > .panel-divider'); + const [toggle] = launcher(' > .panel-collapse-btn'); + assert.match(header?.body || '', /gap:\s*8px/, 'the launcher header gap'); + assert.match(divider?.body || '', /display:\s*none/, 'the launcher divider'); + assert.match( + toggle?.body || '', + /margin-left:\s*auto/, + 'the launcher toggle', + ); + const button = rules.find( + ({ selector }) => selector === '.panel-collapse-btn', + ); + assert.match( + button?.body || '', + /flex-shrink:\s*0/, + 'a long title must not squeeze the toggle', + ); }); test('the default theme geometry change leaves cyber gutters alone', () => { @@ -122,13 +166,21 @@ test('rail titles and dock labels share one set of label tokens', () => { const rail = railRules.find(({ body }) => /--dock-label-font-size/.test(body), ); - for (const body of [dock?.body, rail?.body]) { - assert.match(body || '', /font-size:\s*var\(--dock-label-font-size\)/); - assert.match( - body || '', - /letter-spacing:\s*var\(--dock-label-letter-spacing\)/, - ); - } + assert.match(dock?.body || '', /font-size:\s*var\(--dock-label-font-size\)/); + assert.match( + dock?.body || '', + /letter-spacing:\s*var\(--dock-label-letter-spacing\)/, + ); + // The dock's rem type, capped at its 16px-root size: a collapsed launcher + // is a fixed 176px, so a larger browser font must not cut titles short. + assert.match( + rail?.body || '', + /font-size:\s*min\(var\(--dock-label-font-size\),\s*9\.28px\)/, + ); + assert.match( + rail?.body || '', + /letter-spacing:\s*min\(var\(--dock-label-letter-spacing\),\s*2\.08px\)/, + ); }); test('default theme overrides never reach the cyber theme', () => { @@ -165,8 +217,13 @@ test('default theme overrides never reach the cyber theme', () => { }); test('launcher compaction and header colour stay on the rail panels themselves', () => { - const compact = railRules.filter(({ selector }) => - /\.collapsed > \[class\$='-panel-inner'\] > \.panel-header/.test(selector), + // The divider, gap and toggle rules; the translated type rule is checked + // above. + const compact = railRules.filter( + ({ selector }) => + /\.collapsed > \[class\$='-panel-inner'\] > \.panel-header/.test( + selector, + ) && !selector.startsWith('html:'), ); assert.equal(compact.length, 3); for (const { selector } of compact) { diff --git a/src/ui/styles/controls.css b/src/ui/styles/controls.css index 936f095df..b50e9c2a1 100644 --- a/src/ui/styles/controls.css +++ b/src/ui/styles/controls.css @@ -82,13 +82,16 @@ } /* Side panel titles read like the command dock's labels: one type for every - panel, collapsed or open, and bright enough to read over the globe. */ + panel, collapsed or open, and bright enough to read over the globe. The + dock's type is in rem, but a collapsed launcher is a fixed 176px, so the + rail caps it at its 16px-root size: a larger browser font must not cut + Recent Imagery short. */ :is(#left-panel-stack, #right-context-rail) :is(.panel-header > .panel-title, .pp-header-label) { font-family: var(--font-mono); - font-size: var(--dock-label-font-size); + font-size: min(var(--dock-label-font-size), 9.28px); font-weight: var(--dock-label-font-weight); - letter-spacing: var(--dock-label-letter-spacing); + letter-spacing: min(var(--dock-label-letter-spacing), 2.08px); text-transform: uppercase; } @@ -165,11 +168,15 @@ /* Translated titles run longer (Aktuelle Bilddaten), so a collapsed rail launcher sets them in compact type rather than pushing the disclosure - button out of the card or cutting the word short. */ -html:not([lang='en']) + button out of the card or cutting the word short. Chinese and Japanese + titles fit at full size and stay at or above their 9px floor (cjk.css); + Radio, nested in Context, is not a launcher. */ +html:not([lang='en']):not(:lang(zh)):not(:lang(ja)) :is(#left-panel-stack, #right-context-rail) - .panel-collapsible.collapsed - .panel-title { + > .panel-collapsible.collapsed + > [class$='-panel-inner'] + > .panel-header + > .panel-title { font-size: 8px; letter-spacing: 1px; }