web: gate the newest film sims and the HSL mixer behind PRO
The last three PHOTO STYLE looks (B&W HIGH CONTRAST, LC STREETLIFE CLASSIC and LC STREETLIFE VIVID) and the whole mixer now belong to the account, the way PRO frames and the geotag already do: the chip wears the PRO badge, a guest who picks it is shown the way in, and the look stays off. The HSL tab keeps its place in the rail but offers the one PRO chip while locked, so the tab itself is not a dead end; a look that arrives without the chips — an imported .recipe, or a photo saved before the gate — is still caught where the gate bites, at export. STRAIGHTEN's scale turns with the wheel, one degree a notch, because the ruler is where the angle is being judged and reaching for a slider elsewhere loses the thread. The listener is native and stops the notch before the stage sees it, so the photo does not zoom under the pointer. The scale gives up its opaque card, its blur and its shadow: the frame it is levelling has to stay readable through it, so legibility comes from a text shadow on the heading and a drop shadow on the graduations instead.
This commit is contained in:
@@ -99,6 +99,10 @@ const BASE_RECIPE: Recipe = {
|
||||
// gate owns lives where it is used: the GPS stamp in wmChips, the HDF effect in
|
||||
// paramDefs (its own `pro` flag), and the export tier in TIERS.
|
||||
const PRO_FRAMES: FrameId[] = ['classic-white', 'polaroid', 'wallframe', 'wallframe-landscape'];
|
||||
// The three newest looks on the PHOTO STYLE strip are the account's own: the
|
||||
// pushed monochrome and the Leica rangefinder pair, which are the sims the
|
||||
// studio sells as the reason to prove the address.
|
||||
const PRO_SIMS: string[] = ['sim-bw-high-contrast', 'sim-leica', 'sim-leica-vivid'];
|
||||
|
||||
// FREE draws the frame on the canvas and lets the user drag its corners, so
|
||||
// every ratio is offered here — no ratio is applied until APPLY.
|
||||
@@ -1167,9 +1171,25 @@ export function Workspace() {
|
||||
const proLookInUse = useCallback(
|
||||
() =>
|
||||
PRO_FRAMES.includes(frameId) ||
|
||||
PRO_SIMS.includes(simId ?? '') ||
|
||||
useGeotag ||
|
||||
(recipe.adjustments.hdf ?? 0) > 0,
|
||||
[frameId, useGeotag, recipe.adjustments.hdf]
|
||||
(recipe.adjustments.hdf ?? 0) > 0 ||
|
||||
// The mixer, on either of its two axes: a band's HUE/SAT/LUM, or the
|
||||
// image-wide three that sit under the IMAGE divider.
|
||||
Object.keys(recipe.adjustments.hslBands ?? {}).length > 0 ||
|
||||
(recipe.adjustments.hslHue ?? 0) !== 0 ||
|
||||
(recipe.adjustments.hslSat ?? 0) !== 0 ||
|
||||
(recipe.adjustments.hslLum ?? 0) !== 0,
|
||||
[
|
||||
frameId,
|
||||
simId,
|
||||
useGeotag,
|
||||
recipe.adjustments.hdf,
|
||||
recipe.adjustments.hslBands,
|
||||
recipe.adjustments.hslHue,
|
||||
recipe.adjustments.hslSat,
|
||||
recipe.adjustments.hslLum,
|
||||
]
|
||||
);
|
||||
|
||||
// `true` when a file was handed over, so CLEAR knows the export it offered
|
||||
@@ -1705,7 +1725,7 @@ export function Workspace() {
|
||||
label: string;
|
||||
off: string;
|
||||
value: string;
|
||||
options: { v: string; d: string }[];
|
||||
options: { v: string; d: string; pro?: boolean }[];
|
||||
onPick: (v: string) => void;
|
||||
}> = {
|
||||
cx: {
|
||||
@@ -1760,10 +1780,16 @@ export function Workspace() {
|
||||
style: {
|
||||
label: 'PHOTO STYLE', off: 'sim-provia',
|
||||
value: simId && FILM_SIMS.some((s) => s.id === simId) ? simId : '',
|
||||
options: FILM_SIMS.map((s) => ({ v: s.id, d: s.name })),
|
||||
options: FILM_SIMS.map((s) => ({ v: s.id, d: s.name, pro: PRO_SIMS.includes(s.id) })),
|
||||
onPick: (v) => {
|
||||
const sim = FILM_SIMS.find((s) => s.id === v);
|
||||
if (!sim) return;
|
||||
// The newest three looks are the account's; the strip shows the badge
|
||||
// and the click says why (see the PRO gate at the top of the file).
|
||||
if (PRO_SIMS.includes(sim.id) && !pro) {
|
||||
promptPro();
|
||||
return;
|
||||
}
|
||||
setShowRecipes(false);
|
||||
applyRecipe(filmSimToRecipe(sim));
|
||||
},
|
||||
@@ -1840,7 +1866,7 @@ export function Workspace() {
|
||||
|
||||
const choiceChips = (
|
||||
prefix: string,
|
||||
options: { v: string; d: string }[],
|
||||
options: { v: string; d: string; pro?: boolean }[],
|
||||
current: string,
|
||||
onPick: (v: string) => void
|
||||
): ChipDef[] =>
|
||||
@@ -1848,6 +1874,7 @@ export function Workspace() {
|
||||
key: `${prefix}:${o.v}`,
|
||||
label: o.d,
|
||||
active: current === o.v,
|
||||
pro: o.pro,
|
||||
onClick: () => onPick(o.v),
|
||||
}));
|
||||
|
||||
@@ -2068,6 +2095,10 @@ export function Workspace() {
|
||||
...paramChips(PARAM_DEFS.filters),
|
||||
];
|
||||
case 'hsl': {
|
||||
// The mixer is PRO: the picker, the eight bands and the three
|
||||
// image-wide knobs all come with the account, so a guest is offered the
|
||||
// reason rather than a wall of dead chips.
|
||||
if (!pro) return [{ key: 'hsl-pro', label: t('pro.hslChip'), pro: true, onClick: promptPro }];
|
||||
// PICK arms the eyedropper; the eight band chips choose which band the
|
||||
// ruler edits, and the ruler itself is opened by the three knobs below.
|
||||
// Those three are the image's own move, not the band's: the band chips
|
||||
@@ -2321,7 +2352,7 @@ export function Workspace() {
|
||||
{tab === 'favorited' && saved.length === 0 ? <p className="hint">{t('sec.savedEmpty')}</p> : null}
|
||||
{/* The mixer's readout: the colour the eyedropper last read, as the
|
||||
RGB bytes and the HSL the band is indexed by. */}
|
||||
{tab === 'hsl' ? (
|
||||
{tab === 'hsl' && pro ? (
|
||||
<div className="hsl-readout" data-key="hsl-readout">
|
||||
{sample && picked ? (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user