diff --git a/docker/frontend/src/App.tsx b/docker/frontend/src/App.tsx index 876a6c3..7c79683 100644 --- a/docker/frontend/src/App.tsx +++ b/docker/frontend/src/App.tsx @@ -2810,10 +2810,13 @@ export function Workspace() { }, }); - const framesRow = (): ChipDef[] => [ + // The frame row is the frames alone. CROP, ROTATE and WATERMARK used to close + // it — a frame is the look, and cutting, levelling and signing the print are + // moves made on the frame, which is what TOOLS is (see `shapeChips`). + const framesRow = (): ChipDef[] => // NO FRAME is not a chip of its own: clicking the frame already on the // photo takes it off again, so the row carries one entry per frame. - ...FRAMES.filter((frame) => frame.id !== 'none').map((frame) => ({ + FRAMES.filter((frame) => frame.id !== 'none').map((frame) => ({ key: `fr:${frame.id}`, label: t(frame.label), active: frameId === frame.id, @@ -2828,7 +2831,12 @@ export function Workspace() { remember(); setFrameId(frameId === frame.id ? 'none' : frame.id); }, - })), + })); + + // The three moves that shape the print rather than tint it: the frame is cut + // to a shape, turned to the straight, and signed. Each opens its own strip in + // the last column, the same strip it opened from the frame row. + const shapeChips: ChipDef[] = [ groupChip('crop'), // The chip names whichever of the two is off the straight — and both when // both are — then the mirrors riding on top of them. @@ -3022,6 +3030,11 @@ export function Workspace() { // a tab instead of LIGHT's row. Each carries its own mark at the head of // its label so the four read as one set. // + // CROP, ROTATE and WATERMARK closed the frame row and are here for the + // same reason: cutting, levelling and signing the print are moves on the + // frame rather than a frame picked for it, and they take the same strips + // they always opened (see shapeChips). + // // Nothing here is a knob, so the tab is this row alone: the strip a chip // opens is the last column's (see stripChips), the same as anywhere else. // AUTO and MONOCHROME come with any account — one lands a set of numbers @@ -3069,6 +3082,10 @@ export function Workspace() { } : locked('gradient-mask', t('pro.gradientChip')), { key: 'mono', label: '◑ MONOCHROME', active: monoOn, onClick: toggleMono }, + // The rule stands where the features that read or take the pointer + // end and the marks cut into the print begin. + { key: 'tools-rule', divider: true, label: '', onClick: () => {} }, + ...shapeChips, ]; case 'hsl': { // The mixer is PRO: the picker, the eight bands and the three @@ -3720,11 +3737,11 @@ export function Workspace() { - {/* FRAME's WATERMARK chip opens the two collapses, one per + {/* TOOLS' WATERMARK chip opens the two collapses, one per watermark type: the header chip, then that mark's own controls */} {wmOpen ? (
- setOpenGroup(null) }]} /> + setOpenGroup(null) }]} /> {/* GPS WATERMARK — its colour, its face, its two rows, its place */}
diff --git a/docker/frontend/src/styles/app.css b/docker/frontend/src/styles/app.css index 8eaa620..9323ae9 100644 --- a/docker/frontend/src/styles/app.css +++ b/docker/frontend/src/styles/app.css @@ -323,7 +323,60 @@ font-weight: 700; cursor: pointer; } -input[type="range"] { width: 100%; accent-color: var(--accent); } +/* A slider is a line and a node, and nothing else. The line is the theme's own + border ink at a hairline; the node is hollow — a ring drawn in the accent, no + fill of its own — so the control wears the theme's colour instead of the + browser's. `accent-color` goes with the old look: it painted a track and a + solid thumb of its own, and there is nothing of either left for it to paint. + The box is left taller than the line on purpose: the box is the drag target + (a finger is wider than a hairline), the line is what is read. */ +input[type="range"] { + width: 100%; + height: 16px; + margin: 0; + -webkit-appearance: none; + appearance: none; + background: transparent; + cursor: pointer; +} +/* One rule per engine: a grouped selector naming a pseudo-element the engine + does not know is dropped whole, and the track went with it (measured). */ +input[type="range"]::-webkit-slider-runnable-track { + height: 2px; + border-radius: 999px; + background: var(--border); +} +input[type="range"]::-moz-range-track { + height: 2px; + border-radius: 999px; + background: var(--border); +} +input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 10px; + height: 10px; + margin-top: -4px; /* (10px node - 2px line) / 2 */ + border: 1.5px solid var(--accent); + border-radius: 50%; + background: transparent; +} +input[type="range"]::-moz-range-thumb { + -webkit-appearance: none; + appearance: none; + box-sizing: border-box; + width: 10px; + height: 10px; + border: 1.5px solid var(--accent); + border-radius: 50%; + background: transparent; +} +/* The knob the pointer is on takes the theme's own hover ink, so the node says + it is the one being dragged. */ +input[type="range"]:hover::-webkit-slider-thumb, +input[type="range"]:focus-visible::-webkit-slider-thumb { border-color: var(--accent-hover); } +input[type="range"]:hover::-moz-range-thumb, +input[type="range"]:focus-visible::-moz-range-thumb { border-color: var(--accent-hover); } .hint { color: var(--text-dim); font-size: 12px; line-height: 1.5; } .hint.ok { color: var(--accent); } /* A hint that is also a control — the backup row's folder is the reader's to @@ -1557,19 +1610,20 @@ button.hint:disabled { cursor: default; } } .dev-pro { font-size: 9px; letter-spacing: 0.08em; color: var(--accent); } /* WB's two knobs: the track is the hue the value moves towards, the way - Lightroom's temperature and tint sliders are painted. Everything else keeps - the plain input's accent color. */ -.track-temp, .track-tint { - height: 4px; border-radius: 999px; - appearance: none; -webkit-appearance: none; -} -.track-temp { background: linear-gradient(90deg, #3f6fd8, #b9c2cc 50%, #e8a33d); } -.track-tint { background: linear-gradient(90deg, #3f9d55, #b9c2cc 50%, #c04ec4); } -.track-temp::-webkit-slider-thumb, .track-tint::-webkit-slider-thumb { - appearance: none; -webkit-appearance: none; - width: 12px; height: 12px; border-radius: 50%; - background: var(--text); border: none; cursor: pointer; -} + Lightroom's temperature and tint sliders are painted — the same line as + everywhere else, only coloured, and a shade thicker so the cast it names can + be read. The node is the app's own ring and is left to the rule above: the + line says where the value is, the ring says where the knob is. The colour sits + on the track and not on the input (where it used to be drawn) because the + line is what is read — a background on the box would have shown through + around the hairline every other knob is drawn with. */ +.track-temp::-webkit-slider-runnable-track { background: linear-gradient(90deg, #3f6fd8, #b9c2cc 50%, #e8a33d); height: 4px; } +.track-tint::-webkit-slider-runnable-track { background: linear-gradient(90deg, #3f9d55, #b9c2cc 50%, #c04ec4); height: 4px; } +.track-temp::-moz-range-track { background: linear-gradient(90deg, #3f6fd8, #b9c2cc 50%, #e8a33d); height: 4px; } +.track-tint::-moz-range-track { background: linear-gradient(90deg, #3f9d55, #b9c2cc 50%, #c04ec4); height: 4px; } +/* The node is centred on a track twice the hairline's height, so it shifts up + the one pixel the base rule does not account for. */ +.track-temp::-webkit-slider-thumb, .track-tint::-webkit-slider-thumb { margin-top: -3px; } /* --- responsive --------------------------------------------------------- */ @media (max-width: 860px) { diff --git a/docker/frontend/src/ui/ToolRail.tsx b/docker/frontend/src/ui/ToolRail.tsx index 2a9b923..6453a70 100644 --- a/docker/frontend/src/ui/ToolRail.tsx +++ b/docker/frontend/src/ui/ToolRail.tsx @@ -6,7 +6,9 @@ import { useI18n } from '../i18n/I18nProvider'; // FX, which used to be tabs of their own, are panels of it now. TOOLS is the one // tab of features rather than records: the chips that write a value or take the // pointer (AUTO, FIX, GRADIENT MASK, MONOCHROME), which used to share LIGHT's -// row with the two chips that are not knobs. The rail is a single column on +// row with the two chips that are not knobs, and — since they act on the frame +// rather than pick a look for it — CROP, ROTATE and WATERMARK, which used to +// close the frame row. The rail is a single column on // desktop and becomes the bottom bar under 860px (see styles/app.css). export type TabId = 'presets' | 'favorited' | 'photos' | 'light' | 'tools' | 'hsl' | 'frame' | 'save' | 'create';