Visual pass on recipes
This commit is contained in:
parent
338426114e
commit
059bd40cc7
@ -20,6 +20,8 @@ export default async function AdminRecipePage({
|
||||
backgroundImageUrl={photo.url}
|
||||
recipe={fujifilmRecipe}
|
||||
simulation={filmSimulation}
|
||||
exposure={photo.exposureCompensationFormatted ?? '+0ev'}
|
||||
iso={photo.isoFormatted ?? 'ISO 0'}
|
||||
/>
|
||||
: <div>
|
||||
Can't find photo/recipe
|
||||
|
||||
@ -5,7 +5,7 @@ export default function Badge({
|
||||
className,
|
||||
type = 'large',
|
||||
dimContent,
|
||||
highContrast,
|
||||
contrast = 'low',
|
||||
uppercase,
|
||||
interactive,
|
||||
}: {
|
||||
@ -13,7 +13,7 @@ export default function Badge({
|
||||
className?: string
|
||||
type?: 'large' | 'small' | 'text-only'
|
||||
dimContent?: boolean
|
||||
highContrast?: boolean
|
||||
contrast?: 'low' | 'medium' | 'high' | 'frost'
|
||||
uppercase?: boolean
|
||||
interactive?: boolean
|
||||
}) {
|
||||
@ -30,13 +30,15 @@ export default function Badge({
|
||||
return clsx(
|
||||
'px-[5px] h-[17px] md:h-[18px]',
|
||||
'text-[0.7rem] font-medium rounded-[0.25rem]',
|
||||
highContrast
|
||||
contrast === 'high'
|
||||
? 'text-invert bg-invert'
|
||||
: 'text-medium bg-gray-300/30 dark:bg-gray-700/50',
|
||||
interactive && (highContrast
|
||||
: contrast === 'frost'
|
||||
? 'text-black bg-white/30'
|
||||
: 'text-medium bg-gray-300/30 dark:bg-gray-700/50',
|
||||
interactive && (contrast === 'high'
|
||||
? 'hover:opacity-70'
|
||||
: 'hover:text-gray-900 dark:hover:text-gray-100'),
|
||||
interactive && (highContrast
|
||||
interactive && (contrast === 'high'
|
||||
? 'active:opacity-90'
|
||||
: 'active:bg-gray-200 dark:active:bg-gray-700/60'),
|
||||
);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { ReactNode } from 'react';
|
||||
import { ComponentProps, ReactNode } from 'react';
|
||||
import LabeledIcon, { LabeledIconType } from './LabeledIcon';
|
||||
import Badge from '../Badge';
|
||||
import { clsx } from 'clsx/lite';
|
||||
@ -10,7 +10,7 @@ import Spinner from '../Spinner';
|
||||
export interface EntityLinkExternalProps {
|
||||
type?: LabeledIconType
|
||||
badged?: boolean
|
||||
contrast?: 'low' | 'medium' | 'high'
|
||||
contrast?: ComponentProps<typeof Badge>['contrast']
|
||||
prefetch?: boolean
|
||||
}
|
||||
|
||||
@ -48,6 +48,8 @@ export default function EntityLink({
|
||||
return 'text-dim';
|
||||
case 'high':
|
||||
return 'text-main';
|
||||
case 'frost':
|
||||
return 'text-invert';
|
||||
default:
|
||||
return 'text-medium';
|
||||
}
|
||||
@ -88,7 +90,7 @@ export default function EntityLink({
|
||||
{badged
|
||||
? <Badge
|
||||
type="small"
|
||||
highContrast={contrast === 'high'}
|
||||
contrast={contrast}
|
||||
className='translate-y-[-0.5px]'
|
||||
uppercase
|
||||
interactive
|
||||
|
||||
@ -2,6 +2,7 @@ import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
|
||||
import { FilmSimulation } from '@/simulation';
|
||||
import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation';
|
||||
import clsx from 'clsx/lite';
|
||||
import { IoCloseCircle } from 'react-icons/io5';
|
||||
|
||||
const addSign = (value = 0) => value < 0 ? value : `+${value}`;
|
||||
|
||||
@ -38,18 +39,18 @@ export default function PhotoRecipeFrostLight({
|
||||
bwMagentaGreen,
|
||||
},
|
||||
simulation,
|
||||
exposure,
|
||||
iso,
|
||||
}: {
|
||||
recipe: FujifilmRecipe
|
||||
simulation: FilmSimulation
|
||||
exposure: string
|
||||
iso: string
|
||||
}) {
|
||||
const whiteBalanceFormatted = (whiteBalance?.type ?? 'auto')
|
||||
.replaceAll('auto', ' ')
|
||||
.replaceAll('-', ' ');
|
||||
|
||||
const hasCustomizedWhiteBalance =
|
||||
Boolean(whiteBalance?.red) ||
|
||||
Boolean(whiteBalance?.blue);
|
||||
|
||||
const hasBWAdjustments =
|
||||
Boolean(bwAdjustment) ||
|
||||
Boolean(bwMagentaGreen);
|
||||
@ -75,34 +76,46 @@ export default function PhotoRecipeFrostLight({
|
||||
'rounded-lg shadow-2xl',
|
||||
'bg-white/60 backdrop-blur-xl border border-white/30',
|
||||
'space-y-3',
|
||||
'text-[13px] text-main',
|
||||
'text-[13px] text-black',
|
||||
'saturate-200',
|
||||
)}>
|
||||
<div className="flex items-center gap-2">
|
||||
<PhotoFilmSimulation
|
||||
contrast="high"
|
||||
contrast="frost"
|
||||
className="grow"
|
||||
simulation={simulation}
|
||||
/>
|
||||
<div className="bg-white/60 rounded-md px-1">
|
||||
<span>DR</span>
|
||||
<span>{dynamicRange ?? 100}</span>
|
||||
</div>
|
||||
<IoCloseCircle
|
||||
size={20}
|
||||
className="text-black/25"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="uppercase space-y-2"
|
||||
>
|
||||
<div>
|
||||
{whiteBalanceFormatted.length <= 8 && 'AWB: '}
|
||||
{whiteBalanceFormatted}
|
||||
{hasCustomizedWhiteBalance && <>
|
||||
{' '}
|
||||
<span className="text-extra-dim">{'('}</span>
|
||||
R {addSign(whiteBalance?.red ?? 0)}
|
||||
<span className="text-extra-dim">/</span>
|
||||
B {addSign(whiteBalance?.blue ?? 0)}
|
||||
<span className="text-extra-dim">{')'}</span>
|
||||
</>}
|
||||
<div className="uppercase space-y-2">
|
||||
<div className="flex gap-2 *:grow">
|
||||
<div className={clsx(
|
||||
'inline-flex justify-center',
|
||||
'bg-white/25 border border-white/20 rounded-md px-1',
|
||||
)}>
|
||||
DR{dynamicRange ?? 100}
|
||||
</div>
|
||||
<div className={clsx(
|
||||
'inline-flex justify-center',
|
||||
'bg-white/25 border border-white/20 rounded-md px-1',
|
||||
)}>
|
||||
{iso}
|
||||
</div>
|
||||
<div className={clsx(
|
||||
'inline-flex justify-center',
|
||||
'bg-white/25 border border-white/20 rounded-md px-1',
|
||||
)}>
|
||||
{exposure}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 *:w-full">
|
||||
{renderDataSquare(
|
||||
`R${addSign(whiteBalance?.red)} / B${addSign(whiteBalance?.blue)}`,
|
||||
whiteBalanceFormatted,
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2 *:w-full">
|
||||
{renderDataSquare('Highlight', highlight || random.highlight)}
|
||||
@ -118,31 +131,24 @@ export default function PhotoRecipeFrostLight({
|
||||
{renderDataSquare('Color Chrome', colorChromeEffect)}
|
||||
{renderDataSquare('FX Blue', colorChromeFXBlue)}
|
||||
</div>
|
||||
<div>
|
||||
{highISONoiseReduction !== undefined
|
||||
? <>
|
||||
<span className="inline-flex min-w-[130px]">High ISO NR: </span>
|
||||
<span>{addSign(highISONoiseReduction)}</span>
|
||||
</>
|
||||
: <>
|
||||
<span>Noise Reduction: </span>
|
||||
<span>{noiseReductionBasic}</span>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
{grainEffect &&
|
||||
<div>
|
||||
<span className="inline-flex min-w-[130px]">Grain:</span>
|
||||
{grainEffect.roughness}
|
||||
<span className="text-extra-dim">{' / '}</span>
|
||||
{grainEffect.size}
|
||||
<div className="flex gap-2 *:w-full">
|
||||
{renderDataSquare(
|
||||
highISONoiseReduction !== undefined
|
||||
? 'High ISO NR'
|
||||
: 'Noise Reduction',
|
||||
highISONoiseReduction ?? noiseReductionBasic,
|
||||
)}
|
||||
{renderDataSquare(
|
||||
'Grain',
|
||||
// eslint-disable-next-line max-len
|
||||
`${grainEffect.roughness} / ${grainEffect.size === 'large' ? 'LG' : grainEffect.size === 'small' ? 'SM' : 'OFF'}`,
|
||||
)}
|
||||
</div>}
|
||||
{hasBWAdjustments &&
|
||||
<div>
|
||||
<span className="inline-flex min-w-[130px]">BW Adjustment: </span>
|
||||
{addSign(bwAdjustment)}
|
||||
<span className="text-extra-dim">{' / '}</span>
|
||||
MG: {addSign(bwMagentaGreen)}
|
||||
<div className="flex gap-2 *:w-full">
|
||||
{renderDataSquare('BW Adjustment', bwAdjustment)}
|
||||
{renderDataSquare('BW Magenta Green', bwMagentaGreen)}
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
156
src/photo/PhotoRecipeFrostLightV2.tsx
Normal file
156
src/photo/PhotoRecipeFrostLightV2.tsx
Normal file
@ -0,0 +1,156 @@
|
||||
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
|
||||
import { FilmSimulation } from '@/simulation';
|
||||
import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation';
|
||||
import clsx from 'clsx/lite';
|
||||
import { IoCloseCircle } from 'react-icons/io5';
|
||||
|
||||
const addSign = (value = 0) => value < 0 ? value : `+${value}`;
|
||||
|
||||
const getRandomInt = () => {
|
||||
const randomInt = Math.floor(Math.random() * 4) + 1;
|
||||
return Math.random() >= 0.5 ? randomInt : -randomInt;
|
||||
};
|
||||
|
||||
const random = {
|
||||
highlight: getRandomInt(),
|
||||
shadow: getRandomInt(),
|
||||
color: getRandomInt(),
|
||||
sharpness: getRandomInt(),
|
||||
clarity: getRandomInt(),
|
||||
colorChromeEffect: getRandomInt(),
|
||||
colorChromeFXBlue: getRandomInt(),
|
||||
};
|
||||
|
||||
export default function PhotoRecipeFrostLightV2({
|
||||
recipe: {
|
||||
dynamicRange,
|
||||
whiteBalance,
|
||||
highISONoiseReduction,
|
||||
noiseReductionBasic,
|
||||
highlight,
|
||||
shadow,
|
||||
color,
|
||||
sharpness,
|
||||
clarity,
|
||||
colorChromeEffect,
|
||||
colorChromeFXBlue,
|
||||
grainEffect,
|
||||
bwAdjustment,
|
||||
bwMagentaGreen,
|
||||
},
|
||||
simulation,
|
||||
exposure,
|
||||
iso,
|
||||
}: {
|
||||
recipe: FujifilmRecipe
|
||||
simulation: FilmSimulation
|
||||
exposure: string
|
||||
iso: string
|
||||
}) {
|
||||
const whiteBalanceFormatted = (whiteBalance?.type ?? 'auto')
|
||||
.replaceAll('auto', ' ')
|
||||
.replaceAll('-', ' ');
|
||||
|
||||
const hasBWAdjustments =
|
||||
Boolean(bwAdjustment) ||
|
||||
Boolean(bwMagentaGreen);
|
||||
|
||||
const renderDataSquare = (label: string, value: string | number = '0') => (
|
||||
<div className={clsx(
|
||||
'flex flex-col items-center justify-center gap-0.5',
|
||||
'bg-white/25 border border-white/20 rounded-md p-1',
|
||||
)}>
|
||||
<div>{typeof value === 'number' ? addSign(value) : value}</div>
|
||||
<div className={clsx(
|
||||
'text-[10px] leading-none tracking-wide font-medium text-black/50',
|
||||
)}>
|
||||
{label}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return <div className="flex gap-8">
|
||||
<div className={clsx(
|
||||
'w-[17rem] self-start',
|
||||
'p-3',
|
||||
'rounded-lg shadow-2xl',
|
||||
'bg-white/60 backdrop-blur-xl border border-white/30',
|
||||
'space-y-3',
|
||||
'text-[13px] text-black',
|
||||
'saturate-200',
|
||||
)}>
|
||||
<div className="flex items-center gap-2">
|
||||
<PhotoFilmSimulation
|
||||
contrast="frost"
|
||||
className="grow"
|
||||
simulation={simulation}
|
||||
/>
|
||||
<IoCloseCircle
|
||||
size={20}
|
||||
className="text-black/25"
|
||||
/>
|
||||
</div>
|
||||
<div className="uppercase space-y-2">
|
||||
<div className="flex gap-2 *:grow">
|
||||
<div className={clsx(
|
||||
'inline-flex justify-center',
|
||||
'bg-white/25 border border-white/20 rounded-md px-1',
|
||||
)}>
|
||||
DR{dynamicRange ?? 100}
|
||||
</div>
|
||||
<div className={clsx(
|
||||
'inline-flex justify-center',
|
||||
'bg-white/25 border border-white/20 rounded-md px-1',
|
||||
)}>
|
||||
{iso}
|
||||
</div>
|
||||
<div className={clsx(
|
||||
'inline-flex justify-center',
|
||||
'bg-white/25 border border-white/20 rounded-md px-1',
|
||||
)}>
|
||||
{exposure}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 *:w-full">
|
||||
{renderDataSquare(
|
||||
`R${addSign(whiteBalance?.red)} / B${addSign(whiteBalance?.blue)}`,
|
||||
whiteBalanceFormatted,
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2 *:w-full">
|
||||
{renderDataSquare('Highlight', highlight || random.highlight)}
|
||||
{renderDataSquare('Shadow', shadow || random.shadow)}
|
||||
</div>
|
||||
<div className="flex gap-2 *:w-full">
|
||||
{/* TODO: Confirm color vs saturation label */}
|
||||
{renderDataSquare('Color', color || random.color)}
|
||||
{renderDataSquare('Sharpness', sharpness || random.sharpness)}
|
||||
{renderDataSquare('Clarity', clarity || random.clarity)}
|
||||
</div>
|
||||
<div className="flex gap-2 *:w-full">
|
||||
{renderDataSquare('Color Chrome', colorChromeEffect)}
|
||||
{renderDataSquare('FX Blue', colorChromeFXBlue)}
|
||||
</div>
|
||||
{grainEffect &&
|
||||
<div className="flex gap-2 *:w-full">
|
||||
{renderDataSquare(
|
||||
highISONoiseReduction !== undefined
|
||||
? 'High ISO NR'
|
||||
: 'Noise Reduction',
|
||||
highISONoiseReduction ?? noiseReductionBasic,
|
||||
)}
|
||||
{renderDataSquare(
|
||||
'Grain',
|
||||
// eslint-disable-next-line max-len
|
||||
`${grainEffect.roughness} / ${grainEffect.size === 'large' ? 'LG' : grainEffect.size === 'small' ? 'SM' : 'OFF'}`,
|
||||
)}
|
||||
</div>}
|
||||
{hasBWAdjustments &&
|
||||
<div className="flex gap-2 *:w-full">
|
||||
{renderDataSquare('BW Adjustment', bwAdjustment)}
|
||||
{renderDataSquare('BW Magenta Green', bwMagentaGreen)}
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
@ -1,35 +1,24 @@
|
||||
'use client';
|
||||
|
||||
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
|
||||
import { FilmSimulation } from '@/simulation';
|
||||
import clsx from 'clsx/lite';
|
||||
import ImageLarge from '@/components/image/ImageLarge';
|
||||
import PhotoRecipeFrostLight from './PhotoRecipeFrostLight';
|
||||
import FieldSetWithStatus from '@/components/FieldSetWithStatus';
|
||||
import { useState } from 'react';
|
||||
import PhotoRecipe from './PhotoRecipe';
|
||||
import PhotoRecipeFrostLightV2 from './PhotoRecipeFrostLightV2';
|
||||
|
||||
export default function PhotoRecipeOverlay({
|
||||
backgroundImageUrl,
|
||||
recipe,
|
||||
simulation,
|
||||
exposure,
|
||||
iso,
|
||||
}: {
|
||||
backgroundImageUrl: string
|
||||
recipe: FujifilmRecipe
|
||||
simulation: FilmSimulation
|
||||
exposure: string
|
||||
iso: string
|
||||
}) {
|
||||
const [isFrosted, setIsFrosted] = useState(true);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<FieldSetWithStatus
|
||||
id="is-frosted"
|
||||
type="checkbox"
|
||||
label="Frosted"
|
||||
value={isFrosted ? 'true' : 'false'}
|
||||
onChange={() => setIsFrosted(!isFrosted)}
|
||||
/>
|
||||
</div>
|
||||
<div className={clsx(
|
||||
'relative w-full aspect-[3/2]',
|
||||
)}>
|
||||
@ -42,14 +31,12 @@ export default function PhotoRecipeOverlay({
|
||||
'absolute inset-0',
|
||||
'flex items-center justify-center',
|
||||
)}>
|
||||
{isFrosted
|
||||
? <PhotoRecipeFrostLight
|
||||
recipe={recipe}
|
||||
simulation={simulation}
|
||||
/> : <PhotoRecipe
|
||||
recipe={recipe}
|
||||
simulation={simulation}
|
||||
/>}
|
||||
<PhotoRecipeFrostLightV2 {...{
|
||||
recipe,
|
||||
simulation,
|
||||
exposure,
|
||||
iso,
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user