diff --git a/docker/frontend/src/Landing.tsx b/docker/frontend/src/Landing.tsx index 810ae58..f2fe95e 100644 --- a/docker/frontend/src/Landing.tsx +++ b/docker/frontend/src/Landing.tsx @@ -111,8 +111,11 @@ const REEL: Omit[] = [ { tag: '#ETRNA_VINTAGE', title: { en: 'Faded Postcard', vi: 'Bưu thiếp bạc màu' }, meta: 'ISO 200 · GRAIN 25 · WARMTH +10', photo: SAMPLE(1015), filter: 'sepia(.28) saturate(.9) contrast(.94) brightness(1.08)' }, ]; +type PresetGroup = 'landscape' | 'portrait' | 'streetlife'; + interface Preset { id: string; + group: PresetGroup; // Stock and recipe names are proper nouns — never translated (see i18n/vi.ts). label: string; name: string; @@ -122,12 +125,32 @@ interface Preset { metrics: [number, number, number, number]; } +// The tester's dropdown is grouped by genre, five stocks each. +const PRESET_GROUPS: { id: PresetGroup; label: Txt }[] = [ + { id: 'landscape', label: { en: 'Landscape', vi: 'Phong cảnh' } }, + { id: 'portrait', label: { en: 'Portrait', vi: 'Chân dung' } }, + { id: 'streetlife', label: { en: 'Streetlife', vi: 'Đường phố' } }, +]; + const PRESETS: Preset[] = [ - { id: 'portra', label: 'Kodak Portra 400', name: 'PORTRA 400', desc: { en: 'Warm daylight stock, forgiving highlights and natural skin tones — the everyday portrait recipe.', vi: 'Phim chân dung ánh sáng ban ngày ấm, vùng sáng mềm và tông da tự nhiên — công thức chụp hằng ngày.' }, filter: 'sepia(.18) saturate(1.15) contrast(1.03) brightness(1.05)', metrics: [72, 34, 48, 22] }, - { id: 'chrome', label: 'Fuji Classic Chrome', name: 'CLASSIC CHROME', desc: { en: 'Deep greens, muted reds and a documentary edge straight out of the rangefinder era.', vi: 'Xanh lá sâu, đỏ trầm và chất tài liệu đúng kiểu máy rangefinder.' }, filter: 'saturate(.76) contrast(1.16) hue-rotate(-8deg)', metrics: [38, 22, 68, 14] }, - { id: 'sunset', label: 'Warm Sunset Glow', name: 'SUNSET GLOW', desc: { en: 'Amber backlight with a soft halation around every highlight, made for late golden hour.', vi: 'Ngược sáng hổ phách với quầng sáng mềm quanh vùng cháy, dành cho giờ vàng muộn.' }, filter: 'sepia(.34) saturate(1.35) contrast(1.06) brightness(1.06)', metrics: [88, 28, 52, 61] }, - { id: 'cyber', label: "Cyber Neon '88", name: 'CYBER NEON 88', desc: { en: 'Magenta and cyan push, crushed blacks and a bloom that reads like wet asphalt at night.', vi: 'Đẩy magenta và cyan, đen bị nén và quầng sáng như nhựa đường ướt ban đêm.' }, filter: 'saturate(1.8) contrast(1.3) hue-rotate(266deg)', metrics: [24, 46, 84, 78] }, - { id: 'mono', label: 'Monochromatic Film', name: 'ACROS 100', desc: { en: 'High-contrast black and white with a fine grain structure and clean mid-tone separation.', vi: 'Đen trắng tương phản cao, hạt mịn và vùng trung tính tách bạch.' }, filter: 'grayscale(1) contrast(1.4) brightness(1.02)', metrics: [6, 41, 92, 18] }, + // --- landscape --------------------------------------------------------- + { id: 'velvia', group: 'landscape', label: 'Fuji Velvia 50', name: 'VELVIA 50', desc: { en: 'Saturated slide film for moss, ridge lines and sunset skies — colour that refuses to whisper.', vi: 'Phim slide bão hoà cho rêu, sống núi và trời hoàng hôn — màu không chịu thì thầm.' }, filter: 'saturate(1.5) contrast(1.24) hue-rotate(-5deg)', metrics: [20, 26, 82, 30] }, + { id: 'ektar', group: 'landscape', label: 'Kodak Ektar 100', name: 'EKTAR 100', desc: { en: 'Ultra-fine grain and punchy primaries, the wide-open landscape stock.', vi: 'Hạt cực mịn, màu gốc căng — phim phong cảnh khẩu độ mở.' }, filter: 'saturate(1.32) contrast(1.14) sepia(.06)', metrics: [46, 20, 72, 34] }, + { id: 'provia', group: 'landscape', label: 'Fuji Provia 100F', name: 'PROVIA 100F', desc: { en: 'Neutral slide film that holds the sky and the shadows without pushing either.', vi: 'Phim slide trung tính giữ được cả trời và vùng tối mà không đẩy bên nào.' }, filter: 'saturate(1.14) contrast(1.1) brightness(1.03)', metrics: [34, 24, 64, 22] }, + { id: 'vivid', group: 'landscape', label: 'Classic Vivid', name: 'CLASSIC VIVID', desc: { en: 'The camera’s own vivid profile: deep blues, warm rock, nothing held back.', vi: 'Profile vivid của máy: xanh sâu, đá ấm, không giữ lại gì.' }, filter: 'saturate(1.22) contrast(1.2)', metrics: [30, 28, 76, 26] }, + { id: 'sunset', group: 'landscape', label: 'Warm Sunset Glow', name: 'SUNSET GLOW', desc: { en: 'Amber backlight with a soft halation around every highlight, made for late golden hour.', vi: 'Ngược sáng hổ phách với quầng sáng mềm quanh vùng cháy, dành cho giờ vàng muộn.' }, filter: 'sepia(.34) saturate(1.35) contrast(1.06) brightness(1.06)', metrics: [88, 28, 52, 61] }, + // --- portrait ---------------------------------------------------------- + { id: 'portra', group: 'portrait', label: 'Kodak Portra 400', name: 'PORTRA 400', desc: { en: 'Warm daylight stock, forgiving highlights and natural skin tones — the everyday portrait recipe.', vi: 'Phim chân dung ánh sáng ban ngày ấm, vùng sáng mềm và tông da tự nhiên — công thức chụp hằng ngày.' }, filter: 'sepia(.18) saturate(1.15) contrast(1.03) brightness(1.05)', metrics: [72, 34, 48, 22] }, + { id: 'astia', group: 'portrait', label: 'Fuji Astia', name: 'ASTIA', desc: { en: 'Soft contrast and gentle skin rendering, the portrait film that flatters without lying.', vi: 'Tương phản mềm, da lên dịu — phim chân dung làm đẹp mà không nói dối.' }, filter: 'sepia(.1) saturate(1.06) contrast(.98) brightness(1.06)', metrics: [62, 30, 42, 26] }, + { id: 'eterna', group: 'portrait', label: 'Fuji Eterna', name: 'ETERNA', desc: { en: 'Cinema negative: muted highlights, even skin, the grade of a quiet two-shot.', vi: 'Phim âm bản điện ảnh: vùng sáng trầm, da đều, đúng tông cảnh hai người.' }, filter: 'sepia(.07) saturate(.88) contrast(1.06) brightness(1.02)', metrics: [54, 26, 50, 20] }, + { id: 'classicneg', group: 'portrait', label: 'Classic Negative', name: 'CLASSIC NEG', desc: { en: 'Amber shadows and restrained skin — the modern editorial portrait look.', vi: 'Vùng tối hổ phách, da tiết chế — chất chân dung editorial hiện đại.' }, filter: 'sepia(.12) saturate(.94) contrast(1.16)', metrics: [66, 32, 60, 24] }, + { id: 'gold', group: 'portrait', label: 'Kodak Gold 200', name: 'GOLD 200', desc: { en: 'Sunlit warmth, honeyed highlights and the grain of a family album.', vi: 'Ấm nắng, vùng sáng mật ong và hạt phim như album gia đình.' }, filter: 'sepia(.22) saturate(1.18) contrast(1.04) brightness(1.05)', metrics: [78, 36, 46, 30] }, + // --- streetlife -------------------------------------------------------- + { id: 'chrome', group: 'streetlife', label: 'Fuji Classic Chrome', name: 'CLASSIC CHROME', desc: { en: 'Deep greens, muted reds and a documentary edge straight out of the rangefinder era.', vi: 'Xanh lá sâu, đỏ trầm và chất tài liệu đúng kiểu máy rangefinder.' }, filter: 'saturate(.76) contrast(1.16) hue-rotate(-8deg)', metrics: [38, 22, 68, 14] }, + { id: 'cyber', group: 'streetlife', label: "Cyber Neon '88", name: 'CYBER NEON 88', desc: { en: 'Magenta and cyan push, crushed blacks and a bloom that reads like wet asphalt at night.', vi: 'Đẩy magenta và cyan, đen bị nén và quầng sáng như nhựa đường ướt ban đêm.' }, filter: 'saturate(1.8) contrast(1.3) hue-rotate(266deg)', metrics: [24, 46, 84, 78] }, + { id: 'mono', group: 'streetlife', label: 'Monochromatic Film', name: 'ACROS 100', desc: { en: 'High-contrast black and white with a fine grain structure and clean mid-tone separation.', vi: 'Đen trắng tương phản cao, hạt mịn và vùng trung tính tách bạch.' }, filter: 'grayscale(1) contrast(1.4) brightness(1.02)', metrics: [6, 41, 92, 18] }, + { id: 'streetclassic', group: 'streetlife', label: 'LC Streetlife Classic', name: 'STREETLIFE CLASSIC', desc: { en: 'Cool shadows and hard mid-tones for concrete, signage and passing crowds.', vi: 'Vùng tối lạnh, trung tính gắt cho bê tông, biển hiệu và dòng người qua.' }, filter: 'saturate(.82) contrast(1.24) hue-rotate(-6deg) brightness(.98)', metrics: [30, 38, 74, 16] }, + { id: 'streetvivid', group: 'streetlife', label: 'LC Streetlife Vivid', name: 'STREETLIFE VIVID', desc: { en: 'The same street, turned up: tougher contrast, louder colour, night-market energy.', vi: 'Vẫn đường phố đó, vặn to hơn: tương phản gắt, màu rực, năng lượng chợ đêm.' }, filter: 'saturate(1.4) contrast(1.28) hue-rotate(8deg)', metrics: [26, 40, 80, 34] }, ]; const BAR_LABELS: Txt[] = [ @@ -678,17 +701,28 @@ export function Landing() {
{c({ en: 'Live preset tester', vi: 'Thử công thức trực tiếp' })}

{c({ en: 'Tap a recipe, watch the negative change', vi: 'Chọn công thức, xem âm bản đổi ngay' })}

-

{c({ en: 'Five stocks from the library, applied to the same frame with the same colour maths the exporter uses.', vi: 'Năm loại phim trong thư viện, áp lên cùng một khung với đúng phép màu mà bộ xuất ảnh dùng.' })}

+

{c({ en: 'Fifteen stocks across landscape, portrait and streetlife, applied to the same frame with the same colour maths the exporter uses.', vi: 'Mười lăm loại phim cho phong cảnh, chân dung và đường phố, áp lên cùng một khung với đúng phép màu mà bộ xuất ảnh dùng.' })}

-
- {PRESETS.map((p) => ( - - ))} -
+