diff --git a/docker/frontend/shared/types/index.ts b/docker/frontend/shared/types/index.ts index 9cffb4a..f16a24d 100644 --- a/docker/frontend/shared/types/index.ts +++ b/docker/frontend/shared/types/index.ts @@ -68,7 +68,7 @@ export const MIN_CROP_FRAC = 0.08; // cool tint in shade, plus a seasonal atmospheric haze/fog veil. export type CinemaSeason = 'spring' | 'summer' | 'autumn' | 'winter'; -export type BaseFilter = 'classic-neg' | 'classic-chrome' | 'provia' | 'velvia' | 'astia' | 'eterna' | 'monochrome' | 'leica' | 'leica-vivid' | 'none'; +export type BaseFilter = 'classic-neg' | 'classic-chrome' | 'classic-vivid' | 'provia' | 'velvia' | 'astia' | 'eterna' | 'monochrome' | 'leica' | 'leica-vivid' | 'none'; export interface ColorAdjustments { exposure: number; // -10 to +10 (mapped to matrix multiplier or offset) diff --git a/docker/frontend/shared/utils/colorUtils.ts b/docker/frontend/shared/utils/colorUtils.ts index 4a69a24..4d8dbf2 100644 --- a/docker/frontend/shared/utils/colorUtils.ts +++ b/docker/frontend/shared/utils/colorUtils.ts @@ -120,6 +120,17 @@ export function getSkiaColorMatrix(baseFilter: BaseFilter, adj: ColorAdjustments 0.0652, 0.3973, 0.5575, 0, -0.0100, 0, 0, 0, 1, 0, ]; + } else if (baseFilter === 'classic-vivid') { + // Classic Vivid: the Classic Chrome blue row kept verbatim — skies stay the + // same muted teal/cyan — with the red and green rows taken from Velvia, so + // everything that is not blue reads loud. Two stocks spliced at the blue + // row, which is exactly the point: quiet skies over saturated colour. + matrix = [ + 1.3895, -0.2540, -0.0256, 0, -0.0550, + -0.0772, 1.2355, -0.0261, 0, -0.0661, + 0.0652, 0.3973, 0.5575, 0, -0.0100, + 0, 0, 0, 1, 0, + ]; } else if (baseFilter === 'astia') { // Astia (Soft): gentler than standard — contrast below neutral (shadows // lift, highlights ease off) and colour held back, with a faint warm/pink diff --git a/docker/frontend/shared/utils/defaultRecipes.ts b/docker/frontend/shared/utils/defaultRecipes.ts index 22cb950..700978f 100644 --- a/docker/frontend/shared/utils/defaultRecipes.ts +++ b/docker/frontend/shared/utils/defaultRecipes.ts @@ -41,6 +41,7 @@ export const FILM_SIMS: FilmSim[] = [ { id: 'sim-provia', name: 'PROVIPES', baseFilter: 'provia', adjustments: { ...DEFAULT_ADJUSTMENTS } }, { id: 'sim-velvia', name: 'VELVIPES', baseFilter: 'velvia', adjustments: { ...DEFAULT_ADJUSTMENTS } }, { id: 'sim-classic-chrome', name: 'CLASSIC CHRIPES', baseFilter: 'classic-chrome', adjustments: { ...DEFAULT_ADJUSTMENTS } }, + { id: 'sim-classic-vivid', name: 'CLASSIC VIVIDIPES', baseFilter: 'classic-vivid', adjustments: { ...DEFAULT_ADJUSTMENTS } }, { id: 'sim-classic-neg', name: 'CLASSIC NEGIPES', baseFilter: 'classic-neg', adjustments: { ...DEFAULT_ADJUSTMENTS } }, { id: 'sim-astia', name: 'ASTIPES', baseFilter: 'astia', adjustments: { ...DEFAULT_ADJUSTMENTS } }, { id: 'sim-eterna', name: 'ETERNIPES', baseFilter: 'eterna', adjustments: { ...DEFAULT_ADJUSTMENTS } }, diff --git a/docker/frontend/shared/utils/toneShader.ts b/docker/frontend/shared/utils/toneShader.ts index e1cf48c..4346215 100644 --- a/docker/frontend/shared/utils/toneShader.ts +++ b/docker/frontend/shared/utils/toneShader.ts @@ -207,6 +207,9 @@ export interface ToneUniforms { // without also moving the other. const FILM_TONE: Partial>> = { 'classic-chrome': { sh: -0.28 }, + // Classic Vivid is Classic Chrome's sibling — the shadow crush belongs to the + // stock, not to the matrix rows, so it comes along. + 'classic-vivid': { sh: -0.28 }, 'classic-neg': { shT: [-0.018, 0.009, 0.013], hlT: [0.024, 0.008, -0.012] }, // Acros. A black-and-white stock IS its grey ramp, so this entry only shapes // the two ENDS and leaves the middle an identity: a smooth shadow toe that