diff --git a/docker/frontend/src/Landing.tsx b/docker/frontend/src/Landing.tsx index 5aa7279..afa4f12 100644 --- a/docker/frontend/src/Landing.tsx +++ b/docker/frontend/src/Landing.tsx @@ -162,6 +162,7 @@ export function Landing() { const tools = useRef(null); const account = useRef(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() { ))}
- + + {slotPhoto.testers.length > 1 && ( + <> + + + + )}
f/1.8 SS 1/500s diff --git a/docker/frontend/src/styles/landing.css b/docker/frontend/src/styles/landing.css index f5f2e04..18a7593 100644 --- a/docker/frontend/src/styles/landing.css +++ b/docker/frontend/src/styles/landing.css @@ -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;