From 00d476a0e51629f06407aed035fbc001d3f6b114 Mon Sep 17 00:00:00 2001 From: Pinapelz Date: Fri, 26 Jun 2026 21:55:46 -0700 Subject: initial guess MV mode --- src/components/MVPlayer/index.styled.ts | 73 ++++++++++++++++++++++++++++ src/components/MVPlayer/index.tsx | 85 +++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 src/components/MVPlayer/index.styled.ts create mode 100644 src/components/MVPlayer/index.tsx (limited to 'src/components/MVPlayer') diff --git a/src/components/MVPlayer/index.styled.ts b/src/components/MVPlayer/index.styled.ts new file mode 100644 index 0000000..ca8a06e --- /dev/null +++ b/src/components/MVPlayer/index.styled.ts @@ -0,0 +1,73 @@ +import styled from "styled-components"; + +export const Frame = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + margin: 16px 0; +`; + +export const Images = styled.div` + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 8px; + width: 100%; +`; + +export const ImageSlot = styled.div<{ $revealed: boolean }>` + position: relative; + width: 200px; + height: 113px; + background-color: var(--cl-gray-2); + border: 1px solid ${({ theme }) => theme.border}; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + + img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + } +`; + +export const Placeholder = styled.p` + margin: 0; + font-family: "Roboto Mono", monospace; + font-size: 0.7rem; + color: var(--cl-gray-5); + text-align: center; + padding: 0 8px; +`; + +export const Hint = styled.p` + margin: 0; + font-family: "Roboto Mono", monospace; + font-size: 0.75rem; + color: var(--cl-gray-6); +`; + +export const Overlay = styled.div` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.85); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + cursor: zoom-out; +`; + +export const LightboxImage = styled.img` + max-width: 90vw; + max-height: 90vh; + object-fit: contain; + cursor: default; +`; diff --git a/src/components/MVPlayer/index.tsx b/src/components/MVPlayer/index.tsx new file mode 100644 index 0000000..a1141ab --- /dev/null +++ b/src/components/MVPlayer/index.tsx @@ -0,0 +1,85 @@ +import React from "react"; +import * as Styled from "./index.styled"; + +interface Props { + currentTry: number; + date: string; +} + +const MV_CDN_URL = import.meta.env.VITE_CDN_URL || ""; + +function getRevealedCount(currentTry: number): number { + if (currentTry >= 6) return 3; + if (currentTry >= 3) return 2; + return 1; +} + +export function MVPlayer({ currentTry, date }: Props) { + const revealed = getRevealedCount(currentTry); + const [errored, setErrored] = React.useState>(new Set()); + + const [activeImage, setActiveImage] = React.useState(null); + + const markError = React.useCallback((index: number) => { + setErrored((prev) => { + const next = new Set(prev); + next.add(index); + return next; + }); + }, []); + + React.useEffect(() => { + setErrored(new Set()); + setActiveImage(null); + }, [date]); + + return ( + + + {Array.from({ length: 3 }, (_, i) => { + const index = i + 1; + const isRevealed = index <= revealed; + const src = `${MV_CDN_URL}/k-heardle-mvs/${date}/${index}.jpg`; + const hasError = errored.has(index); + + return ( + + {isRevealed && !hasError ? ( + {`MV markError(index)} + onClick={() => setActiveImage(src)} + style={{ cursor: "zoom-in" }} + /> + ) : isRevealed && hasError ? ( + + Frame {index} not available yet. + + ) : ( + ? + )} + + ); + })} + + + + {revealed < 3 + ? `More frames unlock as you guess. (${revealed}/3 revealed)` + : "All frames revealed."} + + + {/* Lightbox */} + {activeImage && ( + setActiveImage(null)}> + e.stopPropagation()} + /> + + )} + + ); +} -- cgit v1.2.3