한국 경기 한국 좌측 고정, 국가명 한글 통일

develop
jwkim 2026-06-22 10:21:13 +09:00
parent 8580bbb713
commit 90d00a817b
5 changed files with 111 additions and 38 deletions

View File

@ -54,6 +54,10 @@ export default function Arena({
const t = dict(lang);
const aShort = teamShort(match.teamA, lang);
const bShort = teamShort(match.teamB, lang);
// 한국을 항상 왼쪽에 표시(요청). 상태·투표 제출은 원본 A/B 프레임 그대로, 화면 좌우만 교체.
const flip = match.teamB.code === "KOR" && match.teamA.code !== "KOR";
const leftShort = flip ? bShort : aShort;
const rightShort = flip ? aShort : bShort;
const [outcome, setOutcome] = useState<Outcome | null>("DRAW");
const [scoreA, setScoreA] = useState(0);
const [scoreB, setScoreB] = useState(0);
@ -135,7 +139,7 @@ export default function Arena({
const shareText = useMemo(() => {
if (!outcome) return "";
const me = `${aShort} ${scoreA}-${scoreB} ${bShort}`;
const me = `${leftShort} ${flip ? scoreB : scoreA}-${flip ? scoreA : scoreB} ${rightShort}`;
const sameAI = matched.map((m) => m.model).join("·");
if (lang === "en") {
return sameAI
@ -217,7 +221,7 @@ export default function Arena({
</div>
</div>
<div className="ml-auto shrink-0 whitespace-nowrap font-mono text-[28px] font-extrabold tabular-nums">
{p.scoreA}&nbsp;-&nbsp;{p.scoreB}
{flip ? p.scoreB : p.scoreA}&nbsp;-&nbsp;{flip ? p.scoreA : p.scoreB}
</div>
</div>
@ -240,11 +244,17 @@ export default function Arena({
<div className="mt-3 grid grid-cols-3 gap-2">
{(
[
["TEAM_A_WIN", `${aShort} ${t.win}`],
["DRAW", t.drawLabel],
["TEAM_B_WIN", `${bShort} ${t.win}`],
] as [Outcome, string][]
(flip
? [
["TEAM_B_WIN", `${bShort} ${t.win}`],
["DRAW", t.drawLabel],
["TEAM_A_WIN", `${aShort} ${t.win}`],
]
: [
["TEAM_A_WIN", `${aShort} ${t.win}`],
["DRAW", t.drawLabel],
["TEAM_B_WIN", `${bShort} ${t.win}`],
]) as [Outcome, string][]
).map(([val, label]) => (
<button
key={val}
@ -262,9 +272,19 @@ export default function Arena({
</div>
<div className="mt-3 flex items-center justify-center gap-5 rounded-xl border border-[var(--line-l)] bg-white py-3">
<Stepper label={aShort} value={scoreA} onChange={setScoreA} disabled={disabled} />
<span className="text-[26px] font-extrabold text-[var(--ink-muted)]">:</span>
<Stepper label={bShort} value={scoreB} onChange={setScoreB} disabled={disabled} />
{flip ? (
<>
<Stepper label={bShort} value={scoreB} onChange={setScoreB} disabled={disabled} />
<span className="text-[26px] font-extrabold text-[var(--ink-muted)]">:</span>
<Stepper label={aShort} value={scoreA} onChange={setScoreA} disabled={disabled} />
</>
) : (
<>
<Stepper label={aShort} value={scoreA} onChange={setScoreA} disabled={disabled} />
<span className="text-[26px] font-extrabold text-[var(--ink-muted)]">:</span>
<Stepper label={bShort} value={scoreB} onChange={setScoreB} disabled={disabled} />
</>
)}
</div>
</section>
@ -273,7 +293,7 @@ export default function Arena({
<section className="mt-5 rounded-2xl border border-[var(--green)]/50 bg-[var(--bg2)] p-5">
<div className="text-[13px] text-[var(--ink-muted)]">{t.finalResult}</div>
<div className="mt-1 text-[26px] font-extrabold">
{aShort} {match.result.scoreA}-{match.result.scoreB} {bShort}
{leftShort} {flip ? match.result.scoreB : match.result.scoreA}-{flip ? match.result.scoreA : match.result.scoreB} {rightShort}
<span className="ml-2 text-[16px] font-bold text-[var(--green)]">
{outcomeLabel(match, match.result.outcome, lang)}
</span>
@ -361,7 +381,7 @@ export default function Arena({
<div className="mt-5 rounded-2xl border border-[var(--green)]/50 bg-[var(--bg2)] p-5">
<div className="text-[13px] text-[var(--ink-muted)]">{t.myPick}</div>
<div className="mt-1 text-[26px] font-extrabold">
{aShort} {scoreA}-{scoreB} {bShort}
{leftShort} {flip ? scoreB : scoreA}-{flip ? scoreA : scoreB} {rightShort}
<span className="ml-2 text-[16px] font-bold text-[var(--green)]">
{outcomeLabel(match, outcome, lang)}
</span>
@ -397,8 +417,11 @@ export default function Arena({
<ul className="flex flex-col gap-2">
{(votesExpanded ? myPreds : myPreds.slice(0, 1)).map((p) => {
const isThis = p.matchId === match.matchId;
const pFlip = p.teamB.code === "KOR" && p.teamA.code !== "KOR";
const pA = teamShort(p.teamA, lang);
const pB = teamShort(p.teamB, lang);
const pLeft = pFlip ? pB : pA;
const pRight = pFlip ? pA : pB;
const pickTxt =
p.outcome === "TEAM_A_WIN"
? `${pA} ${t.win}`
@ -416,7 +439,7 @@ export default function Arena({
>
<div className="min-w-0">
<div className="text-[15px] font-bold text-white">
{pA} {p.scoreA}-{p.scoreB} {pB}
{pLeft} {pFlip ? p.scoreB : p.scoreA}-{pFlip ? p.scoreA : p.scoreB} {pRight}
</div>
<div className="mt-0.5 text-[12px] text-[var(--ink-muted)]">
{pickTxt}
@ -546,7 +569,13 @@ export default function Arena({
<div className="flex h-14 overflow-hidden rounded-2xl border border-[var(--line-d)]">
{(() => {
const [a, d, b] = pctParts([crowd.teamAWin, crowd.draw, crowd.teamBWin]);
return (
return flip ? (
<>
<CrowdSeg team={match.teamB} value={b} tone="a" />
<CrowdSeg value={d} tone="draw" />
<CrowdSeg team={match.teamA} value={a} tone="b" />
</>
) : (
<>
<CrowdSeg team={match.teamA} value={a} tone="a" />
<CrowdSeg value={d} tone="draw" />

View File

@ -1,4 +1,4 @@
import { Link } from "react-router-dom";
import { Link, useNavigate } from "react-router-dom";
import ShareButton from "./ShareButton";
import LangSwitch from "./LangSwitch";
import { type Lang, dict } from "@/lib/i18n";
@ -16,13 +16,23 @@ export default function Hero({
lang?: Lang;
}) {
const t = dict(lang);
const navigate = useNavigate();
const home = lang === "en" ? "/?lang=en" : "/";
// 뒤로가기: 앱 내 이력이 있으면 직전 페이지로 그대로 복귀(전체 일정의 선택 날짜 보존),
// 없으면(공유 링크로 직접 진입 등) 전체 일정으로 이동.
const goBack = (e: React.MouseEvent) => {
e.preventDefault();
const idx = (window.history.state?.idx as number | undefined) ?? 0;
if (idx > 0) navigate(-1);
else navigate(home);
};
return (
<header className="pt-5 text-center">
{back && (
<div className="mb-3 flex items-center justify-between">
<Link
to={home}
onClick={goBack}
className="flex items-center gap-1.5 rounded-full border border-white/12 bg-white/8 px-[18px] py-[9px] text-[18px] font-bold text-white/85 active:scale-95"
>
{t.back}

View File

@ -1,6 +1,6 @@
import type { Match } from "@/lib/types";
import { kickoffDisplay } from "@/lib/format";
import { type Lang, dict } from "@/lib/i18n";
import { type Lang, dict, teamShort } from "@/lib/i18n";
import BallIcon from "./BallIcon";
import TeamFlag from "./TeamFlag";
@ -8,6 +8,12 @@ export default function MatchupHUD({ match, lang = "ko" }: { match: Match; lang?
const t = dict(lang);
const { group } = match;
const finished = !!match.result;
// 한국을 항상 왼쪽에 표시(요청). 데이터/투표는 원본 A/B 프레임 유지, 화면 좌우만 교체.
const flip = match.teamB.code === "KOR" && match.teamA.code !== "KOR";
const left = flip ? match.teamB : match.teamA;
const right = flip ? match.teamA : match.teamB;
const leftScore = flip ? match.result?.scoreB : match.result?.scoreA;
const rightScore = flip ? match.result?.scoreA : match.result?.scoreB;
return (
<section className="mt-6">
{/* 대결 카드 (녹색 글로우 보더) */}
@ -20,8 +26,8 @@ export default function MatchupHUD({ match, lang = "ko" }: { match: Match; lang?
<BallIcon className="h-14 w-14 shrink-0" />
<div className="min-w-0">
<div className="text-[22px] font-extrabold leading-tight">
{match.teamA.name} <span className="text-white/55">vs</span>{" "}
{match.teamB.name}
{teamShort(left, lang)} <span className="text-white/55">vs</span>{" "}
{teamShort(right, lang)}
</div>
<div className="mt-1 text-[15px] font-bold text-[var(--green)]">
{kickoffDisplay(match.kickoffKst, lang)} · {t.group(group)}
@ -32,14 +38,14 @@ export default function MatchupHUD({ match, lang = "ko" }: { match: Match; lang?
{/* 국기 + 라이트닝 VS (종료 시 최종 스코어) */}
<div className="mt-5 grid grid-cols-[1fr_auto_1fr] items-center gap-3">
<TeamFlag team={match.teamA} className="mx-auto h-[68px] w-[104px]" />
<TeamFlag team={left} className="mx-auto h-[68px] w-[104px]" />
<div className="relative grid place-items-center">
<div className="vs-glow absolute h-28 w-28" />
{finished && match.result ? (
<span className="relative whitespace-nowrap font-mono text-[34px] font-extrabold tabular-nums text-white">
{match.result.scoreA}
{leftScore}
<span className="px-1 text-white/55">-</span>
{match.result.scoreB}
{rightScore}
</span>
) : (
<span
@ -50,7 +56,7 @@ export default function MatchupHUD({ match, lang = "ko" }: { match: Match; lang?
</span>
)}
</div>
<TeamFlag team={match.teamB} className="mx-auto h-[68px] w-[104px]" />
<TeamFlag team={right} className="mx-auto h-[68px] w-[104px]" />
</div>
{finished && (

View File

@ -32,7 +32,9 @@ export default function ScheduleBoard({
lang?: Lang;
}) {
const t = dict(lang);
const [tab, setTab] = useState<Tab>("date");
const [tab, setTab] = useState<Tab>(() =>
sessionStorage.getItem("tp.sched.tab") === "group" ? "group" : "date",
);
// 날짜·조 목록 (정렬)
const dates = useMemo(
@ -44,14 +46,30 @@ export default function ScheduleBoard({
[matches],
);
// 기본 선택: 오늘(없으면 첫 날짜) / A조
// 기본 선택: 오늘(없으면 첫 날짜) / A조.
// 단, 직전 세션 선택을 sessionStorage 에 보관 → 상세 진입 후 뒤로가기 시 그대로 복원(오늘로 초기화 X).
const todayKey = useMemo(() => new Date().toISOString().slice(0, 10), []);
const [selDate, setSelDate] = useState(
() => (dates.includes(todayKey) ? todayKey : dates[0]) ?? "",
);
const [selGroup, setSelGroup] = useState(
() => (groups.includes("A") ? "A" : groups[0]) ?? "",
);
const [selDate, setSelDate] = useState(() => {
const saved = sessionStorage.getItem("tp.sched.date");
if (saved && dates.includes(saved)) return saved;
return (dates.includes(todayKey) ? todayKey : dates[0]) ?? "";
});
const [selGroup, setSelGroup] = useState(() => {
const saved = sessionStorage.getItem("tp.sched.group");
if (saved && groups.includes(saved)) return saved;
return (groups.includes("A") ? "A" : groups[0]) ?? "";
});
// 선택 변경 시 보관(다음 진입/뒤로가기에서 복원)
useEffect(() => {
if (selDate) sessionStorage.setItem("tp.sched.date", selDate);
}, [selDate]);
useEffect(() => {
if (selGroup) sessionStorage.setItem("tp.sched.group", selGroup);
}, [selGroup]);
useEffect(() => {
sessionStorage.setItem("tp.sched.tab", tab);
}, [tab]);
const shown = useMemo(() => {
const list =
@ -234,6 +252,12 @@ function MatchCard({ match, lang }: { match: Match; lang: Lang }) {
const t = dict(lang);
const phase = match.phase;
const finished = !!match.result;
// 한국을 항상 왼쪽에 표시(상세 페이지와 동일 규칙). 데이터는 원본 A/B 유지, 화면 좌우만 교체.
const flip = match.teamB.code === "KOR" && match.teamA.code !== "KOR";
const left = flip ? match.teamB : match.teamA;
const right = flip ? match.teamA : match.teamB;
const leftScore = flip ? match.result?.scoreB : match.result?.scoreA;
const rightScore = flip ? match.result?.scoreA : match.result?.scoreB;
// 비투표(타 조)는 클릭 비활성, 투표 가능 조(A)는 상세로 이동
const inner = (
@ -255,21 +279,21 @@ function MatchCard({ match, lang }: { match: Match; lang: Lang }) {
<div className="mt-3 grid grid-cols-[1fr_auto_1fr] items-center gap-2">
<div className="flex items-center gap-2">
<TeamFlag team={match.teamA} className="h-6 w-9 shrink-0" />
<span className="truncate text-[15px] font-extrabold">{teamShort(match.teamA, lang)}</span>
<TeamFlag team={left} className="h-6 w-9 shrink-0" />
<span className="truncate text-[15px] font-extrabold">{teamShort(left, lang)}</span>
</div>
{match.result ? (
<span className="whitespace-nowrap font-mono text-[18px] font-extrabold tabular-nums text-white">
{match.result.scoreA}
{leftScore}
<span className="px-1.5 text-white/40">:</span>
{match.result.scoreB}
{rightScore}
</span>
) : (
<span className="font-impact text-[18px] italic text-[#94FBE0]">VS</span>
)}
<div className="flex items-center justify-end gap-2">
<span className="truncate text-right text-[15px] font-extrabold">{teamShort(match.teamB, lang)}</span>
<TeamFlag team={match.teamB} className="h-6 w-9 shrink-0" />
<span className="truncate text-right text-[15px] font-extrabold">{teamShort(right, lang)}</span>
<TeamFlag team={right} className="h-6 w-9 shrink-0" />
</div>
</div>

View File

@ -57,7 +57,11 @@ export default function MatchDetail() {
const aShort = teamShort(match.teamA, lang);
const bShort = teamShort(match.teamB, lang);
const hook = lang === "en" ? t.hook(aShort, bShort) : match.hookText;
// 한국을 항상 왼쪽에 표시(요청)와 동일하게 공유 제목/후킹 카피도 한국 먼저.
const flip = match.teamB.code === "KOR" && match.teamA.code !== "KOR";
const leftShort = flip ? bShort : aShort;
const rightShort = flip ? aShort : bShort;
const hook = lang === "en" ? t.hook(leftShort, rightShort) : match.hookText;
const url = matchUrl(match.matchId);
return (
@ -67,7 +71,7 @@ export default function MatchDetail() {
lang={lang}
share={{
url,
title: `${aShort} vs ${bShort} — TriplePick`,
title: `${leftShort} vs ${rightShort} — TriplePick`,
text:
lang === "en"
? `${hook} — see all 3 AI picks and make yours!`