diff --git a/App.tsx b/App.tsx
index ec330d7..dfd166f 100644
--- a/App.tsx
+++ b/App.tsx
@@ -1249,6 +1249,9 @@ export default function App() {
recipes={recipes}
currentRecipeId={selectedRecipe?.id || ''}
adjustments={adjustments}
+ lookDefaults={
+ (selectedRecipe && pristineRecipe(selectedRecipe)?.adjustments) || DEFAULT_ADJUSTMENTS
+ }
selectedFrame={selectedFrame}
cropRatio={cropRatio}
onUpdateCropRatio={setCropRatio}
diff --git a/src/components/AdjustmentPanel.tsx b/src/components/AdjustmentPanel.tsx
index 058867c..d3554dc 100644
--- a/src/components/AdjustmentPanel.tsx
+++ b/src/components/AdjustmentPanel.tsx
@@ -36,6 +36,9 @@ interface AdjustmentPanelProps {
recipes: Recipe[];
currentRecipeId: string;
adjustments: ColorAdjustments;
+ // The values the active look ships with — a saved recipe's own set, a film
+ // sim's canonical set, or the neutral defaults. RESET puts them back.
+ lookDefaults: ColorAdjustments;
selectedFrame: FrameId;
// FRAME tab CROP. Only meaningful on the library's plain frames (the camera
// composes through its ratio band, polaroid/wall through their own window),
@@ -163,6 +166,7 @@ export default function AdjustmentPanel({
recipes,
currentRecipeId,
adjustments,
+ lookDefaults,
selectedFrame,
cropRatio,
onUpdateCropRatio,
@@ -246,6 +250,17 @@ export default function AdjustmentPanel({
const a = adjustments;
+ // RESET (parameter tabs): every number of the active look back on the value
+ // it ships with. Only the parameters count as "changed" — the groups below
+ // are part of the same look, so they light it too.
+ const lookDirty =
+ [...PARAM_DEFS.iq, ...PARAM_DEFS.wb, ...PARAM_DEFS.filters].some(
+ (d) => d.get(a) !== d.get(lookDefaults)
+ ) ||
+ (a.colorChrome || 'none') !== (lookDefaults.colorChrome || 'none') ||
+ (a.colorChromeBlue || 'none') !== (lookDefaults.colorChromeBlue || 'none') ||
+ String(a.dynamicRange ?? 'auto') !== String(lookDefaults.dynamicRange ?? 'auto');
+
const renderChip = (c: ChipDef) => {
const tone = c.active
? 'border-amber-500/70 bg-amber-500/15'
@@ -302,6 +317,19 @@ export default function AdjustmentPanel({
})
);
+ const resetChip = (): ChipDef => ({
+ key: 'reset-params',
+ label: 'RESET',
+ active: false,
+ onPress: () => onUpdateAdjustments({ ...lookDefaults }),
+ });
+
+ // Pinned OUTSIDE the scroll row: the rows keep their scroll offset across
+ // tabs, so a RESET sitting inside one can be scrolled off just when it is
+ // wanted — after a tweak halfway along the strip.
+ const resetChipFixed = () =>
+ lookDirty ? {renderChip(resetChip())} : null;
+
const choiceChips = (
keyPrefix: string,
options: { v: string; d: string }[],
@@ -521,22 +549,28 @@ export default function AdjustmentPanel({
// opens its AUTO/DR100/DR200/DR400 strip above (same as the chrome
// groups on WB).
return (
-
- {paramChips(paramDefs.iq)}
- {renderChip(groupChip('dr'))}
-
+
+ {resetChipFixed()}
+
+ {paramChips(paramDefs.iq)}
+ {renderChip(groupChip('dr'))}
+
+
);
case 'wb':
return (
-
+
+ {resetChipFixed()}
{choiceChips(
@@ -561,13 +595,17 @@ export default function AdjustmentPanel({
case 'filters':
return (
-
- {paramChips(paramDefs.filters)}
-
+
+ {resetChipFixed()}
+
+ {paramChips(paramDefs.filters)}
+
+
);
case 'frame':
diff --git a/src/components/RecipeCreateModal.tsx b/src/components/RecipeCreateModal.tsx
index 9b69a07..d10770c 100644
--- a/src/components/RecipeCreateModal.tsx
+++ b/src/components/RecipeCreateModal.tsx
@@ -228,9 +228,12 @@ export default function RecipeCreateModal({
setIso(null);
}, [visible, currentBase, initial]);
- // Switching the simulation reloads its standard values into the form.
+ // Switching the simulation reloads its standard values into the form — not
+ // in the editor though: there the numbers on screen are the user's own, and
+ // swapping the base look under them must not throw them away.
const selectSim = (b: BaseFilter) => {
setSim(b);
+ if (initial) return;
const v = valuesOf(b);
setDr(v.dr);
setGrain(v.grain);