From 4d0d44170f8ca87ecd1850892b363122016e0248 Mon Sep 17 00:00:00 2001 From: 3dtours Date: Fri, 18 Sep 2026 16:16:28 +0700 Subject: [PATCH] web: an FX chip that flips the photo to monochrome MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is a switch, not a look: it swaps the base filter for the mono stock and swaps right back to the one the photo was wearing — name and sim included. The adjustments are never touched, so a knob moved while it is on survives the trip back, and the swap rides the undo stack like any other edit. --- docker/frontend/src/App.tsx | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/docker/frontend/src/App.tsx b/docker/frontend/src/App.tsx index 2638d9f..3088d27 100644 --- a/docker/frontend/src/App.tsx +++ b/docker/frontend/src/App.tsx @@ -256,6 +256,9 @@ export function Workspace() { // Which look of the open frame's own history is standing on the stage, if one // was picked from its column; a fresh edit or another frame clears it. const [histPick, setHistPick] = useState(null); + // FX's MONOCHROME switch remembers what the photo was wearing, so flipping it + // back returns that filter — and its sim name — instead of guessing PROVIA. + const monoPrev = useRef<{ baseFilter: Recipe['baseFilter']; simId: string | null; name: string } | null>(null); const lookRef = useRef(null); lookRef.current = { recipe, simId, frameId, crop, cropRect, cropApplied, rotation, markOn, markText, markStyle, useGeotag, gpsStyle }; // Twenty looks is enough to walk, but the oldest entry is the floor — the look @@ -1331,6 +1334,23 @@ export function Workspace() { // looks it carried before its last saves and its landing-strip consent. const openPhoto = myPhotos.find((p) => p.id === openPhotoId); + // FX's MONOCHROME switch is not a look of its own: it swaps the base filter + // for the mono stock and swaps right back. Adjustments are never touched, so + // a knob moved while the switch is on survives the trip back. + const monoOn = recipe.baseFilter === 'monochrome'; + const toggleMono = () => { + remember(); + if (monoOn) { + const back = monoPrev.current ?? { baseFilter: 'provia' as Recipe['baseFilter'], simId: 'sim-provia', name: 'PROVIPES' }; + setRecipe((prev) => ({ ...prev, baseFilter: back.baseFilter, name: back.name, isCustom: true })); + setSimId(back.simId); + } else { + monoPrev.current = { baseFilter: recipe.baseFilter, simId, name: recipe.name }; + setRecipe((prev) => ({ ...prev, baseFilter: 'monochrome', name: 'MONOCHROME', isCustom: true })); + setSimId(null); + } + }; + // The main chip row of the active tab, exactly the phone's per-tab sets. const chips: ChipDef[] = (() => { switch (tab) { @@ -1401,7 +1421,10 @@ export function Workspace() { groupChip('cxb'), ]; case 'fx': - return paramChips(PARAM_DEFS.filters); + return [ + { key: 'mono', label: 'MONOCHROME', active: monoOn, onClick: toggleMono }, + ...paramChips(PARAM_DEFS.filters), + ]; case 'frame': // WATERMARK is a chip like the rest: its own sub-panel opens as the next // column (see the wm column in the render), so the frame chips stay put.