web: the preset tester steps through every frame in its slot

This commit is contained in:
2026-09-18 21:02:30 +07:00
parent d3c5c47973
commit 2da045f232
2 changed files with 60 additions and 2 deletions
+37 -2
View File
@@ -162,6 +162,7 @@ export function Landing() {
const tools = useRef<HTMLSpanElement>(null);
const account = useRef<HTMLSpanElement>(null);
const [presetId, setPresetId] = useState(PRESETS[0].id);
const [testerIdx, setTesterIdx] = useState(0);
const [temp, setTemp] = useState(0);
const [sat, setSat] = useState(100);
const [con, setCon] = useState(100);
@@ -254,9 +255,17 @@ export function Landing() {
// The QR card's payload is the curated photo's own look, as the link the
// phone app downloads it from. The card's mock address stands in while the
// section holds nothing that has a look to hand out.
// The tester cycles every still the curator put in its slot, so a recipe
// can be checked against more than one frame. The built-in sample stands
// in on its own while the slot holds nothing.
const curated = photos.filter((p) => p.slots.includes('tester')).map((p) => api.photoUrl(p.id));
const testers = curated.length ? curated : [SAMPLE(1043)];
const qr = pick('qr');
return {
tester: src('tester', SAMPLE(1043)),
testers,
// A plain step count on top of one random start keeps the frame random
// per load without an effect that would reset it on every re-render.
testerStart: Math.floor(Math.random() * testers.length),
creator: src('creator', SAMPLE(1015)),
qr: qr ? api.photoUrl(qr.id) : SAMPLE(1069),
qrLink: qr?.hasPreset ? new URL(api.presetUrl(qr.id), window.location.origin).href : `${window.location.origin}/app`,
@@ -622,7 +631,33 @@ export function Landing() {
))}
</div>
<div className="lp-view">
<Shot src={slotPhoto.tester} alt={`${preset.name} preview`} filter={preset.filter} />
<Shot
src={slotPhoto.testers[(slotPhoto.testerStart + testerIdx) % slotPhoto.testers.length]}
alt={`${preset.name} preview`}
filter={preset.filter}
/>
{slotPhoto.testers.length > 1 && (
<>
<button
type="button"
className="lp-arrow lp-arrow-prev"
data-key="lp-tester-prev"
aria-label={c({ en: 'Previous frame', vi: 'Khung trước' })}
onClick={() => setTesterIdx((i) => (i - 1 + slotPhoto.testers.length) % slotPhoto.testers.length)}
>
{'\u2039'}
</button>
<button
type="button"
className="lp-arrow lp-arrow-next"
data-key="lp-tester-next"
aria-label={c({ en: 'Next frame', vi: 'Khung sau' })}
onClick={() => setTesterIdx((i) => (i + 1) % slotPhoto.testers.length)}
>
{'\u203a'}
</button>
</>
)}
<div className="lp-hud">
<span>f/<b>1.8</b></span>
<span>SS <b>1/500s</b></span>
+23
View File
@@ -343,6 +343,29 @@ html { scroll-behavior: smooth; scroll-padding-top: 116px; }
.lp-tab[aria-pressed="true"] { border-color: var(--lp-amber); color: var(--lp-ink-amber); background: var(--accent-soft); }
.lp-view { position: relative; border-radius: 16px; overflow: hidden; border: 1px solid var(--lp-line); background: #000; }
.lp-view img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block; transition: filter 380ms ease; }
.lp-arrow {
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 2;
width: 38px;
height: 38px;
display: grid;
place-items: center;
padding: 0;
border-radius: 50%;
border: 1px solid var(--lp-line);
background: rgba(0, 0, 0, 0.55);
color: var(--lp-text);
font: inherit;
font-size: 22px;
line-height: 1;
cursor: pointer;
transition: background 160ms ease, border-color 160ms ease, color 160ms ease;
}
.lp-arrow:hover, .lp-arrow:focus-visible { background: rgba(0, 0, 0, 0.85); border-color: var(--lp-amber); color: var(--lp-amber); }
.lp-arrow-prev { left: 10px; }
.lp-arrow-next { right: 10px; }
.lp-hud {
position: absolute;
inset: auto 0 0 0;