Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ native `<input type=color>`, no Acrylic or Accent opacity slider. Spec and plan:
browser paints no ground; its address row, list, preview slot and status paint `--p-bg`, the places
panel (and `.browse-viewer-places`) `--p-side`, the Settings rail's and project tree's colour, one
coat each. Every catalogue theme's panel differs from its ground. The `sidebarGround` e2e holds both.
**THE ADDRESS ROW WEARS IT TOO** (#306; owner, 2026-10-07, of a mockup with the row in the panel
colour: "yes make this the same color as the sidebar"): back/forward/up/refresh, the address field,
preview and search, across the window (`.folder-browser > .browse-toolbar`, and
`.browse-viewer-toolbar` over an opened file), `--p-side`, its hairline kept, so only the list,
preview slot and status are `--p-bg`. The address field's fill and edge step off the ROW
(`--p-side-flat`), not the page: off the page they sat at 1.01:1 on the row on every dark style
(MEASURED). Near-black is still judged by the page (`nearBlackField(bg, text, row)`).

## Scope

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "prism",
"version": "0.93.0",
"version": "0.93.1",
"description": "A fast, universal media viewer for Windows.",
"author": "Max",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/components/browse/BrowseToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export function BrowseToolbar(props: Props): JSX.Element {
name={crumb.name}
color="var(--p-tree-zip)"
size={14}
bg="var(--p-control)"
bg="var(--p-field)"
/>
)}
{crumb.name}
Expand Down
23 changes: 15 additions & 8 deletions src/renderer/src/components/browse/browse.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,22 @@
see-through coats are an opaque slab on glass): the address row, the list,
the preview slot and the status line --p-bg, the places panel --p-side.
The four cover the grid between them, with no gap. A peeking panel keeps
its flat --p-side-flat (below), being laid over the list. */
its flat --p-side-flat (below), being laid over the list.
THE ADDRESS ROW WEARS IT TOO (#306; owner, 2026-10-07, of a mockup with
the row in the panel colour: "yes make this the same color as the
sidebar"): back, forward, up, refresh, the address field, the preview
toggle and search, across the whole window, so the sidebar colour runs
along the top and down the left and the list is the one --p-bg area.
Its hairline stays. Still one coat: the row is its own grid cell. */
.folder-browser > .browse-toolbar,
.folder-browser > .browse-places {
background-color: var(--p-side);
}
.folder-browser > .browse-list-area,
.folder-browser > .browse-preview-slot,
.folder-browser > .browse-status {
background-color: var(--p-bg);
}
.folder-browser > .browse-places {
background-color: var(--p-side);
}
.folder-browser[data-preview] {
grid-template-columns: var(--browse-places-width) minmax(0, 1fr) var(
--browse-preview-width,
Expand Down Expand Up @@ -176,9 +182,9 @@
min-height: 48px;
flex-shrink: 0;
color: var(--p-text);
/* No ground of its own (#294): it sits on the folder browser's ground, or
on .browse-viewer-toolbar's over a file, and a second see-through coat
of --p-bg made the address bar an opaque band on a glass style. */
/* No ground in this rule (#294): the folder browser paints its cell above
(#306: --p-side), .browse-viewer-toolbar paints it over a file, and a
second see-through coat made the address bar an opaque band on glass. */
font-family: 'Segoe UI', sans-serif;
font-size: 15px;
}
Expand Down Expand Up @@ -225,7 +231,8 @@
"make the url box more visible"). The breadcrumb sat on the toolbar with no
box, so only the search looked like somewhere to type. Both wear this: one
height, radius, fill and edge, from the --p-field tokens (lib/fieldColours:
on a near-black ground a darker fill and an edge that carries the shape). */
on a near-black ground a darker fill and an edge that carries the shape).
Both are stepped off the ROW's ground, the sidebar colour (#306). */
.browse-field {
height: 36px;
border: 1px solid var(--p-field-edge);
Expand Down
4 changes: 3 additions & 1 deletion src/renderer/src/components/browse/workspace.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
.browse-surface-host {
z-index: 1;
}
/* Over an opened file the address row is the same row, so the same sidebar
colour (#306), one coat: the .browse-toolbar inside paints nothing here. */
.browse-viewer-toolbar {
flex-shrink: 0;
background: var(--p-bg);
background: var(--p-side);
}
.browse-viewer-toolbar > .browse-toolbar {
height: 48px;
Expand Down
33 changes: 26 additions & 7 deletions src/renderer/src/lib/fieldColours.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { contrastOf, hintOn, isNearBlack, nearBlackField } from './fieldColours'
import { contrastOf, controlFieldOn, hintOn, isNearBlack, nearBlackField } from './fieldColours'
import { STYLES, variablesFor } from './theme'

const byId = (id: string) => STYLES.find((s) => s.id === id)!
Expand Down Expand Up @@ -38,17 +38,36 @@ describe('the toolbar fields (#267)', () => {
expect(contrastOf('#707174', '#e7e7e8')).toBeLessThan(4.5)
})

it('Void wears the dark field; Paper keeps the fill it had', () => {
it('Void wears the dark field; Paper the control step; both off the row (#306)', () => {
const v = variablesFor(byId('new-void'))
expect(v['--p-field']).toBe('#060606')
expect(contrastOf(v['--p-field-edge'], '#000000')).toBeGreaterThanOrEqual(1.6)
expect(contrastOf(v['--p-field-edge'], '#000000')).toBeLessThan(1.8)
const vRow = v['--p-side-flat']
expect(vRow).toBe('#070707')
expect(v['--p-field']).toBe(nearBlackField('#000000', '#e8eaf0', vRow)!.fill)
expect(contrastOf(v['--p-field-edge'], vRow)).toBeGreaterThanOrEqual(1.6)
expect(contrastOf(v['--p-field-edge'], vRow)).toBeLessThan(1.8)
const p = variablesFor(byId('paper'))
expect(p['--p-field']).toBe(p['--p-control'])
expect(p['--p-field']).toBe('#e7e7e8')
expect(p['--p-field']).toBe(controlFieldOn(p['--p-side-flat'], '#1b1d21', true))
expect(p['--p-field-edge']).toBe(p['--p-divider'])
})

it('a near-black style is judged by its page, its field coloured off the row', () => {
// Aurora's page is near-black, its sidebar (#121419) is not: it keeps
// the quiet field, stepped off the sidebar.
const f = nearBlackField('#0b0d12', '#e8eaf0', '#121419')!
expect(f).not.toBeNull()
expect(contrastOf(f.fill, '#121419')).toBeGreaterThan(1.02)
expect(contrastOf(f.edge, '#121419')).toBeGreaterThanOrEqual(1.6)
})

it('every shipped style: the address field stands off the row it sits on (#306)', () => {
// MEASURED before: 1.01:1 on every dark style, the fill a step off the
// page and the row a step off it the other way.
for (const s of STYLES) {
const v = variablesFor(s, true)
expect(contrastOf(v['--p-field'], v['--p-side-flat']), s.id).toBeGreaterThan(1.03)
}
})

it('every shipped style: the field text and hint read on the field', () => {
for (const s of STYLES) {
const v = variablesFor(s, true)
Expand Down
21 changes: 17 additions & 4 deletions src/renderer/src/lib/fieldColours.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,29 @@ export interface FieldColours {
/**
* The field's colours on a near-black `ground` (both flat hex), or null where
* the style's own control fill and edge stay in force.
*
* `row` is the ground the field actually SITS on, when that is not the page's
* (#306: the address row wears the sidebar colour, a step off the page). The
* style is judged near-black by its page, so a black style whose sidebar is
* lifted keeps its quiet field; the colours step off the row, or a fill one
* shade off the page vanished into a sidebar one shade off it the other way
* (MEASURED: 1.01:1 on Void and Aurora).
*/
export function nearBlackField(ground: string, text: string): FieldColours | null {
export function nearBlackField(ground: string, text: string, row: string = ground): FieldColours | null {
if (!isNearBlack(ground)) return null
return {
fill: mixHex(ground, text, FILL_STEP),
edge: lineAt(ground, text, EDGE),
edgeHover: lineAt(ground, text, EDGE_HOVER)
fill: mixHex(row, text, FILL_STEP),
edge: lineAt(row, text, EDGE),
edgeHover: lineAt(row, text, EDGE_HOVER)
}
}

/** The field's fill anywhere else: the control step (3.5% towards the text on
* dark, 9% on light), taken from the ground the field sits on. */
export function controlFieldOn(row: string, text: string, light: boolean): string {
return mixHex(row, text, light ? 0.09 : 0.035)
}

/** A hint (the search's placeholder) held to 4.5:1 on the field it sits in:
* the dim ink is chosen against the page, and on a light style's field fill
* it fell to 3.95:1 (MEASURED on Paper). Walks the hint towards the text
Expand Down
9 changes: 6 additions & 3 deletions src/renderer/src/lib/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
withAlpha
} from 'prism-term-core/renderer/lib/colour'
import { accentAlphaOf, fillOf } from './accentAlpha'
import { hintOn, nearBlackField } from './fieldColours'
import { controlFieldOn, hintOn, nearBlackField } from './fieldColours'
import { THEME_STYLES } from './themes/catalogue'
import { migrateThemeStorage, RETIRED_KEY } from './themes/migrate'
import { RETIRED_MAP } from './themes/retired'
Expand Down Expand Up @@ -781,9 +781,12 @@ export function variablesFor(input: Style, opaque = false): Record<string, strin
// THE TOOLBAR'S FIELDS, the address and the search (#267): one fill, one
// edge. On a near-black ground (measured) a darker fill and an edge that
// carries the shape; elsewhere the search's own control fill and divider.
// Both step off the ADDRESS ROW's ground, the sidebar colour since #306: a
// fill stepped off the page sat at 1.01:1 on the row on every dark style
// (MEASURED), a field with no shape but its edge.
const flat = derive(style)
const field = nearBlackField(flat['--p-bg'], style.text)
const fieldFill = field?.fill ?? flat['--p-control']
const field = nearBlackField(flat['--p-bg'], style.text, flatSide)
const fieldFill = field?.fill ?? controlFieldOn(flatSide, style.text, style.mode === 'light')

return {
...flat,
Expand Down
39 changes: 33 additions & 6 deletions tools/e2e/run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9053,20 +9053,43 @@ async function sidebarGroundScenario(fixtures) {
const side = await cssColour(win, 'var(--p-side)')
let places = null
let list = null
let row = null
await until(async () => {
places = await groundOf('.folder-browser > .browse-places')
list = await groundOf('.folder-browser > .browse-list-area')
return places === side
row = await groundOf('.folder-browser > .browse-toolbar')
return places === side && row === side
}, 4000, 100)
ok(places !== null && places !== list, `${style}: the places panel's ground is not the list's (${places} vs ${list})`)
ok(places === side, `${style}: the places panel wears the sidebar colour (${places}, --p-side ${side})`)
// 3. THE ADDRESS ROW (#306; owner, 2026-10-07: "yes make this the same
// color as the sidebar"): the places panel's ground, not the list's,
// its hairline kept, and its field still a box on it.
ok(row === places, `${style}: the address row wears the places panel's ground (${row} vs ${places})`)
ok(row !== list, `${style}: and not the list's (${row} vs ${list})`)
const rowLook = await win.evaluate(() => {
const tb = document.querySelector('.folder-browser > .browse-toolbar')
const field = tb?.querySelector('nav.browse-path')
const s = tb ? getComputedStyle(tb) : null
return {
line: s ? { w: parseFloat(s.borderBottomWidth), c: s.borderBottomColor } : null,
field: field ? getComputedStyle(field).backgroundColor : null,
wide: tb ? Math.abs(tb.getBoundingClientRect().width - document.querySelector('.folder-browser').getBoundingClientRect().width) < 1 : false
}
})
ok(rowLook.wide, `${style}: the row runs across the whole browser`)
ok(rowLook.line && rowLook.line.w > 0 && rowLook.line.c !== row, `${style}: the row keeps its bottom hairline (${JSON.stringify(rowLook.line)})`)
ok(rowLook.field && rowLook.field !== row, `${style}: the address field is not the row's colour (${rowLook.field} on ${row})`)
await win.mouse.move(2, 400)
await sleep(300)
await win.screenshot({ path: join(SHOTS, `sidebar-ground-${style}.png`) })
const vw = await win.evaluate(() => window.innerWidth)
await win.screenshot({ path: join(SHOTS, `address-row-${style}.png`), clip: { x: 0, y: 0, width: vw, height: 220 } })
await settingsPage(win, 'appearance')
await win.waitForSelector('[data-settings-page] > nav', { timeout: 10000 })
const rail = await groundOf('[data-settings-page] > nav')
ok(rail === places, `${style}: and that is the Settings rail's ground (${rail})`)
ok(rail === row, `${style}: and the address row's (${row})`)
await win.click('[aria-label="Settings"]')
await sleep(300)
}
Expand Down Expand Up @@ -9954,13 +9977,17 @@ async function addressFieldScenario(fixtures) {
const cr = contrast(rgb(l.crumb), rgb(l.path.fill))
ok(cr >= 4.5, `${name}: a name reads on the field (${cr.toFixed(2)}:1)`)
}
// On Void the fill is DARKER than the old control step (rgb 8,8,8), still
// a step off the black, and a quiet edge carries the box (owner,
// 2026-10-04: "the white border stands out too much on the black theme").
ok(lum(rgb(v.path.fill)) < lum([8, 8, 8]) && lum(rgb(v.path.fill)) > 0, `Void: the field is a darker grey than before (${v.path.fill})`)
// On Void the fill is only a shade off the row it sits on, and a quiet
// edge carries the box (owner, 2026-10-04: "the white border stands out
// too much on the black theme"). Since #306 the row is the sidebar
// colour, and both step off THAT: a fill stepped off the page sat at
// 1.01:1 on the row (MEASURED), a field with no shape but its edge.
const vFill = contrast(rgb(v.path.fill), rgb(v.ground))
ok(vFill > 1.02 && vFill < 1.1, `Void: the field is a shade off the row, not a slab (${v.path.fill} on ${v.ground}, ${vFill.toFixed(3)}:1)`)
const edge = contrast(rgb(v.path.edge), rgb(v.ground))
ok(edge >= 1.5 && edge < 1.9, `Void: the field's edge is a quiet line, not a white frame (${edge.toFixed(2)}:1)`)
ok(p.path.fill === 'rgb(231, 231, 232)', `Paper: the field wears the control fill (${p.path.fill})`)
const pFill = contrast(rgb(p.path.fill), rgb(p.ground))
ok(pFill > 1.1 && lum(rgb(p.path.fill)) < lum(rgb(p.ground)), `Paper: the field wears the control step off the row (${p.path.fill} on ${p.ground}, ${pFill.toFixed(3)}:1)`)
ok(v.path.fill !== p.path.fill, 'Void and Paper fill the field differently')
// A hover strengthens the edge and leaves the fill alone.
const pathBox = win.locator('.folder-browser [data-testid="browse-toolbar"] nav.browse-path')
Expand Down
Loading