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';