The two Color Chromes close the WB panel and D.RANGE gets a box of its own

Two picks that were drawn as rows of the column rather than as picks of a panel,
both of them because of where a slot happened to be rather than what the control
is. The pair of Color Chromes was LIGHT's own `chip-row grid` at the foot of the
column, under TONE CURVE, where it read as a third strip of the tab and sat far
from the two tracks it is the other axis of. D.RANGE was the effects slot, four
chips poured into the foot of DETAIL & EFFECTS where they were four more rows of
the panel and not the one setting they are.

WB now owns the pair. `DevelopPanels` gains a `foot` on a panel — a slot drawn
under the rows instead of above them — and the `wb` entry names `wbFoot`, so
COLOR CHROME and CHROME BLUE are rendered under COLOR TEMP and TINT, on the tab
whose colour they are and against the pair of tracks they are read with. They
keep everything else: the `chip-row grid` table layout the WB presets wear, their
keys, their OFF/ON labels and their pointer behaviour. The `wb` panel had no
slot but its presets and its two rows, so the foot is the whole of what changed
there; the four panels that name no foot are untouched, and the slot is looked up
per panel (`slots?.[panel.foot]`) so a panel that never asked for one draws
nothing.

D.RANGE closes DETAIL & EFFECTS inside a box that carries its name. `.dev-box` is
a frame in `--border-soft` with a `.dev-box-title` in the small-caps the panel
heads use, and the four stops sit in it as one set — which is what they are: a
hold on the whole frame, read as a set of stops, not as four more knobs of the
effects. The box is the slot's own wrapper, so it holds whichever chips the slot
is given and the chips keep their own keys and their amber AUTO.

Verified: `npx tsc --noEmit` clean, `npm run build` clean
(`dist/assets/index-v5g7p7bS.js`, `dist/assets/index-RYLacibB.css` at 66.29 kB).
Against the built page (`127.0.0.1:8090`, 1440x900 desktop and 393x852 phone),
LIGHT tab, panels open:

- LIGHT's own row is now `∿ TONE CURVE` alone — the pair is out of the column.
- WB's children read `[chip-row grid (presets), mini-slider COLOR TEMP,
  mini-slider TINT, chip-row grid COLOR CHROME OFF / CHROME BLUE OFF]`, and both
  chromes report `inWb: true` — `COLOR CHROME [94,424,147,37]`,
  `CHROME BLUE [247,424,147,37]` at 1440 wide, `[8,599,186,37]` and
  `[200,599,186,37]` at 393 — in the WB body, under the two tracks.
- The D.RANGE box reports `data-key="dev-box-dr"`, title `DYNAMIC RANGE`,
  `[94,754,300,162]` under `effBox [94,569,300,347]` at 1440 wide, border
  `1px rgb(236,238,241)` (`--border-soft`); the four chips are full-width rows
  (282px) at y=781/814/847/880, AUTO `aria-pressed=true` in amber
  `rgb(206,117,9)` on `rgba(206,117,9,0.14)` and DR100/200/400 on white. On the
  phone the box is `[8,929,377,63]` and the four chips come back to one line at
  `.chip-row`'s own `@media (max-width:860px)` override, `[17,956,58,27]`,
  `[81,956,62,27]`, `[149,956,64,27]`, `[219,956,65,27]` — no clipping, nothing
  over the caption — and `errors: []` on both.

Co-authored-by: PenguinHarness <noreply@penguin.local>
This commit is contained in:
2026-09-30 06:37:15 +07:00
parent 92776d2cfc
commit 4476372b9e
3 changed files with 40 additions and 20 deletions
+24 -18
View File
@@ -2369,8 +2369,8 @@ export function Workspace() {
};
};
// The two Color Chromes, side by side in LIGHT's own column: one row of two
// chips, each naming the strength it is on. They are a pair, not a strip —
// The two Color Chromes, side by side at the foot of the WB panel: one row of
// two chips, each naming the strength it is on. They are a pair, not a strip —
// COLOR CHROME and CHROME BLUE are the same switch over two axes of the same
// colour, so they are read against each other rather than scanned in turn, and
// they wear the table layout the WB presets wear (see .chip-row.grid /
@@ -2781,8 +2781,8 @@ export function Workspace() {
// and glows amber once the graph is off the diagonal. It stands over the
// TONE panel, whose knobs it is the shape of. The four features that
// write rather than name — AUTO, FIX, GRADIENT MASK, MONOCHROME — are
// the TOOLS tab's now, and the two Color Chromes are the pair of chips
// under this row (see chromeChips). WB and FX are panels of this tab
// the TOOLS tab's now, and the two Color Chromes close the WB panel
// (see chromeChips). WB and FX are panels of this tab
// now, so their rows live here: a chip that opened the same record the
// panel already shows would be a second way to one knob.
//
@@ -3047,13 +3047,12 @@ export function Workspace() {
})),
];
// The studio sidebar's three slots (DevelopPanels): the picks that name a
// whole look rather than a number, so they stay strips of their own inside the
// panel that owns them — SIM with the profile, the WB presets with the two WB
// tracks, D.RANGE at the foot of the effects. The two Color Chromes are the one
// pair that does not live in a panel: they are LIGHT's own row (see
// chromeChips). Every one of them is the strip that already existed, so a pick
// made here is the pick made on the tab it came from.
// The studio sidebar's slots (DevelopPanels): the picks that name a whole look
// rather than a number, so they stay strips of their own inside the panel that
// owns them — SIM with the profile, the WB presets and then the two Color
// Chromes with the two WB tracks, D.RANGE in its own box at the foot of the
// effects. Every one of them is the strip that already existed, so a pick made
// here is the pick made on the tab it came from.
const developSlots = {
profile: (
<ChipRow chips={choiceChips('sim', groupDefs.style.options, groupDefs.style.value, groupDefs.style.onPick, simPreview)} />
@@ -3089,11 +3088,19 @@ export function Workspace() {
}))}
/>
),
// D.RANGE closes DETAIL & EFFECTS: it is a hold on the whole frame, so it
// stands at the foot of the effects rather than with the tone knobs it is
// read against.
// The two Color Chromes close WB: same switch, the other axis of the two
// tracks above it, so they are read under the pair they are read against —
// and on the tab whose colour they are.
wbFoot: <ChipRow grid chips={chromeChips} />,
// D.RANGE closes DETAIL & EFFECTS in a box of its own: it is a hold on the
// whole frame, so it stands at the foot of the effects rather than with the
// tone knobs it is read against, and the four stops are one setting read as
// a set — the box and its name are what say so.
effects: (
<ChipRow chips={choiceChips('dr', DR_OPTIONS, String(recipe.adjustments.dynamicRange ?? 'auto'), groupDefs.dr.onPick)} />
<div className="dev-box" data-key="dev-box-dr">
<span className="dev-box-title">DYNAMIC RANGE</span>
<ChipRow chips={choiceChips('dr', DR_OPTIONS, String(recipe.adjustments.dynamicRange ?? 'auto'), groupDefs.dr.onPick)} />
</div>
),
};
@@ -3251,10 +3258,9 @@ export function Workspace() {
) : (
<>
{/* LIGHT's column is the sidebar itself: the chip that is not
a knob, the pair of Color Chromes, then every knob of the tab
in one stack, all of them open at once. */}
a knob, then every knob of the tab in one stack, all of
them folded away until one is asked for. */}
{tab === 'light' ? <ChipRow chips={chips} /> : null}
{tab === 'light' ? <ChipRow grid chips={chromeChips} /> : null}
{tab === 'light' ? (
<DevelopPanels
adj={recipe.adjustments}
+8
View File
@@ -1361,6 +1361,14 @@ input[type="range"] { width: 100%; accent-color: var(--accent); }
.dev-caret { font-size: 17px; line-height: 1; opacity: 0.75; }
.dev-panel-head:hover .dev-caret { opacity: 1; }
.dev-panel-body { display: flex; flex-direction: column; gap: 10px; padding-top: 8px; }
/* A pick that is a set of its own rather than a row of the panel's: framed and
named, so the four stops of D.RANGE read as one setting and not as four more
rows of DETAIL & EFFECTS. No colour rules: the frame is the token's. */
.dev-box { border: 1px solid var(--border-soft); border-radius: var(--radius-sm); padding: 8px; }
.dev-box-title {
display: block; margin-bottom: 6px;
font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-dim);
}
/* A row that cannot be edited at all: a PRO knob on the LITE build. It reads as
a row, not as a button — the tag is what says the row is not a slider. */
.dev-row { display: flex; flex-direction: column; gap: 4px; }
+8 -2
View File
@@ -25,13 +25,17 @@ const PANELS: {
// reading order — before the panel's knobs except in DETAIL & EFFECTS, whose
// D.RANGE selects close the column.
slot?: 'profile' | 'wb' | 'effects';
// A pick that closes its panel rather than opening it: drawn under the rows,
// where WB's two Color Chromes belong (they are read after the pair of tracks
// they are the other axis of).
foot?: 'wbFoot';
slotFirst?: boolean;
}[] = [
// PROFILE is the film simulation and nothing else: EV's ruler is a knob of the
// TONE panel's, the range the picture is on, so it sits with the rest of them
// and not with the stock the frame is shot on.
{ slug: 'profile', title: 'PROFILE', rows: [], slot: 'profile', slotFirst: true },
{ slug: 'wb', title: 'WB', rows: ['temperature', 'tint'], slot: 'wb', slotFirst: true },
{ slug: 'wb', title: 'WB', rows: ['temperature', 'tint'], slot: 'wb', slotFirst: true, foot: 'wbFoot' },
// EV leads TONE: it is the one number the whole range hangs off, and the
// exposure below it is the finer move on the same axis.
{ slug: 'tone', title: 'TONE', rows: ['ev', 'exposure', 'contrast', 'highlight', 'shadow', 'whites', 'blacks'] },
@@ -109,7 +113,7 @@ export function DevelopPanels({
set: (patch: Partial<ColorAdjustments>) => void;
pro?: boolean;
onPro?: () => void;
slots?: { profile?: ReactNode; wb?: ReactNode; effects?: ReactNode };
slots?: { profile?: ReactNode; wb?: ReactNode; wbFoot?: ReactNode; effects?: ReactNode };
}) {
const [open, setOpen] = useState<Record<string, boolean>>(() => {
const saved = new Set((recall(OPEN_KEY) ?? '').split(','));
@@ -125,6 +129,7 @@ export function DevelopPanels({
{PANELS.map((panel) => {
const isOpen = open[panel.slug];
const slot = panel.slot ? slots?.[panel.slot] : null;
const foot = panel.foot ? slots?.[panel.foot] : null;
return (
<section key={panel.slug} className="dev-panel" data-key={`dev-panel-${panel.slug}`}>
<button
@@ -143,6 +148,7 @@ export function DevelopPanels({
<DevRow key={key} paramKey={key} adj={adj} set={set} pro={pro} onPro={onPro} />
))}
{panel.slotFirst ? null : slot}
{foot}
</div>
) : null}
</section>