From 44e968db3a6d7c2a395a36ea80fec639961090d6 Mon Sep 17 00:00:00 2001 From: Minseok Chae <154256470+Min0504@users.noreply.github.com> Date: Sun, 9 Aug 2026 22:14:29 +0900 Subject: [PATCH 1/4] fix: localize results for UI locale and harden public surfaces Keep comparison tables and SEO/session results aligned with the active language, highlight only the better bar value, and tighten rate limits, admin auth, and CSP before ship. Co-authored-by: Cursor --- AGENTS.md | 93 +- app/api/admin/extract/route.ts | 6 +- app/api/compare/route.ts | 4 +- app/api/price/route.ts | 4 +- app/api/share/guest/route.ts | 16 +- app/api/track/route.ts | 38 +- app/api/watches/route.ts | 12 +- app/compare/[slug]/page.tsx | 49 +- app/compare/page.tsx | 7 +- app/globals.css | 4389 +---------------------------- app/page.tsx | 8 - app/styles/auth.css | 796 ++++++ app/styles/commerce.css | 1967 +++++++++++++ app/styles/home.css | 664 +++++ app/styles/results.css | 915 ++++++ components/results-view.tsx | 59 +- components/session-results.tsx | 69 +- components/settings-bar.tsx | 1 - components/spec-graphs.tsx | 12 +- lib/admin-auth.ts | 27 + lib/ai/axis-prompt.ts | 148 +- lib/ai/decide.ts | 6 +- lib/comparison-cache.ts | 2 +- lib/decision-engine.ts | 28 +- lib/i18n/en.ts | 55 +- lib/i18n/ja.ts | 55 +- lib/i18n/ko.ts | 54 +- lib/rate-limit.ts | 92 +- lib/specs/dataset/laptops.ts | 10 +- lib/specs/extract/url-patterns.ts | 7 +- lib/specs/extracted-table.ts | 4 +- lib/specs/localize-value.ts | 239 ++ next.config.ts | 44 +- tests/admin-auth.test.ts | 48 + tests/axis-prompt.test.ts | 50 +- tests/dataset.test.ts | 6 + tests/i18n.test.ts | 86 +- tests/localize-value.test.ts | 57 + tests/rate-limit.test.ts | 14 +- 39 files changed, 5342 insertions(+), 4799 deletions(-) create mode 100644 app/styles/auth.css create mode 100644 app/styles/commerce.css create mode 100644 app/styles/home.css create mode 100644 app/styles/results.css create mode 100644 lib/admin-auth.ts create mode 100644 lib/specs/localize-value.ts create mode 100644 tests/admin-auth.test.ts create mode 100644 tests/localize-value.test.ts diff --git a/AGENTS.md b/AGENTS.md index 8389434..408235a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,93 +7,10 @@ - DB: Supabase. - PM만 최종 승인과 배포를 한다. -## Conductor 역할 흐름 - -1. PM이 Codex FE/BE에게 작업 지시. -2. Codex FE와 Codex BE가 각자 영역에서 구현, todo 관리, lint/typecheck/test/build 실행. -3. FE/BE는 각자 작업 결과를 종합해 수정 파일, 실행 명령, 실패/남은 이슈를 역할 노트에 기록한다. -4. PM이 "검증 단계로 넘겨" 또는 "검증" 지시 전까지 Codex Security는 개입하지 않는다. -5. Security가 1차 검증을 수행한다: 전체 diff, FE/BE 연결, 보안, 권한, 타입, 테스트, 런타임 위험. -6. Security는 발견한 문제를 Security 선에서 전부 수정하고, 수정 내용과 남은 리스크를 `security-report.md`에 기록한다. -7. Security 완료 후 Claude Lead가 2차 검증과 최종 종합을 수행한다: security-report, diff, test 결과, 구조 리스크, 남은 이슈, 다음 작업, merge 가능 여부. - -## 파일 경계 - -### Codex FE - -담당: -- UI/UX, 컴포넌트, 화면, 스타일, 반응형, 클라이언트 상태, API 연결부, 프론트 테스트 - -금지: -- `api/`, `server/`, `src/server/`, `supabase/`, `data/`, `engine/`, `config/`, `lib/specs/`, `lib/pricing/`, `lib/ai/` 직접 수정 -- API 스펙 임의 변경 -- DB schema/migration/auth 임의 변경 - -### Codex BE - -담당: -- API, 서버 로직, `lib/specs/`, `lib/pricing/`, `lib/ai/`, DB 연결, migration, auth, permission, validation, backend security, backend test - -금지: -- `components/`, `pages/`, `screens/`, `ui/`, `styles/`, 프론트 화면 파일 직접 수정 -- 응답 형식 임의 변경 -- package/lockfile 임의 변경 - -### Codex Security - -- PM 검증 지시 후에만 실행. -- 검증: FE/BE API 불일치, request/response 타입, auth/session, permission 우회, DB/migration, secret/env 노출, webhook/결제/파일업로드, lint/typecheck/test/build, 런타임 에러, 파일 범위 침범. -- 발견한 보안/타입/설정/구조 문제는 Security 선에서 전부 수정한다. -- 수정한 내용, 수정하지 못한 리스크, 검증 결과를 `.context/security-report.md`에 기록한다. - -### Claude Lead - -- Security 통과 뒤 마지막에만 실행. -- 담당: 2차 검증, 최종 종합, 큰 구조 리뷰, 변경 요약, 남은 이슈, 다음 task 설계, merge 가능 여부 판단. -- 2차 검증에서는 `security-report.md`, FE/BE 노트, 전체 diff, lint/typecheck/test/build 결과, 파일 범위 침범 여부를 다시 확인한다. -- 코드 대량 수정 금지. - -## 기록 위치 - -- `.context/handoff.md` -- `.context/fe-notes.md` -- `.context/be-notes.md` -- `.context/security-report.md` -- 필요 시 `docs/inbox/frontend.md`, `docs/inbox/backend.md`, `docs/inbox/security.md`, `docs/inbox/lead.md` - -## 보고 형식 - -토큰 절약을 위해 모든 역할은 기본 보고를 아래 형식으로 끝낸다. - -```text -한것: file1, file2 -검증: lint ✅ type ✅ test ✅ build ✅ -남은것: 없음 -``` - -문제 있을 때만 짧게 이유를 붙인다. - -```text -한것: file1 -검증: test ❌ — 핵심 이유 -남은것: 해야 할 일 -``` - -Lead 최종 보고는 아래 형식을 쓴다. - -```text -판정: merge 가능/불가 -한것: 핵심 3줄 이하 -검증: Security ✅ Lead ✅ -남은것: 없음/항목 -``` - -긴 설명, 전체 파일 출력, 장문 로그 붙여넣기는 금지한다. 상세 판단은 Conductor diff/test 결과로 한다. - ## 공통 규칙 -- 기본은 자율 수행이다. 역할 범위 안 코드 수정, 검증 명령 실행, 문서/.context 기록, 작은 설정 수정은 PM 승인 없이 진행한다. -- 단, 아래 항목은 반드시 PM 승인 후 진행한다: +- 시크릿/앱키를 클라이언트 번들에 넣지 않는다. +- 아래 항목은 PM 승인 후 진행한다: - 개인정보/시크릿/env 값 생성·수정·노출 - DB schema/rules/migration 실제 적용 - 인증 방식 변경 @@ -104,10 +21,4 @@ Lead 최종 보고는 아래 형식을 쓴다. - git push/merge/delete branch - 대량 삭제 - 프로젝트 밖 파일 수정 -- package.json/lockfile 변경은 PM 승인 후. -- DB schema/migration 변경은 PM 승인 후. -- 인증 방식 변경은 PM 승인 후. - 배포 실행은 PM만. -- 시크릿/앱키를 클라이언트 번들에 넣지 않는다. -- 실패한 명령은 숨기지 않고 역할 노트에 기록한다. -- 긴 전체 파일 출력 금지. diff, 테스트 결과, 수정 파일 목록 중심으로 보고한다. diff --git a/app/api/admin/extract/route.ts b/app/api/admin/extract/route.ts index 8787e6a..d1454c5 100644 --- a/app/api/admin/extract/route.ts +++ b/app/api/admin/extract/route.ts @@ -1,4 +1,5 @@ import { NextResponse } from "next/server"; +import { isAdminRequest } from "@/lib/admin-auth"; import { isAiConfigured } from "@/lib/ai/decide"; import { getProductById, allVerifiedProducts } from "@/lib/specs/dataset"; import { discoverOfficialUrl } from "@/lib/specs/extract/discover"; @@ -15,16 +16,17 @@ import type { ProductSourceCandidate } from "@/lib/specs/types"; * they're committed to the verified store. * * Safety: - * - Gated behind AXIS_ADMIN=1 (off in production by default). + * - Gated behind AXIS_ADMIN=1 + AXIS_ADMIN_TOKEN (Bearer) in production. * - SSRF-safe: only fetches the `source` URL already stored in our catalog; * the caller supplies an `id`, never a URL. * - Returns { result: null } when no AI key is configured (honest no-op). * * Usage: GET /api/admin/extract?id=macbook-air-13-m3 * GET /api/admin/extract → lists available ids + * Header: Authorization: Bearer */ export async function GET(req: Request) { - if (process.env.AXIS_ADMIN !== "1") { + if (!isAdminRequest(req)) { return NextResponse.json({ error: "not found" }, { status: 404 }); } diff --git a/app/api/compare/route.ts b/app/api/compare/route.ts index 15c9d9f..f887592 100644 --- a/app/api/compare/route.ts +++ b/app/api/compare/route.ts @@ -2,7 +2,7 @@ import { NextResponse } from "next/server"; import { buildDecision, buildQuery, parseOptions } from "@/lib/decision-engine"; import { createSupabaseRouteClient } from "@/lib/supabase-route"; import { ensureUserProfile } from "@/lib/users/ensure-profile"; -import { getClientIp, rateLimit } from "@/lib/rate-limit"; +import { checkRateLimit, getClientIp } from "@/lib/rate-limit"; import { COUNTRY_COOKIE, LOCALE_COOKIE, countryForLocale, isCountry, isLocale } from "@/lib/i18n"; import type { ComparisonResult } from "@/lib/types"; @@ -34,7 +34,7 @@ function collectOptions(body: Body): string[] { export async function POST(req: Request) { const ip = getClientIp(req); - const limit = rateLimit(`compare:${ip}`, RATE_LIMIT, RATE_WINDOW_MS); + const limit = await checkRateLimit(`compare:${ip}`, RATE_LIMIT, RATE_WINDOW_MS); if (!limit.allowed) { const retryAfter = Math.ceil((limit.resetAt - Date.now()) / 1000); return NextResponse.json( diff --git a/app/api/price/route.ts b/app/api/price/route.ts index 5e8d24d..248cda1 100644 --- a/app/api/price/route.ts +++ b/app/api/price/route.ts @@ -8,7 +8,7 @@ import { type Region } from "@/lib/pricing"; import { isLocale, type Locale } from "@/lib/i18n"; -import { getClientIp, rateLimit } from "@/lib/rate-limit"; +import { checkRateLimit, getClientIp } from "@/lib/rate-limit"; export type PriceApiResult = { productId: string; @@ -34,7 +34,7 @@ export type PriceApiResult = { */ export async function GET(req: Request) { const ip = getClientIp(req); - const limit = rateLimit(`price:${ip}`, 60, 60_000); + const limit = await checkRateLimit(`price:${ip}`, 60, 60_000); if (!limit.allowed) { const retryAfter = Math.ceil((limit.resetAt - Date.now()) / 1000); return NextResponse.json( diff --git a/app/api/share/guest/route.ts b/app/api/share/guest/route.ts index 9e789a7..7ed5993 100644 --- a/app/api/share/guest/route.ts +++ b/app/api/share/guest/route.ts @@ -1,7 +1,7 @@ import { randomBytes } from "node:crypto"; import { NextResponse } from "next/server"; import { createSupabaseRouteClient } from "@/lib/supabase-route"; -import { getClientIp, rateLimit } from "@/lib/rate-limit"; +import { checkRateLimit, getClientIp } from "@/lib/rate-limit"; import type { ComparisonResult } from "@/lib/types"; /** 22-char URL-safe token (~131 bits) — harder to enumerate than 10-char charset. */ @@ -9,6 +9,9 @@ function generateToken() { return randomBytes(16).toString("base64url").slice(0, 22); } +/** Hard cap on serialized result size to blunt guest-share abuse. */ +const MAX_RESULT_BYTES = 48_000; + /** * Guest share endpoint: accepts a result payload directly and stores it * as a public anonymous comparison so the link can be shared without login. @@ -16,7 +19,7 @@ function generateToken() { */ export async function POST(req: Request) { const ip = getClientIp(req); - const limit = rateLimit(`share-guest:${ip}`, 10, 60_000); + const limit = await checkRateLimit(`share-guest:${ip}`, 10, 60_000); if (!limit.allowed) { const retryAfter = Math.ceil((limit.resetAt - Date.now()) / 1000); return NextResponse.json( @@ -42,6 +45,15 @@ export async function POST(req: Request) { return NextResponse.json({ error: "결과 데이터가 없습니다." }, { status: 400 }); } + try { + const encoded = JSON.stringify(body.result); + if (encoded.length > MAX_RESULT_BYTES) { + return NextResponse.json({ error: "결과 데이터가 너무 큽니다." }, { status: 413 }); + } + } catch { + return NextResponse.json({ error: "잘못된 요청입니다." }, { status: 400 }); + } + const supabase = await createSupabaseRouteClient(req); if (!supabase) { return NextResponse.json({ error: "Supabase not configured" }, { status: 503 }); diff --git a/app/api/track/route.ts b/app/api/track/route.ts index 23fe910..9de31ba 100644 --- a/app/api/track/route.ts +++ b/app/api/track/route.ts @@ -1,12 +1,22 @@ import { NextResponse } from "next/server"; import { createServiceClientSafe } from "@/lib/supabase-server"; -import { getClientIp, rateLimit } from "@/lib/rate-limit"; +import { checkRateLimit, getClientIp } from "@/lib/rate-limit"; export const runtime = "nodejs"; +const EVENT_TYPES = new Set(["affiliate", "compare_view"]); +const REGIONS = new Set(["KR", "US", "JP"]); + +function clip(value: unknown, max: number): string | null { + if (value == null) return null; + const text = String(value).trim(); + if (!text) return null; + return text.slice(0, max); +} + export async function POST(req: Request) { const ip = getClientIp(req); - const limit = rateLimit(`track:${ip}`, 60, 60_000); + const limit = await checkRateLimit(`track:${ip}`, 60, 60_000); if (!limit.allowed) { const retryAfter = Math.ceil((limit.resetAt - Date.now()) / 1000); return NextResponse.json( @@ -17,27 +27,29 @@ export async function POST(req: Request) { try { const body = await req.json(); - const { event_type, product_id, slug, region, retailer, session_id } = body; - - if (!event_type || !["affiliate", "compare_view"].includes(event_type)) { + const eventType = clip(body?.event_type, 32); + if (!eventType || !EVENT_TYPES.has(eventType)) { return NextResponse.json({ error: "invalid_event" }, { status: 400 }); } + const regionRaw = clip(body?.region, 8)?.toUpperCase() ?? null; + const region = regionRaw && REGIONS.has(regionRaw) ? regionRaw : null; + const db = createServiceClientSafe(); if (!db) { return NextResponse.json({ ok: true }); // silently skip if no DB } - const referrer = req.headers.get("referer") ?? undefined; + const referrer = clip(req.headers.get("referer"), 500) ?? undefined; await db.from("click_events").insert({ - event_type, - product_id: product_id ?? null, - slug: slug ?? null, - region: region ?? null, - retailer: retailer ?? null, - session_id: session_id ?? null, - referrer: referrer ?? null, + event_type: eventType, + product_id: clip(body?.product_id, 120), + slug: clip(body?.slug, 160), + region, + retailer: clip(body?.retailer, 64), + session_id: clip(body?.session_id, 128), + referrer: referrer ?? null }); return NextResponse.json({ ok: true }); diff --git a/app/api/watches/route.ts b/app/api/watches/route.ts index adf36fa..58d5d68 100644 --- a/app/api/watches/route.ts +++ b/app/api/watches/route.ts @@ -1,7 +1,7 @@ import { NextResponse } from "next/server"; import { upsertWatch, deleteWatch, listWatchesByEmail } from "@/lib/watch/db"; import { createSupabaseRouteClient } from "@/lib/supabase-route"; -import { getClientIp, rateLimit } from "@/lib/rate-limit"; +import { checkRateLimit, getClientIp } from "@/lib/rate-limit"; import type { Region } from "@/lib/pricing/types"; function isValidEmail(email: string): boolean { @@ -33,9 +33,9 @@ async function requireSessionEmail(req: Request): Promise< return { email }; } -function rateLimitOrReject(req: Request, action: string): NextResponse | null { +async function rateLimitOrReject(req: Request, action: string): Promise { const ip = getClientIp(req); - const limit = rateLimit(`watches:${action}:${ip}`, 30, 60_000); + const limit = await checkRateLimit(`watches:${action}:${ip}`, 30, 60_000); if (!limit.allowed) { const retryAfter = Math.ceil((limit.resetAt - Date.now()) / 1000); return NextResponse.json( @@ -51,7 +51,7 @@ function rateLimitOrReject(req: Request, action: string): NextResponse | null { * Returns the watch list for the authenticated user only. */ export async function GET(req: Request) { - const limited = rateLimitOrReject(req, "get"); + const limited = await rateLimitOrReject(req, "get"); if (limited) return limited; const auth = await requireSessionEmail(req); @@ -67,7 +67,7 @@ export async function GET(req: Request) { * Optional `email` in body is ignored (session email wins). */ export async function POST(req: Request) { - const limited = rateLimitOrReject(req, "post"); + const limited = await rateLimitOrReject(req, "post"); if (limited) return limited; const auth = await requireSessionEmail(req); @@ -106,7 +106,7 @@ export async function POST(req: Request) { * Body: { productId, region } */ export async function DELETE(req: Request) { - const limited = rateLimitOrReject(req, "delete"); + const limited = await rateLimitOrReject(req, "delete"); if (limited) return limited; const auth = await requireSessionEmail(req); diff --git a/app/compare/[slug]/page.tsx b/app/compare/[slug]/page.tsx index aa0ec49..5c5b804 100644 --- a/app/compare/[slug]/page.tsx +++ b/app/compare/[slug]/page.tsx @@ -9,6 +9,8 @@ import PageViewTracker from "@/components/page-view-tracker"; import type { ComparisonResult } from "@/lib/types"; import { getSiteUrl } from "@/lib/site-url"; import { isIndexable } from "@/lib/specs/source"; +import { getLocale } from "@/lib/i18n/server"; +import { getDictionary, type Locale } from "@/lib/i18n"; // Re-generate at most once per day. export const revalidate = 86400; @@ -31,7 +33,8 @@ export async function generateMetadata({ params }: Props): Promise { const title = `${def.title} — Axis의 선택은?`; const siteUrl = getSiteUrl(); - const result = await getOrGenerate(slug, def.options); + // SEO metadata stays Korean (KR index pages). + const result = await getOrGenerate(slug, def.options, "ko"); const indexable = result ? isIndexable(result.verification ?? "unverified") : false; return { @@ -49,10 +52,15 @@ export async function generateMetadata({ params }: Props): Promise { }; } -async function getOrGenerate(slug: string, options: string[]): Promise { +async function getOrGenerate( + slug: string, + options: string[], + locale: Locale +): Promise { const db = createServiceClient(); + const query = buildQuery(options); - // 1. Try cache + // 1. Try cache — only reuse when cached result language matches. const { data } = await db .from("seo_comparisons") .select("result") @@ -60,19 +68,20 @@ async function getOrGenerate(slug: string, options: string[]): Promise { if (error) console.error("[seo_comparisons.upsert]", error.message); }); @@ -86,7 +95,9 @@ export default async function ComparePage({ params }: Props) { const def = COMPARISONS.find((c) => c.slug === slug); if (!def) notFound(); - const result = await getOrGenerate(slug, def.options); + const locale = await getLocale(); + const t = getDictionary(locale); + const result = await getOrGenerate(slug, def.options, locale); if (!result) notFound(); const query = buildQuery(def.options); @@ -95,8 +106,11 @@ export default async function ComparePage({ params }: Props) { ).slice(0, 4); const siteUrl = getSiteUrl(); + const categoryLabel = + t.home.categoryLabels[def.category as keyof typeof t.home.categoryLabels] ?? + CATEGORY_LABELS[def.category]; - // JSON-LD structured data + // JSON-LD structured data (KR SEO) const jsonLd = { "@context": "https://schema.org", "@type": "Article", @@ -114,11 +128,10 @@ export default async function ComparePage({ params }: Props) { dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} /> - {/* Breadcrumb */}
axis › - {CATEGORY_LABELS[def.category]} + {categoryLabel} › {def.title}
@@ -127,16 +140,15 @@ export default async function ComparePage({ params }: Props) { - {/* Related comparisons */} {relatedComparisons.length > 0 && (
-

비슷한 비교

+

{t.home.compareTitle}

    {relatedComparisons.map((c) => (
  • @@ -151,13 +163,10 @@ export default async function ComparePage({ params }: Props) {
)} - {/* CTA */}
-

- 내 상황(예산·용도·기존 기기)을 더해서 맞춤 분석을 받고 싶다면? -

+

{t.home.compareIndex.ctaCopy}

- 내 상황 맞춤 비교 받기 → + {t.home.compareIndex.cta}
diff --git a/app/compare/page.tsx b/app/compare/page.tsx index 0aee1a5..661de5b 100644 --- a/app/compare/page.tsx +++ b/app/compare/page.tsx @@ -6,7 +6,8 @@ import { localizedComparisonTitle } from "@/lib/compare-pages/localized-title"; import { getDictionary } from "@/lib/i18n"; import { getLocale } from "@/lib/i18n/server"; -const CATEGORIES: ComparisonDef["category"][] = ["laptop", "smartphone", "earphones", "tablet"]; +/** Ops focus: KR + laptop first. Other category assets remain in COMPARISONS. */ +const FOCUS_CATEGORIES: ComparisonDef["category"][] = ["laptop"]; export async function generateMetadata(): Promise { const locale = await getLocale(); @@ -21,7 +22,7 @@ export default async function ComparePage() { const locale = await getLocale(); const t = getDictionary(locale); const byCategory = Object.fromEntries( - CATEGORIES.map((cat) => [cat, COMPARISONS.filter((c) => c.category === cat)]) + FOCUS_CATEGORIES.map((cat) => [cat, COMPARISONS.filter((c) => c.category === cat)]) ) as Record; return ( @@ -37,7 +38,7 @@ export default async function ComparePage() {

{t.home.compareIndex.sub}

- {CATEGORIES.map((cat) => ( + {FOCUS_CATEGORIES.map((cat) => (

{t.home.categoryLabels[cat]}

    diff --git a/app/globals.css b/app/globals.css index 6d6a907..48e0e52 100644 --- a/app/globals.css +++ b/app/globals.css @@ -367,4388 +367,7 @@ a { margin: 0; } -/* ===== Home: compact, input-first hero ===== */ -.hero { - text-align: center; - margin: 1.4rem 0 1.6rem; -} - -.hero.compact { - margin-top: 2rem; -} - -.badge { - display: inline-flex; - align-items: center; - gap: 0.45rem; - background: rgba(255, 255, 255, 0.72); - border: 1px solid var(--border); - color: var(--muted); - padding: 0.34rem 0.85rem 0.34rem 0.7rem; - border-radius: 9999px; - font-size: 0.8rem; - font-weight: 600; - box-shadow: var(--shadow-sm); -} - -.badge::before { - content: ""; - width: 6px; - height: 6px; - border-radius: 9999px; - background: var(--accent); - box-shadow: 0 0 0 3px rgba(52, 84, 232, 0.18); -} - -[data-theme="dark"] .badge { - background: rgba(255, 255, 255, 0.05); -} - -.hero h1 { - margin: 0.85rem 0 0.7rem; - font-size: clamp(1.85rem, 4.4vw, 2.9rem); - font-weight: 700; - line-height: 1.18; - letter-spacing: -0.025em; -} - -.hero h1 span { - color: var(--accent); -} - -.sub { - color: var(--muted); - font-size: 1.02rem; - max-width: 520px; - margin: 0 auto; - line-height: 1.65; - white-space: pre-line; -} - -.hero-proof { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 0.5rem; - margin-top: 1.2rem; -} - -.proof-pill { - display: inline-flex; - align-items: center; - gap: 0.3rem; - border: 1px solid rgba(52, 84, 232, 0.25); - border-radius: 9999px; - background: rgba(52, 84, 232, 0.08); - padding: 0.4rem 0.85rem; - color: var(--accent); - font-size: 0.82rem; - font-weight: 700; -} - -.proof-pill strong { - color: var(--accent); - font-size: 0.82rem; - font-weight: 700; -} - -[data-theme="dark"] .proof-pill { - background: rgba(91, 120, 245, 0.14); - border-color: rgba(91, 120, 245, 0.35); -} - -/* ===== Home sections ===== */ -.home-section { - margin-top: 2.6rem; -} - -.section-copy { - max-width: 640px; - margin-bottom: 1.1rem; -} - -.section-copy.center { - margin-left: auto; - margin-right: auto; - text-align: center; -} - -.section-kicker { - margin: 0 0 0.4rem; - color: var(--accent); - font-size: 0.74rem; - font-weight: 800; - letter-spacing: 0.08em; - text-transform: uppercase; -} - -.section-copy h2 { - margin: 0; - font-size: clamp(1.35rem, 2.6vw, 1.75rem); - font-weight: 800; - line-height: 1.25; - letter-spacing: -0.02em; -} - -.section-copy p:not(.section-kicker) { - margin: 0.55rem 0 0; - color: var(--muted); - font-size: 0.95rem; - line-height: 1.6; -} - -/* ===== Example chips (one-tap prefill) ===== */ -.example-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 0.8rem; -} - -.example-card { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 0.3rem; - width: 100%; - text-align: left; - font: inherit; - cursor: pointer; - border: 1px solid var(--border); - border-radius: 18px; - background: rgba(255, 255, 255, 0.74); - box-shadow: var(--shadow-sm); - padding: 1rem 1.1rem 0.9rem; - transition: transform 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease; -} - -[data-theme="dark"] .example-card { - background: rgba(255, 255, 255, 0.04); -} - -.example-card:hover { - transform: translateY(-2px); - border-color: var(--accent); - box-shadow: 0 14px 30px rgba(52, 84, 232, 0.16); -} - -.example-cat { - color: var(--accent); - font-size: 0.72rem; - font-weight: 800; - letter-spacing: 0.02em; -} - -.example-query { - font-size: 1.02rem; - font-weight: 700; - line-height: 1.3; - letter-spacing: -0.01em; -} - -.example-note { - color: var(--muted); - font-size: 0.86rem; - line-height: 1.5; -} - -.example-go { - margin-top: 0.55rem; - color: var(--muted); - font-size: 0.82rem; - font-weight: 600; - transition: color 0.16s ease, transform 0.16s ease; -} - -.example-card:hover .example-go { - color: var(--accent); - transform: translateX(2px); -} - -/* ===== Method band ===== */ -/* ── popular comparison grid ── */ -.home-compare-grid { - list-style: none; - margin: 0; - padding: 0; - display: grid; - grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); - gap: 0.65rem; -} - -.home-compare-card { - border: 1px solid var(--border); - border-radius: 12px; - background: var(--card); - transition: box-shadow .15s; -} - -.home-compare-card:hover { - box-shadow: 0 2px 12px rgba(0,0,0,.08); -} - -[data-theme="dark"] .home-compare-card:hover { - box-shadow: 0 2px 12px rgba(0,0,0,.3); -} - -.home-compare-link { - display: flex; - flex-direction: column; - gap: 0.25rem; - padding: 0.9rem 1rem; - text-decoration: none; - color: var(--fg); -} - -.home-compare-cat { - font-size: 0.7rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: .06em; - color: var(--muted); -} - -.compare-data-note { - margin: 0.4rem 0 0; - font-size: 0.85rem; - color: var(--muted); -} - -.home-compare-rank { - font-size: 0.7rem; - font-weight: 800; - color: var(--accent); - opacity: 0.7; -} - -.home-compare-title { - font-size: 0.88rem; - font-weight: 700; - line-height: 1.35; -} - -.home-compare-arrow { - font-size: 0.8rem; - color: var(--muted); - margin-top: 0.1rem; -} - -.home-compare-more { - margin-top: 0.9rem; - text-align: center; - font-size: 0.85rem; -} - -.home-compare-more a { - color: var(--muted); - text-decoration: none; -} - -.home-compare-more a:hover { - color: var(--fg); -} - -/* ── end popular comparison grid ── */ - -.home-method { - display: grid; - grid-template-columns: minmax(220px, 0.8fr) 1.2fr; - gap: 1.4rem; - align-items: start; -} - -.method-grid { - display: grid; - gap: 0.7rem; -} - -.method-item { - display: grid; - grid-template-columns: auto 1fr; - column-gap: 0.85rem; - row-gap: 0.25rem; - padding: 0.95rem 1.1rem; - border: 1px solid var(--border); - border-radius: 16px; - background: rgba(255, 255, 255, 0.74); - box-shadow: var(--shadow-sm); -} - -[data-theme="dark"] .method-item { - background: rgba(255, 255, 255, 0.04); -} - -.method-item span { - grid-row: span 2; - align-self: center; - display: inline-flex; - align-items: center; - justify-content: center; - width: 30px; - height: 30px; - border-radius: 9px; - background: rgba(52, 84, 232, 0.1); - color: var(--accent); - font-size: 0.82rem; - font-weight: 800; -} - -[data-theme="dark"] .method-item span { - background: rgba(91, 120, 245, 0.16); -} - -.method-item h3 { - margin: 0; - font-size: 0.98rem; -} - -.method-item p { - margin: 0; - color: var(--muted); - line-height: 1.55; - font-size: 0.9rem; -} - -.method-em { - font-weight: 600; - color: var(--fg); -} - -/* ── Feature grid (replaces method 01/02/03 cards) ── */ -.feature-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 0.55rem; - align-content: start; -} - -.feature-item { - display: flex; - flex-direction: column; - gap: 0.18rem; - padding: 0.75rem 0.9rem; - border: 1px solid var(--border); - border-radius: 12px; - background: rgba(52, 84, 232, 0.04); -} - -[data-theme="dark"] .feature-item { - background: rgba(91, 120, 245, 0.07); - border-color: var(--border); -} - -.feature-item strong { - font-size: 0.86rem; - font-weight: 700; - color: var(--fg); - line-height: 1.3; -} - -.feature-item span { - font-size: 0.78rem; - color: var(--muted); - line-height: 1.4; -} - -/* ── Rank list (popular comparisons) ── */ -.home-rank-wrap { - position: relative; - max-height: 305px; - overflow: hidden; -} - -.home-rank-list { - list-style: none; - margin: 0; - padding: 0; -} - -.home-rank-item { - border-bottom: 1px solid var(--border-light); -} - -.home-rank-item:first-child { - border-top: 1px solid var(--border-light); -} - -.home-rank-link { - display: flex; - align-items: center; - gap: 0.9rem; - padding: 0.78rem 0.4rem; - color: var(--fg); - text-decoration: none; - background: none; - border: none; - width: 100%; - cursor: pointer; - font-family: inherit; - text-align: left; - transition: color 0.15s ease; -} - -.home-rank-link:hover { - color: var(--accent); -} - -.home-rank-link:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.home-rank-link.home-rank-loading { - color: var(--accent); -} - -.home-rank-num { - font-size: 0.78rem; - font-weight: 800; - color: var(--accent); - min-width: 18px; - text-align: right; - opacity: 0.8; - flex-shrink: 0; -} - -.home-rank-text { - font-size: 0.93rem; - font-weight: 600; - line-height: 1.35; - flex: 1; -} - -.home-rank-arrow { - font-size: 0.8rem; - color: var(--muted); - flex-shrink: 0; - transition: transform 0.15s ease; -} - -.home-rank-link:hover .home-rank-arrow { - transform: translateX(3px); - color: var(--accent); -} - -@keyframes rankSpin { - to { transform: rotate(360deg); } -} - -.home-rank-spinner { - display: inline-block; - width: 14px; - height: 14px; - border: 2px solid var(--accent); - border-top-color: transparent; - border-radius: 50%; - animation: rankSpin 0.7s linear infinite; - flex-shrink: 0; -} - -.home-rank-fade { - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 88px; - background: linear-gradient(to bottom, transparent, var(--bg) 90%); - pointer-events: none; -} - -.vs-shell { - margin-top: 0.5rem; -} - -.vs-grid { - display: grid; - grid-template-columns: 1fr auto 1fr; - gap: 1rem; - align-items: stretch; -} - -.vs-divider { - align-self: center; - font-size: 0.72rem; - font-weight: 700; - letter-spacing: 0.08em; - text-transform: uppercase; - color: var(--muted); - opacity: 0.5; - padding: 0 0.25rem; - user-select: none; -} - -.vs-card { - position: relative; - display: flex; - flex-direction: column; - gap: 0.55rem; - min-height: 180px; - padding: 1.2rem; - border: 1px solid var(--border); - border-radius: 24px; - background: var(--card); - box-shadow: var(--shadow-md); - overflow: hidden; - transition: border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease; -} - -.vs-card:focus-within { - border-color: var(--accent); - box-shadow: 0 18px 44px rgba(52, 84, 232, 0.18); - transform: translateY(-2px); -} - -.vs-card input { - border: none; - outline: none; - font-size: 1.35rem; - font-weight: 600; - background: transparent; - padding: 0; - z-index: 1; -} - -.vs-card input::placeholder { - color: #c7cdd8; - font-weight: 500; -} - -.vs-tag { - width: 28px; - height: 28px; - border-radius: 9999px; - display: inline-flex; - align-items: center; - justify-content: center; - font-size: 0.85rem; - font-weight: 700; - color: white; - z-index: 1; -} - -.vs-card-a .vs-tag { - background: #3454e8; -} - -.vs-card-b .vs-tag { - background: #7c5cff; -} - -.vs-label { - color: var(--muted); - font-size: 0.9rem; - z-index: 1; -} - -.vs-watermark { - position: absolute; - right: 0.2rem; - bottom: -1.4rem; - font-size: 8rem; - font-weight: 800; - line-height: 1; - opacity: 0.08; - pointer-events: none; -} - -.vs-card-a .vs-watermark { - color: #3454e8; -} - -.vs-card-b .vs-watermark { - color: #7c5cff; -} - -@media (max-width: 560px) { - .vs-grid { - grid-template-columns: 1fr; - } - - .vs-divider { - text-align: center; - padding: 0.2rem 0; - } -} - -.btn-primary { - width: 100%; - margin-top: 1.2rem; - border: none; - border-radius: 14px; - padding: 0.9rem 1rem; - color: white; - font-size: 1rem; - font-weight: 700; - background: linear-gradient(135deg, #1a2238 0%, #2d3f86 55%, #4b54c9 100%); - cursor: pointer; - transition: transform 0.16s ease, box-shadow 0.16s ease, opacity 0.16s ease; - box-shadow: 0 8px 20px rgba(45, 63, 134, 0.22); -} - -.btn-primary:hover:not(:disabled) { - transform: translateY(-1px); - box-shadow: 0 14px 28px rgba(45, 63, 134, 0.3); -} - -.btn-primary:active:not(:disabled) { - transform: translateY(0); -} - -.btn-primary.large { - padding: 1.05rem 1rem; - font-size: 1.05rem; -} - -.btn-primary:disabled { - opacity: 0.45; - cursor: not-allowed; - box-shadow: none; -} - -.btn-outline { - width: 100%; - margin-top: 0.8rem; - border: 1px solid var(--border); - border-radius: 14px; - padding: 0.85rem 1rem; - text-align: center; - background: white; - cursor: pointer; - font-weight: 600; - transition: border-color 0.16s ease, color 0.16s ease, transform 0.16s ease; -} - -.btn-outline:hover { - border-color: var(--accent); - color: var(--accent); - transform: translateY(-1px); -} - -.btn-primary.block { - display: block; - text-align: center; -} - -.history-card, -.result-card, -.detail-card { - border: 1px solid var(--border); - border-radius: 20px; - background: var(--card); - box-shadow: var(--shadow-sm); -} - -[data-theme="dark"] .history-card, -[data-theme="dark"] .result-card, -[data-theme="dark"] .detail-card { - background: var(--card); -} - -.history-card { - margin-top: 1.5rem; - padding: 0; - overflow: hidden; -} - -.result-card, -.detail-card { - margin-top: 1rem; - padding: 1.4rem 1.5rem; -} - -.detail-card h2 { - margin: 0 0 1.1rem; - font-size: 0.82rem; - font-weight: 700; - letter-spacing: 0.06em; - text-transform: uppercase; - color: var(--muted); -} - -/* ════════════════════════════════════════ - Verdict hero — 결과 첫 블록 (프리미엄) - ════════════════════════════════════════ */ -.verdict-hero { - position: relative; - overflow: hidden; - margin-top: 0; - padding: 1.8rem 1.9rem 1.7rem; - border: 1px solid var(--border); - border-radius: 22px; - background: var(--card); - box-shadow: var(--shadow-md); -} - -/* 상단 accent 라인 */ -.verdict-hero::before { - content: ""; - position: absolute; - inset: 0 0 auto 0; - height: 3px; - background: linear-gradient(90deg, var(--accent), var(--accent-2)); -} - -/* AXIS 선택 배지 */ -.vh-badge { - display: inline-flex; - align-items: center; - gap: 0.45rem; - padding: 0.34rem 0.75rem 0.34rem 0.6rem; - border-radius: 9999px; - background: color-mix(in srgb, var(--accent) 10%, transparent); - color: var(--accent); - font-size: 0.72rem; - font-weight: 800; - letter-spacing: 0.04em; - text-transform: uppercase; -} - -.vh-badge-dot { - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--accent); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent); -} - -.vh-title { - margin: 0.85rem 0 0.5rem; - font-size: clamp(1.7rem, 5vw, 2.55rem); - font-weight: 900; - line-height: 1.12; - letter-spacing: -0.035em; - color: var(--text); - word-break: break-word; - overflow-wrap: break-word; -} - -.vh-conclusion { - margin: 0; - max-width: 34rem; - color: var(--muted); - font-size: 1.02rem; - line-height: 1.62; - word-break: break-word; - overflow-wrap: break-word; -} - -/* 승부 막대 영역 */ -.vh-matchup { - margin-top: 1.6rem; - padding-top: 1.5rem; - border-top: 1px solid var(--border-light); - display: flex; - flex-direction: column; - gap: 1rem; -} - -.vh-row { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.vh-row-head { - display: flex; - align-items: center; - gap: 0.55rem; - min-width: 0; -} - -.vh-row-name { - font-size: 0.92rem; - font-weight: 600; - color: var(--muted); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - min-width: 0; -} - -.vh-row-win .vh-row-name { - color: var(--text); - font-weight: 800; -} - -.vh-row-tag { - flex-shrink: 0; - padding: 0.12rem 0.5rem; - border-radius: 9999px; - background: var(--accent); - color: #fff; - font-size: 0.64rem; - font-weight: 800; - letter-spacing: 0.05em; - text-transform: uppercase; -} - -.vh-bar-track { - height: 8px; - border-radius: 9999px; - background: var(--border-light); - overflow: hidden; -} - -.vh-bar-fill { - height: 100%; - border-radius: 9999px; - background: var(--border); - transition: width 0.7s cubic-bezier(0.22, 1, 0.36, 1); -} - -.vh-row-win .vh-bar-track { - background: color-mix(in srgb, var(--accent) 14%, transparent); -} - -.vh-row-win .vh-bar-fill { - background: linear-gradient(90deg, var(--accent), var(--accent-2)); -} - -.vh-bar-note { - margin: 0.1rem 0 0; - font-size: 0.72rem; - color: var(--muted); - opacity: 0.6; - line-height: 1.4; -} - -/* result-card / result-conclusion — admin hero 등에서 재사용되는 기본형 */ -.result-card { - padding: 1.7rem 1.8rem; - text-align: center; -} - -.result-conclusion { - margin: 0.5rem auto 0; - max-width: 30rem; - color: var(--muted); - font-size: 1rem; - line-height: 1.62; -} - -.detail-card ul { - margin: 0; - padding-left: 1.1rem; -} - -/* Reason list */ -.reason-list { - list-style: none; - margin: 0; - padding: 0; - display: flex; - flex-direction: column; - gap: 0; -} - -.reason-list li { - display: flex; - align-items: flex-start; - gap: 0.75rem; - line-height: 1.6; - font-size: 0.95rem; - padding: 0.75rem 0; - border-bottom: 1px solid var(--border-light); -} - -.reason-list li:last-child { border-bottom: none; } - -.reason-mark { - flex-shrink: 0; - width: 20px; - height: 20px; - margin-top: 0.2rem; - border-radius: 50%; - display: inline-flex; - align-items: center; - justify-content: center; - font-size: 0.58rem; - font-weight: 900; - color: white; - background: linear-gradient(135deg, #3454e8 0%, #7c5cff 100%); - box-shadow: 0 2px 6px rgba(52, 84, 232, 0.3); -} - - -/* ---- spec section ---- */ -.spec-section { - padding-bottom: 0; -} - -.spec-source-note { - margin: 0 0 0.9rem; - color: var(--muted); - font-size: 0.82rem; - font-weight: 500; -} - -/* Scroll wrapper for horizontal overflow — extends to card edges */ -.spec-table-scroll { - overflow-x: auto; - -webkit-overflow-scrolling: touch; - margin: 0 -1.5rem; - padding: 0 1.5rem 1.4rem; -} - -/* Official link icon inside column header */ -.cmp-head-name { - display: flex; - align-items: center; - gap: 0.35rem; -} - -.cmp-head-link { - font-size: 0.75rem; - color: var(--muted); - opacity: 0.7; - flex-shrink: 0; - transition: opacity 0.15s; -} - -.cmp-head-link:hover { - opacity: 1; - color: var(--accent); -} - -.cmp-source-chip { - display: inline-flex; - align-items: center; - gap: 0.18rem; - max-width: 9.5rem; - padding: 0.16rem 0.45rem; - border-radius: 9999px; - border: 1px solid rgba(52, 84, 232, 0.16); - background: rgba(52, 84, 232, 0.08); - color: var(--accent); - font-size: 0.66rem; - font-weight: 800; - line-height: 1.2; - white-space: nowrap; -} - -.cmp-source-chip.source-authorized_importer { - border-color: rgba(16, 185, 129, 0.2); - background: rgba(16, 185, 129, 0.09); - color: #059669; -} - -.cmp-source-chip:hover { - border-color: currentColor; -} - -.admin-hero h1 { - font-size: clamp(2rem, 4vw, 3rem); -} - -.admin-source-card { - display: flex; - flex-direction: column; - gap: 1rem; -} - -.admin-grid { - display: grid; - grid-template-columns: minmax(0, 1fr) 8rem; - gap: 0.8rem; -} - -.admin-field { - display: flex; - flex-direction: column; - gap: 0.45rem; - font-size: 0.84rem; - font-weight: 700; - color: var(--text); -} - -.admin-field input, -.admin-field select, -.admin-field textarea { - width: 100%; - border: 1.5px solid var(--border); - border-radius: 12px; - background: var(--bg); - color: var(--text); - font: inherit; - box-sizing: border-box; -} - -.admin-field input, -.admin-field select { - min-height: 3rem; - padding: 0 0.9rem; -} - -.admin-field textarea { - min-height: 22rem; - padding: 0.9rem; - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; - font-size: 0.86rem; - line-height: 1.55; - resize: vertical; -} - -.admin-field input:focus, -.admin-field select:focus, -.admin-field textarea:focus { - outline: none; - border-color: var(--accent); - box-shadow: 0 0 0 3px var(--ring); -} - -.admin-actions { - display: grid; - grid-template-columns: 1fr auto; - gap: 0.8rem; - align-items: center; -} - -.admin-actions .btn-primary, -.admin-actions .btn-outline { - margin-top: 0; -} - -.admin-validity { - display: inline-flex; - align-items: center; - justify-content: center; - min-height: 2.5rem; - border-radius: 9999px; - padding: 0 0.85rem; - font-size: 0.84rem; - font-weight: 800; - white-space: nowrap; -} - -.admin-validity.valid { - color: #059669; - background: rgba(16, 185, 129, 0.09); -} - -.admin-validity.invalid { - color: #dc2626; - background: rgba(220, 38, 38, 0.08); -} - -.admin-source-summary { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; - align-items: center; - color: var(--muted); - font-size: 0.88rem; -} - -.admin-source-summary span, -.admin-source-summary a { - display: inline-flex; - align-items: center; - border: 1px solid var(--border); - border-radius: 9999px; - padding: 0.34rem 0.7rem; - background: rgba(52, 84, 232, 0.06); - font-weight: 700; -} - -.admin-source-summary a { - color: var(--accent); -} - -.admin-message { - margin: 0; - color: var(--muted); - font-size: 0.92rem; - line-height: 1.5; -} - -/* Empty spec state */ -.spec-empty { - padding: 1.2rem 0 1.4rem; - text-align: center; -} - -.spec-empty p { - margin: 0 0 0.9rem; - color: var(--muted); - font-size: 0.92rem; - line-height: 1.55; -} - -.spec-empty-links { - display: flex; - flex-wrap: wrap; - gap: 0.6rem; - justify-content: center; -} - -.spec-empty-links a { - display: inline-flex; - align-items: center; - padding: 0.4rem 0.85rem; - border-radius: 9999px; - border: 1px solid var(--border); - font-size: 0.85rem; - font-weight: 600; - color: var(--text); - transition: border-color 0.15s, color 0.15s; -} - -.spec-empty-links a:hover { - border-color: var(--accent); - color: var(--accent); -} - -.hint { - margin: 0.8rem 0 0; - color: var(--muted); - font-size: 0.9rem; - text-align: center; -} - -.hint.error { - color: #dc2626; -} - -.guest { - display: inline-block; -} - -/* ---- back button ---- */ -.btn-back { - display: inline-flex; - align-items: center; - gap: 0.25rem; - font-size: 0.92rem; - font-weight: 500; - color: var(--muted); - margin-bottom: 1.2rem; - transition: color 0.16s ease; -} - -.btn-back:hover { - color: var(--text); -} - -/* ---- settings bar ---- */ -.settings-bar { - display: flex; - align-items: center; - gap: 0.5rem; -} - -/* Language dropdown */ -.lang-dropdown { - position: relative; -} - -.lang-trigger { - display: flex; - align-items: center; - gap: 0.3rem; - border: 1px solid var(--border); - border-radius: 10px; - padding: 0.38rem 0.7rem; - background: white; - font-size: 0.82rem; - font-weight: 600; - color: var(--text); - cursor: pointer; - transition: border-color 0.14s ease, color 0.14s ease; - white-space: nowrap; -} - -.lang-trigger:hover { - border-color: var(--accent); - color: var(--accent); -} - -.lang-globe { - font-size: 0.9rem; - opacity: 0.6; -} - -.lang-caret { - font-size: 0.55rem; - color: var(--muted); - margin-left: 0.05rem; -} - -.lang-menu { - position: absolute; - right: 0; - top: calc(100% + 0.4rem); - background: var(--card); - border: 1px solid var(--border); - border-radius: 12px; - box-shadow: var(--shadow-md); - padding: 0.3rem; - min-width: 110px; - z-index: 30; -} - -.lang-option { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - text-align: left; - border: none; - background: transparent; - padding: 0.5rem 0.7rem; - border-radius: 8px; - font-size: 0.85rem; - font-weight: 500; - color: var(--text); - cursor: pointer; - transition: background 0.12s ease; -} - -.lang-option:hover { - background: var(--bg); -} - -.lang-option.active { - font-weight: 700; - color: var(--accent); -} - -.lang-check { - font-size: 0.72rem; - font-weight: 800; - color: var(--accent); -} - -[data-theme="dark"] .lang-trigger { - background: var(--card); -} - -.theme-toggle { - width: 34px; - height: 34px; - border: 1px solid var(--border); - border-radius: 10px; - background: white; - font-size: 1rem; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - transition: border-color 0.14s ease; -} - -[data-theme="dark"] .theme-toggle { - background: var(--card); - color: var(--text); -} - -.theme-toggle:hover { - border-color: var(--accent); -} - -/* ---- user dropdown menu ---- */ -.user-nav { - position: relative; -} - -.user-trigger { - display: flex; - align-items: center; - gap: 0.5rem; - border: 1px solid var(--border); - border-radius: 9999px; - padding: 0.35rem 0.7rem 0.35rem 0.4rem; - background: white; - cursor: pointer; -} - -.user-avatar { - width: 28px; - height: 28px; - border-radius: 9999px; - display: inline-flex; - align-items: center; - justify-content: center; - background: linear-gradient(135deg, #3454e8, #7c5cff); - color: white; - font-size: 0.85rem; - font-weight: 700; - flex-shrink: 0; -} - -.user-avatar.lg { - width: 40px; - height: 40px; - font-size: 1.1rem; -} - -.user-caret { - color: var(--muted); - font-size: 0.7rem; -} - -.user-dropdown { - position: absolute; - right: 0; - top: calc(100% + 0.5rem); - width: 250px; - background: white; - border: 1px solid var(--border); - border-radius: 16px; - box-shadow: 0 16px 40px rgba(17, 24, 39, 0.12); - padding: 0.5rem; - z-index: 20; -} - -.user-dropdown-head { - display: flex; - align-items: center; - gap: 0.7rem; - padding: 0.6rem 0.6rem 0.8rem; - border-bottom: 1px solid var(--border); -} - -.user-dropdown-id { - display: flex; - flex-direction: column; - min-width: 0; -} - -.user-dropdown-id strong { - font-size: 0.9rem; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.user-dropdown-id span { - color: var(--muted); - font-size: 0.78rem; -} - -.user-dropdown-list { - display: flex; - flex-direction: column; - padding: 0.4rem 0; -} - -.user-dropdown-list a { - padding: 0.6rem 0.6rem; - border-radius: 10px; - font-size: 0.92rem; -} - -.user-dropdown-list a:hover { - background: var(--bg); -} - -.user-dropdown-signout { - width: 100%; - text-align: left; - border: none; - border-top: 1px solid var(--border); - background: transparent; - color: #dc2626; - font-size: 0.92rem; - padding: 0.7rem 0.6rem 0.5rem; - cursor: pointer; -} - -/* ---- account page ---- */ -.account-card { - display: flex; - flex-direction: column; - gap: 0; - padding: 0.4rem 1.2rem; -} - -.account-row { - display: flex; - justify-content: space-between; - align-items: center; - gap: 1rem; - padding: 0.9rem 0; - border-bottom: 1px solid var(--border); -} - -.account-row:last-child { - border-bottom: none; -} - -.account-key { - color: var(--muted); - font-size: 0.9rem; -} - -.account-val { - font-weight: 600; - text-align: right; -} - -.hint-inline { - color: var(--muted); - font-weight: 400; - font-size: 0.85rem; -} - -.plan-badge { - display: inline-block; - padding: 0.2rem 0.6rem; - border-radius: 9999px; - font-size: 0.8rem; - font-weight: 700; -} - -.plan-badge.free { - background: var(--bg); - color: var(--muted); -} - -.plan-badge.pro { - background: linear-gradient(135deg, #3454e8, #7c5cff); - color: white; -} - -.upsell { - text-align: center; -} - -.account-actions-row { - margin-top: 1.2rem; -} - -/* ---- membership / pricing ---- */ -.membership .feature-table, -.membership > .hint { - max-width: 720px; - margin-left: auto; - margin-right: auto; -} - -.plan-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 1rem; - margin-top: 1rem; -} - -.plan-grid.three { - grid-template-columns: repeat(3, 1fr); - align-items: stretch; -} - -.plan-grid.three .plan-card { - display: flex; - flex-direction: column; -} - -.plan-grid.three .plan-points { - flex: 1; -} - -@media (max-width: 760px) { - .plan-grid, - .plan-grid.three { - grid-template-columns: 1fr; - } -} - -.plan-badge.plus { - background: rgba(52, 84, 232, 0.12); - color: var(--accent); -} - -.plan-card { - position: relative; - border: 1px solid var(--border); - border-radius: 20px; - background: white; - padding: 1.4rem; - box-shadow: 0 10px 30px rgba(17, 24, 39, 0.06); -} - -.plan-card.featured { - border-color: #3454e8; - box-shadow: 0 16px 40px rgba(52, 84, 232, 0.18); -} - -.plan-ribbon { - position: absolute; - top: -0.7rem; - right: 1.2rem; - background: linear-gradient(135deg, #3454e8, #7c5cff); - color: white; - font-size: 0.72rem; - font-weight: 700; - padding: 0.25rem 0.7rem; - border-radius: 9999px; -} - -.plan-head h2 { - margin: 0; - font-size: 1.3rem; -} - -.plan-price { - margin: 0.5rem 0 0.2rem; - font-size: 2rem; - font-weight: 800; -} - -.plan-price span { - font-size: 0.95rem; - font-weight: 500; - color: var(--muted); -} - -.plan-tagline { - margin: 0 0 1rem; - color: var(--muted); - font-size: 0.9rem; -} - -.plan-points { - list-style: none; - padding: 0; - margin: 0 0 1.2rem; - display: flex; - flex-direction: column; - gap: 0.55rem; -} - -.plan-points li { - position: relative; - padding-left: 1.4rem; - font-size: 0.92rem; -} - -.plan-points li::before { - content: "✓"; - position: absolute; - left: 0; - color: #3454e8; - font-weight: 700; -} - -.plan-current { - text-align: center; - color: var(--muted); - font-size: 0.9rem; - margin: 0; -} - -.feature-table { - margin-top: 1.2rem; -} - -.feat-grid { - display: flex; - flex-direction: column; -} - -.feat-row { - display: grid; - grid-template-columns: 2fr 1fr 1fr; - align-items: center; - gap: 0.6rem; - padding: 0.7rem 0; - border-bottom: 1px solid var(--border); - font-size: 0.92rem; -} - -.feat-grid.three .feat-row { - grid-template-columns: 1.6fr 1fr 1fr 1fr; -} - -.feat-row:last-child { - border-bottom: none; -} - -.feat-row > span:not(:first-child) { - text-align: center; -} - -.feat-header { - font-weight: 700; - color: var(--muted); -} - -.feat-yes { - color: #3454e8; - font-weight: 700; -} - -.feat-no { - color: #c7cdd8; -} - -/* ════════════════════════════════════════ - Auth page — redesigned - ════════════════════════════════════════ */ - -.auth-page { - min-height: 100svh; - display: flex; - align-items: center; - justify-content: center; - padding: 2rem 1.5rem; - background: var(--bg); -} - -.auth-wrap { - position: relative; - z-index: 1; - width: 100%; - max-width: 360px; - display: flex; - flex-direction: column; - align-items: center; - gap: 2rem; -} - -/* Brand */ -.auth-brand { - text-align: center; -} - -.auth-logo { - display: inline-flex; - align-items: center; - gap: 0.5rem; - font-size: 1.95rem; - font-weight: 800; - color: var(--text); - letter-spacing: -0.04em; - text-decoration: none; -} - -.auth-logo-mark { - display: flex; - align-items: center; - color: var(--accent); - flex-shrink: 0; -} - -.auth-tagline { - margin: 0.55rem 0 0; - font-size: 1rem; - font-weight: 600; - color: var(--text); - letter-spacing: -0.01em; -} - -/* Subtle dot-grid background */ -.auth-page::before { - content: ""; - position: fixed; - inset: 0; - background-image: radial-gradient(var(--border) 1px, transparent 1px); - background-size: 28px 28px; - opacity: 0.45; - pointer-events: none; - z-index: 0; -} - -/* Form wrapper — open, no card border */ -.auth-form-wrap { - width: 100%; - display: flex; - flex-direction: column; - gap: 0; - background: var(--card); - border: 1px solid var(--border); - border-radius: 20px; - padding: 2rem 1.8rem; - box-shadow: 0 4px 24px rgba(0,0,0,0.07), 0 1px 4px rgba(0,0,0,0.04); -} - -[data-theme="dark"] .auth-form-wrap { - box-shadow: 0 4px 24px rgba(0,0,0,0.3); -} - -/* Fields */ -.auth-fields { - display: flex; - flex-direction: column; - gap: 1rem; -} - -.auth-field { - display: flex; - flex-direction: column; - gap: 0.4rem; -} - -.auth-field label { - font-size: 0.84rem; - font-weight: 600; - color: var(--text); -} - -.auth-field-row { - display: flex; - align-items: center; - justify-content: space-between; -} - -.auth-field input { - width: 100%; - padding: 0.78rem 0.95rem; - border: 1.5px solid var(--border); - border-radius: 10px; - background: var(--bg); - color: var(--text); - font-size: 0.96rem; - font-family: inherit; - transition: border-color 0.15s, box-shadow 0.15s; - box-sizing: border-box; -} - -.auth-field input:focus { - outline: none; - border-color: var(--accent); - box-shadow: 0 0 0 3px var(--ring); -} - -[data-theme="dark"] .auth-field input { - background: rgba(255,255,255,0.05); - border-color: rgba(255,255,255,0.12); - color: var(--text); -} - -/* Error */ -.auth-error { - margin: 0; - padding: 0.6rem 0.8rem; - background: rgba(220, 38, 38, 0.06); - border: 1px solid rgba(220, 38, 38, 0.2); - border-radius: 8px; - color: #dc2626; - font-size: 0.84rem; - line-height: 1.45; -} - -/* Forgot password — inline next to label */ -.auth-forgot { - background: none; - border: none; - color: var(--muted); - font-size: 0.82rem; - cursor: pointer; - padding: 0; - transition: color 0.15s; -} - -.auth-forgot:hover { color: var(--accent); } - -/* Primary CTA */ -.btn-auth-primary { - margin-top: 0.5rem; - width: 100%; - padding: 0.82rem 1rem; - background: var(--accent); - border: none; - border-radius: 11px; - color: white; - font-size: 0.97rem; - font-weight: 700; - cursor: pointer; - transition: opacity 0.15s, box-shadow 0.15s; - box-shadow: 0 2px 10px rgba(52, 84, 232, 0.3); -} - -.btn-auth-primary:hover:not(:disabled) { - opacity: 0.92; - box-shadow: 0 4px 16px rgba(52, 84, 232, 0.4); -} - -.btn-auth-primary:disabled { - opacity: 0.45; - cursor: default; - box-shadow: none; -} - -/* Mode switch link */ -.auth-switch { - margin: 1rem 0 0; - text-align: center; - font-size: 0.86rem; - color: var(--muted); -} - -.auth-switch button { - background: none; - border: none; - color: var(--accent); - font-size: 0.86rem; - font-weight: 600; - cursor: pointer; - padding: 0; -} - -.auth-switch button:hover { text-decoration: underline; } - -/* Separator */ -.auth-sep { - display: flex; - align-items: center; - gap: 0.75rem; - margin: 1.4rem 0 1rem; - color: var(--muted); - font-size: 0.78rem; - letter-spacing: 0.04em; -} - -.auth-sep::before, -.auth-sep::after { - content: ""; - flex: 1; - height: 1px; - background: var(--border); -} - -/* Google button — secondary style */ -.btn-auth-google { - display: flex; - align-items: center; - justify-content: center; - gap: 0.6rem; - width: 100%; - padding: 0.72rem 1rem; - background: transparent; - border: 1.5px solid var(--border); - border-radius: 11px; - color: var(--text); - font-size: 0.9rem; - font-weight: 600; - cursor: pointer; - transition: background 0.15s, border-color 0.15s; -} - -.btn-auth-google:hover:not(:disabled) { - background: var(--bg-subtle); - border-color: var(--accent); -} - -.btn-auth-google:disabled { opacity: 0.45; cursor: default; } - -[data-theme="dark"] .btn-auth-google { - border-color: rgba(255,255,255,0.14); -} - -/* Guest link */ -.auth-guest { - font-size: 0.84rem; - color: var(--muted); - text-decoration: none; - transition: color 0.15s; -} - -.auth-guest:hover { color: var(--text); } - -/* Done states (email sent / reset sent) */ -.auth-done { - width: 100%; - background: var(--card); - border: 1px solid var(--border); - border-radius: 20px; - padding: 2.4rem 1.8rem; - box-shadow: 0 4px 24px rgba(0,0,0,0.07); - text-align: center; - display: flex; - flex-direction: column; - align-items: center; - gap: 0.6rem; -} - -.auth-done-icon { font-size: 2.4rem; } - -.auth-done h2 { - margin: 0.2rem 0 0; - font-size: 1.1rem; - font-weight: 700; -} - -.auth-done p { - margin: 0; - color: var(--muted); - font-size: 0.9rem; - line-height: 1.6; -} - -.auth-done button { - margin-top: 0.6rem; - background: none; - border: none; - color: var(--accent); - font-size: 0.88rem; - font-weight: 600; - cursor: pointer; - text-decoration: underline; -} - -.upgrade-cta { - display: block; - margin-top: 0.8rem; - color: var(--accent); - font-weight: 600; -} - -/* ---- option input (multi-way) ---- */ -.opt-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 1rem; -} - -.opt-card { - position: relative; - display: flex; - flex-direction: column; - gap: 0.55rem; - min-height: 150px; - padding: 1.2rem; - border: 1px solid var(--border); - border-radius: 22px; - background: var(--card); - box-shadow: var(--shadow-md); - overflow: hidden; - transition: border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease; -} - -.opt-card:focus-within { - border-color: var(--accent); - box-shadow: 0 18px 44px rgba(52, 84, 232, 0.18); - transform: translateY(-2px); -} - -.opt-card-top { - display: flex; - align-items: center; - gap: 0.5rem; - z-index: 1; -} - -.opt-tag { - width: 28px; - height: 28px; - border-radius: 9999px; - display: inline-flex; - align-items: center; - justify-content: center; - font-size: 0.85rem; - font-weight: 700; - color: white; - background: var(--accent); -} - -.opt-0 .opt-tag { background: #3454e8; } -.opt-1 .opt-tag { background: #7c5cff; } -.opt-2 .opt-tag { background: #0ea5a3; } -.opt-3 .opt-tag { background: #f59e0b; } -.opt-4 .opt-tag { background: #ec4899; } - -.opt-label { - color: var(--muted); - font-size: 0.9rem; - flex: 1; -} - -.opt-remove { - border: none; - background: transparent; - color: #c7cdd8; - font-size: 1.2rem; - line-height: 1; - cursor: pointer; - padding: 0 0.2rem; -} - -.opt-remove:hover { color: #dc2626; } - -.opt-card input { - border: none; - outline: none; - font-size: 1.3rem; - font-weight: 600; - background: transparent; - padding: 0; - z-index: 1; -} - -.opt-card input::placeholder { - color: #c7cdd8; - font-weight: 500; -} - -.opt-watermark { - position: absolute; - right: 0.2rem; - bottom: -1.4rem; - font-size: 7rem; - font-weight: 800; - line-height: 1; - opacity: 0.07; - pointer-events: none; - color: var(--accent); -} - -.opt-0 .opt-watermark { color: #3454e8; } -.opt-1 .opt-watermark { color: #7c5cff; } -.opt-2 .opt-watermark { color: #0ea5a3; } -.opt-3 .opt-watermark { color: #f59e0b; } -.opt-4 .opt-watermark { color: #ec4899; } - -.opt-add-btn { - display: flex; - align-items: center; - gap: 0.5rem; - width: fit-content; - margin: 1rem auto 0; - border: 1px solid var(--border); - border-radius: 9999px; - background: white; - color: var(--muted); - font-size: 0.9rem; - font-weight: 600; - padding: 0.5rem 1.05rem; - cursor: pointer; - transition: border-color 0.16s ease, color 0.16s ease, background 0.16s ease; -} - -.opt-add-btn:hover { - border-color: var(--accent); - color: var(--accent); - background: rgba(52, 84, 232, 0.04); -} - -.opt-add-count { - font-size: 0.78rem; - opacity: 0.6; -} - -/* ---- result option chips ---- */ -.result-chips { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: center; - gap: 0.45rem; - margin: 0.2rem 0 1rem; -} - -.result-chip { - font-size: 0.85rem; - font-weight: 600; - color: var(--muted); - background: white; - border: 1px solid var(--border); - border-radius: 9999px; - padding: 0.3rem 0.8rem; -} - -.result-chip.win { - color: var(--accent); - border-color: rgba(52, 84, 232, 0.4); - background: rgba(52, 84, 232, 0.07); -} - -.result-chip.link { - display: inline-flex; - align-items: center; - gap: 0.3rem; - transition: border-color 0.16s ease, color 0.16s ease, box-shadow 0.16s ease; -} - -.result-chip.link:hover { - border-color: var(--accent); - color: var(--accent); - box-shadow: 0 4px 12px rgba(52, 84, 232, 0.14); -} - -.chip-ext { - font-size: 0.72rem; - opacity: 0.6; -} - -.result-vs { - font-size: 0.72rem; - font-weight: 700; - color: #c4c9d4; -} - -/* ---- per-option analysis ---- */ -.analysis-list { - display: flex; - flex-direction: column; - gap: 0.8rem; -} - -.analysis-item { - border: 1px solid var(--border); - border-radius: 16px; - padding: 1rem 1.1rem; - scroll-margin-top: 1.2rem; - transition: border-color 0.3s ease, box-shadow 0.3s ease; -} - -.analysis-item.winner-item { - border-color: rgba(52, 84, 232, 0.25); - border-left: 3px solid var(--accent); - background: linear-gradient(to right, rgba(52, 84, 232, 0.03), transparent 40%); -} - -[data-theme="dark"] .analysis-item.winner-item { - border-color: rgba(91, 120, 245, 0.3); - border-left: 3px solid #7c5cff; - background: linear-gradient(to right, rgba(124, 92, 255, 0.06), transparent 40%); -} - -.analysis-item:target { - border-color: var(--accent); - box-shadow: 0 0 0 3px var(--ring); -} - -.analysis-head { - display: flex; - align-items: center; - gap: 0.5rem; - margin-bottom: 0.5rem; -} - -.analysis-letter { - width: 24px; - height: 24px; - flex-shrink: 0; - border-radius: 7px; - display: inline-flex; - align-items: center; - justify-content: center; - font-size: 0.78rem; - font-weight: 700; - color: white; - background: linear-gradient(135deg, #3454e8, #7c5cff); -} - -.analysis-name { - font-weight: 700; - font-size: 1.02rem; -} - -.analysis-pick { - font-size: 0.72rem; - font-weight: 700; - color: var(--accent); - background: rgba(52, 84, 232, 0.1); - border-radius: 9999px; - padding: 0.15rem 0.5rem; -} - -.analysis-official { - margin-left: auto; - font-size: 0.82rem; - color: var(--muted); -} - -.analysis-official:hover { - color: var(--accent); -} - -.analysis-item p { - margin: 0; - line-height: 1.65; - word-break: break-word; - overflow-wrap: break-word; - color: var(--muted); - font-size: 0.9rem; -} - -/* ---- share + affiliate CTA ---- */ -.share-actions { - display: flex; - flex-direction: column; - gap: 0.8rem; - margin: 0; -} - -.share-card-preview { - position: relative; - overflow: hidden; - padding: 1rem; - border: 1px solid var(--border); - border-radius: 14px; - background: - linear-gradient(135deg, rgba(52, 84, 232, 0.1), rgba(15, 23, 42, 0.02) 48%, rgba(52, 84, 232, 0.12)), - var(--card); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.62); -} - -[data-theme="dark"] .share-card-preview { - background: - linear-gradient(135deg, rgba(91, 120, 245, 0.16), rgba(255, 255, 255, 0.04) 52%, rgba(91, 120, 245, 0.1)), - var(--card); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.07); -} - -.share-card-eyebrow { - display: block; - margin-bottom: 0.5rem; - color: var(--accent); - font-size: 0.68rem; - font-weight: 800; - letter-spacing: 0.08em; - text-transform: uppercase; -} - -.share-card-pick { - display: block; - color: var(--fg); - font-size: 1.02rem; - line-height: 1.25; - overflow-wrap: anywhere; -} - -.share-card-preview p { - margin: 0.45rem 0 0; - color: var(--muted); - font-size: 0.85rem; - line-height: 1.45; - overflow-wrap: anywhere; -} - -.btn-buy { - display: flex; - align-items: center; - gap: 0.65rem; - width: 100%; - padding: 1rem 1.15rem; - border-radius: 14px; - border: none; - background: #0f172a; - color: white; - font-size: 0.98rem; - font-weight: 700; - text-align: left; - cursor: pointer; - text-decoration: none; - transition: opacity 0.15s ease; -} - -[data-theme="dark"] .btn-buy { - background: #e2e8f4; - color: #0d0f14; -} - -[data-theme="dark"] .btn-buy .buy-icon { - background: rgba(0, 0, 0, 0.12); - color: #0d0f14; -} - -[data-theme="dark"] .btn-buy .buy-store { - background: rgba(0, 0, 0, 0.1); - color: #0d0f14; -} - -.btn-buy:hover { - opacity: 0.88; - text-decoration: none; -} - -.btn-buy:focus-visible, -.btn-share:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 3px; -} - -.buy-icon { - width: 22px; - height: 22px; - border-radius: 7px; - display: inline-flex; - align-items: center; - justify-content: center; - background: rgba(255, 255, 255, 0.15); - flex-shrink: 0; - font-size: 0.8rem; -} - -.buy-text { - display: block; - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.buy-copy { - flex: 1; - min-width: 0; -} - -.buy-hint { - display: block; - margin-top: 0.2rem; - color: rgba(255, 255, 255, 0.7); - font-size: 0.76rem; - font-weight: 500; - line-height: 1.2; -} - -[data-theme="dark"] .btn-buy .buy-hint { - color: rgba(13, 15, 20, 0.62); -} - -.buy-copy, -.buy-text { - overflow: hidden; - text-overflow: ellipsis; -} - -.buy-official-badge { - display: inline-block; - font-size: 0.65rem; - font-weight: 700; - background: rgba(255,255,255,0.22); - border-radius: 4px; - padding: 1px 5px; - margin-left: 5px; - vertical-align: middle; - letter-spacing: 0.02em; -} - -.buy-store { - margin-left: auto; - font-size: 0.8rem; - font-weight: 600; - opacity: 0.88; - background: rgba(255, 255, 255, 0.2); - border-radius: 8px; - padding: 0.2rem 0.55rem; - white-space: nowrap; -} - -.share-row { - display: flex; - gap: 0.55rem; -} - -.btn-share { - flex: 1; - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.85rem; - padding: 0.8rem 1rem; - border: 1px solid var(--border); - border-radius: 14px; - background: var(--card); - color: var(--fg); - font-size: 0.93rem; - font-weight: 600; - cursor: pointer; - transition: border-color 0.16s ease, color 0.16s ease, box-shadow 0.16s ease; -} - -.btn-share:disabled { - cursor: wait; - opacity: 0.72; -} - -.btn-share:hover { - border-color: var(--accent); - color: var(--accent); - box-shadow: 0 2px 8px rgba(52, 84, 232, 0.1); -} - -.share-hint { - flex-shrink: 0; - color: var(--muted); - font-size: 0.76rem; - font-weight: 500; - text-align: right; -} - -.affiliate-note { - display: grid; - gap: 0.25rem; - margin: 0; - font-size: 0.78rem; - color: var(--muted); - text-align: left; - line-height: 1.4; - overflow-wrap: anywhere; -} - -/* ════════════════════════════════════════ - TimingSection — 구매 타이밍 - ════════════════════════════════════════ */ - -.timing-section { - padding: 1.35rem 1.5rem 1.2rem; -} - -/* 헤더 행: 라벨 + 신호 점 */ -.ts-header { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 1rem; -} - -.ts-label { - font-size: .72rem; - font-weight: 600; - letter-spacing: .08em; - text-transform: uppercase; - color: var(--muted); -} - -/* 신호 점 — 이모지 없이 CSS로 */ -.ts-signal { - width: 8px; - height: 8px; - border-radius: 50%; - flex-shrink: 0; -} -.ts-green { background: #22c55e; box-shadow: 0 0 0 3px rgba(34,197,94,.18); } -.ts-amber { background: #f59e0b; box-shadow: 0 0 0 3px rgba(245,158,11,.18); } -.ts-red { background: #ef4444; box-shadow: 0 0 0 3px rgba(239,68,68,.18); } -.ts-gray { background: var(--muted); } - -[data-theme="dark"] .ts-green { box-shadow: 0 0 0 3px rgba(34,197,94,.25); } -[data-theme="dark"] .ts-amber { box-shadow: 0 0 0 3px rgba(245,158,11,.25); } -[data-theme="dark"] .ts-red { box-shadow: 0 0 0 3px rgba(239,68,68,.25); } - -/* 판정 텍스트 */ -.ts-verdict { - margin: 0 0 .3rem; - font-size: 1.12rem; - font-weight: 800; - letter-spacing: -.025em; - line-height: 1.2; - color: var(--fg); -} - -.ts-sub { - margin: 0 0 1.2rem; - font-size: .82rem; - color: var(--muted); - line-height: 1.6; -} - -/* 가격 게이지 */ -.ts-gauge-wrap { margin-bottom: .6rem; } - -.ts-gauge-track { - position: relative; - height: 3px; - background: var(--border); - border-radius: 99px; - overflow: visible; - margin-bottom: .55rem; -} - -.ts-gauge-fill { - height: 100%; - border-radius: 99px; - transition: width .5s cubic-bezier(.22,1,.36,1); -} - -.ts-fill-green { background: #22c55e; } -.ts-fill-amber { background: #f59e0b; } -.ts-fill-red { background: #ef4444; } -.ts-fill-gray { background: var(--border); } - -/* 현재 가격 마커 */ -.ts-gauge-cursor { - position: absolute; - top: 50%; - transform: translate(-50%, -50%); - width: 11px; - height: 11px; - border-radius: 50%; - background: var(--card); - border: 2px solid var(--fg); - box-shadow: 0 1px 4px rgba(0,0,0,.15); - transition: left .5s cubic-bezier(.22,1,.36,1); -} - -[data-theme="dark"] .ts-gauge-cursor { - box-shadow: 0 1px 6px rgba(0,0,0,.4); -} - -/* 최저가 / 현재 / 평균 레이블 */ -.ts-price-row { - display: flex; - justify-content: space-between; - font-size: .72rem; - color: var(--muted); - line-height: 1.3; - gap: .2rem; -} - -.ts-price-row span { - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.ts-price-row span:nth-child(2) { - text-align: center; -} - -.ts-price-row span:last-child { - text-align: right; -} - -.ts-price-current { - font-weight: 700; - color: var(--fg); -} - -@media (max-width: 400px) { - .ts-price-row { - font-size: .66rem; - } -} - -/* 다음 모델 힌트 */ -.ts-cycle { - display: flex; - align-items: center; - gap: .45rem; - padding-top: 1rem; - border-top: 1px solid var(--border-light); - font-size: .76rem; - color: var(--muted); - line-height: 1.4; -} - -.ts-cycle svg { flex-shrink: 0; opacity: .6; } - -/* ════════════════════════════════════════ - ContextCard — 맞춤 재분석 - ════════════════════════════════════════ */ - -/* 닫힌 상태: 미니멀 텍스트 버튼 */ -.cc-trigger { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - padding: 1rem 1.4rem; - background: var(--card); - border: 1px solid var(--border); - border-radius: 20px; - cursor: pointer; - text-align: left; - transition: border-color .18s, background .18s; - margin-top: .9rem; -} - -.cc-trigger:hover { - border-color: var(--muted); - background: var(--bg-subtle); -} - -[data-theme="dark"] .cc-trigger:hover { - background: rgba(255,255,255,.03); -} - -.cc-trigger-text { - display: flex; - flex-direction: column; - gap: .2rem; -} - -.cc-trigger-title { - font-size: .88rem; - font-weight: 700; - color: var(--fg); - line-height: 1.2; -} - -.cc-trigger-hint { - font-size: .76rem; - color: var(--muted); - font-weight: 400; -} - -.cc-chevron { - color: var(--muted); - flex-shrink: 0; - opacity: .6; -} - -/* 열린 상태 */ -.cc-card { - margin-top: .9rem; - border: 1px solid var(--border); - border-radius: 20px; - background: var(--card); - overflow: hidden; - box-shadow: var(--shadow-sm); -} - -.cc-head { - display: flex; - align-items: center; - justify-content: space-between; - padding: 1rem 1.4rem; - border-bottom: 1px solid var(--border-light); -} - -.cc-head-title { - font-size: .88rem; - font-weight: 700; - color: var(--fg); -} - -.cc-close { - display: flex; - align-items: center; - justify-content: center; - width: 26px; - height: 26px; - background: var(--bg-subtle); - border: 1px solid var(--border); - border-radius: 50%; - cursor: pointer; - color: var(--muted); - transition: border-color .15s, color .15s; - flex-shrink: 0; -} - -.cc-close:hover { - border-color: var(--fg); - color: var(--fg); -} - -[data-theme="dark"] .cc-close { - background: rgba(255,255,255,.05); -} - -.cc-form { - display: flex; - flex-direction: column; - gap: 1.1rem; - padding: 1.2rem 1.4rem 1.4rem; -} - -.cc-field { - display: flex; - flex-direction: column; - gap: .5rem; -} - -.cc-field-label { - font-size: .7rem; - font-weight: 700; - letter-spacing: .07em; - text-transform: uppercase; - color: var(--muted); - margin: 0; -} - -.cc-optional { - font-weight: 400; - text-transform: none; - letter-spacing: 0; -} - -.cc-chips { - display: flex; - flex-wrap: wrap; - gap: .4rem; -} - -.cc-chip { - padding: .38rem .82rem; - border: 1px solid var(--border); - border-radius: 99px; - background: transparent; - cursor: pointer; - font-size: .8rem; - color: var(--muted); - font-weight: 500; - transition: all .13s; - line-height: 1.4; -} - -.cc-chip:hover { - border-color: var(--fg); - color: var(--fg); -} - -.cc-chip-on { - border-color: var(--accent); - color: var(--accent); - background: color-mix(in srgb, var(--accent) 8%, var(--card)); - font-weight: 700; -} - -[data-theme="dark"] .cc-chip-on { - background: color-mix(in srgb, var(--accent) 14%, var(--card)); -} - -.cc-input { - padding: .6rem .9rem; - border: 1px solid var(--border); - border-radius: 10px; - background: var(--bg-subtle); - color: var(--fg); - font-size: .84rem; - line-height: 1.5; - transition: border-color .15s, background .15s; - font-family: inherit; -} - -.cc-input:focus { - outline: none; - border-color: var(--accent); - background: var(--card); -} - -.cc-input::placeholder { - color: var(--muted); - opacity: .7; -} - -[data-theme="dark"] .cc-input { - background: rgba(255,255,255,.05); -} - -[data-theme="dark"] .cc-input:focus { - background: rgba(255,255,255,.07); -} - -.cc-submit { - display: flex; - align-items: center; - justify-content: center; - gap: .5rem; - padding: .82rem 1rem; - background: var(--fg); - color: var(--bg); - border: none; - border-radius: 12px; - cursor: pointer; - font-size: .88rem; - font-weight: 700; - letter-spacing: -.01em; - transition: opacity .15s; - font-family: inherit; - margin-top: .1rem; -} - -.cc-submit:hover { opacity: .82; } - -[data-theme="dark"] .cc-submit { - background: var(--fg); - color: #0d0f14; -} - -/* ---- price comparison (live price + history) ---- */ -.price-head { - display: flex; - align-items: center; - gap: 0.6rem; - margin-bottom: 0.7rem; -} - -.price-head h2 { - margin: 0; -} - -.price-demo { - font-size: 0.7rem; - font-weight: 700; - color: var(--muted); - background: var(--bg); - border: 1px solid var(--border); - border-radius: 9999px; - padding: 0.12rem 0.5rem; -} - -.price-list { - list-style: none; - margin: 0; - padding: 0; -} - -.price-row { - display: flex; - align-items: center; - gap: 0.9rem; - padding: 0.75rem 0.6rem; - border-bottom: 1px solid var(--border); - border-radius: 10px; - flex-wrap: wrap; -} - -.price-row:last-child { - border-bottom: none; -} - -.price-row.cheapest { - background: rgba(52, 84, 232, 0.05); -} - -[data-theme="dark"] .price-row.cheapest { - background: rgba(91, 120, 245, 0.1); -} - -.price-main { - display: flex; - flex-direction: column; - gap: 0.3rem; - flex: 1; - min-width: 0; -} - -.price-name { - font-weight: 700; - font-size: 0.98rem; -} - -.price-tags { - display: flex; - gap: 0.35rem; -} - -.price-tag { - font-size: 0.68rem; - font-weight: 700; - border-radius: 9999px; - padding: 0.1rem 0.45rem; -} - -.cheapest-tag { - color: var(--accent); - background: rgba(52, 84, 232, 0.12); -} - -.deal-tag { - color: #0f9d6b; - background: rgba(15, 157, 107, 0.12); -} - -[data-theme="dark"] .deal-tag { - color: #34d399; - background: rgba(52, 211, 153, 0.14); -} - -.spark { - color: var(--muted); - flex-shrink: 0; -} - -.price-figures { - display: flex; - flex-direction: column; - align-items: flex-end; - gap: 0.15rem; - flex-shrink: 0; -} - -.price-now { - font-size: 1.05rem; - font-weight: 800; - letter-spacing: -0.01em; - color: var(--text); - transition: color 0.14s ease; -} - -.price-now:hover { - color: var(--accent); -} - -.price-go { - font-size: 0.75rem; - color: var(--muted); -} - -.price-sub { - font-size: 0.76rem; - color: var(--muted); -} - -@media (max-width: 480px) { - .price-figures { - align-items: flex-start; - width: 100%; - } -} - -/* ---- watch / track ---- */ -.watch-btn-wrap { - position: relative; - display: inline-block; -} - -.watch-btn { - display: inline-flex; - align-items: center; - gap: 0.3rem; - flex-shrink: 0; - border: 1px solid var(--border); - border-radius: 9999px; - background: var(--card); - color: var(--muted); - font-size: 0.78rem; - font-weight: 600; - padding: 0.34rem 0.7rem; - cursor: pointer; - transition: border-color 0.14s ease, color 0.14s ease, background 0.14s ease; -} - -.watch-btn:hover { - border-color: var(--accent); - color: var(--accent); -} - -.watch-btn.on { - border-color: var(--accent); - color: var(--accent); - background: rgba(52, 84, 232, 0.08); -} - -[data-theme="dark"] .watch-btn.on { - background: rgba(91, 120, 245, 0.14); -} - -/* Home watchlist card */ -.watch-card { - margin-top: 1.5rem; - border: 1px solid var(--border); - border-radius: 24px; - background: var(--card); - box-shadow: var(--shadow-sm); - overflow: hidden; -} - -.watch-head { - display: flex; - align-items: center; - gap: 0.55rem; - padding: 0.85rem 1.2rem; - border-bottom: 1px solid var(--border); -} - -.watch-head h3 { - margin: 0; - font-size: 0.92rem; - font-weight: 700; -} - -.watch-count { - font-size: 0.7rem; - font-weight: 700; - color: var(--accent); - background: rgba(52, 84, 232, 0.1); - border-radius: 9999px; - padding: 0.1rem 0.5rem; -} - -.watch-items { - list-style: none; - margin: 0; - padding: 0; -} - -.watch-item { - display: flex; - align-items: center; - justify-content: space-between; - gap: 1rem; - padding: 0.85rem 1.2rem; - border-bottom: 1px solid var(--border); -} - -.watch-item:last-child { - border-bottom: none; -} - -.watch-name { - font-weight: 600; - font-size: 0.95rem; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.watch-right { - display: flex; - align-items: center; - gap: 0.8rem; - flex-shrink: 0; -} - -.watch-price { - font-weight: 800; - font-size: 0.96rem; - color: var(--text); - transition: color 0.14s ease; -} - -.watch-price:hover { - color: var(--accent); -} - -.watch-remove { - width: 28px; - height: 28px; - border: 1px solid transparent; - border-radius: 8px; - background: transparent; - color: #b8c0d0; - font-size: 1.05rem; - line-height: 1; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - transition: background 0.14s ease, color 0.14s ease, border-color 0.14s ease; -} - -.watch-remove:hover { - background: rgba(220, 38, 38, 0.08); - border-color: rgba(220, 38, 38, 0.2); - color: #dc2626; -} - -.watch-note { - margin: 0; - padding: 0.7rem 1.2rem 0.9rem; - font-size: 0.8rem; - color: var(--muted); - border-top: 1px solid var(--border); -} - -/* ---- watch push prompt ---- */ -.watch-push-prompt { - position: absolute; - top: calc(100% + 8px); - left: 50%; - transform: translateX(-50%); - z-index: 200; - background: var(--card); - border: 1px solid var(--border); - border-radius: 12px; - padding: 1rem 1.1rem 0.9rem; - width: 240px; - box-shadow: 0 8px 24px rgba(0,0,0,0.12); -} - -[data-theme="dark"] .watch-push-prompt { - box-shadow: 0 8px 24px rgba(0,0,0,0.4); -} - -.watch-push-sub { - margin: 0 0 0.8rem; - font-size: 0.76rem; - color: var(--muted); - padding-right: 1rem; - line-height: 1.4; -} - -.watch-push-denied { - padding: 0.7rem 1rem; -} - -.watch-push-denied-msg { - margin: 0; - font-size: 0.78rem; - color: var(--muted); - padding-right: 1.4rem; - line-height: 1.4; -} - -/* ---- watch email modal ---- */ -.watch-email-modal { - position: absolute; - top: calc(100% + 8px); - left: 50%; - transform: translateX(-50%); - z-index: 200; - background: var(--card); - border: 1px solid var(--border); - border-radius: 12px; - padding: 1rem 1.1rem 0.9rem; - width: 260px; - box-shadow: 0 8px 24px rgba(0,0,0,0.12); -} - -[data-theme="dark"] .watch-email-modal { - box-shadow: 0 8px 24px rgba(0,0,0,0.4); -} - -.watch-email-close { - position: absolute; - top: 0.5rem; - right: 0.6rem; - background: none; - border: none; - font-size: 1rem; - color: var(--muted); - cursor: pointer; - padding: 0 0.2rem; - line-height: 1; -} - -.watch-email-label { - margin: 0 0 0.7rem; - font-size: 0.82rem; - font-weight: 600; - color: var(--fg); - padding-right: 1rem; -} - -.watch-email-form { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.watch-email-input { - width: 100%; - padding: 0.45rem 0.6rem; - border: 1px solid var(--border); - border-radius: 7px; - background: var(--bg); - color: var(--fg); - font-size: 0.82rem; - box-sizing: border-box; -} - -.watch-email-input:focus { - outline: none; - border-color: var(--accent, #3b82f6); -} - -.watch-email-confirm { - width: 100%; - padding: 0.45rem; - background: var(--fg); - color: var(--bg); - border: none; - border-radius: 7px; - font-size: 0.82rem; - font-weight: 600; - cursor: pointer; -} - -.watch-email-confirm:disabled { - opacity: 0.5; - cursor: default; -} - -.watch-email-skip { - display: block; - width: 100%; - margin-top: 0.4rem; - background: none; - border: none; - font-size: 0.75rem; - color: var(--muted); - cursor: pointer; - text-align: center; - padding: 0.2rem 0; -} - -.watch-email-skip:hover { - color: var(--fg); -} - -/* ---- share watermark (Free shared pages) ---- */ -.share-watermark { - position: fixed; - bottom: 0; - left: 0; - right: 0; - background: white; - border-top: 1px solid var(--border); - padding: 1rem 1.2rem 1.4rem; - text-align: center; - box-shadow: 0 -8px 32px rgba(17, 24, 39, 0.1); - z-index: 10; -} - -.share-watermark p { - margin: 0 0 0.1rem; - font-size: 0.95rem; - color: var(--muted); -} - -.share-watermark strong { - color: var(--text); -} - -.vs-upsell { - text-align: center; - color: var(--muted); - font-size: 0.9rem; - margin: 0.9rem 0 0; -} - -.vs-upsell a { - color: var(--accent); - font-weight: 600; -} - -/* ---- comparison table (N columns) ---- */ -.cmp-table { - display: flex; - flex-direction: column; - width: 100%; -} - -.cmp-row { - display: grid; - grid-template-columns: minmax(80px, 0.85fr) repeat(var(--cols), minmax(0, 1fr)); - column-gap: 0.5rem; - align-items: start; -} - -.cmp-key, -.cmp-col { - padding: 0.7rem 0.6rem; - border-bottom: 1px solid var(--border); -} - -.cmp-row:last-child .cmp-key, -.cmp-row:last-child .cmp-col { - border-bottom: none; -} - -.cmp-key { - color: var(--muted); - font-size: 0.85rem; - font-weight: 600; - align-self: start; - padding-top: 0.9rem; - min-width: 0; - word-break: keep-all; -} - -.cmp-col { - font-size: 0.9rem; - line-height: 1.4; - word-break: break-word; - overflow-wrap: break-word; - min-width: 0; -} - -.cmp-head .cmp-col { - font-weight: 700; - color: var(--text); - display: flex; - flex-direction: column; - gap: 0.3rem; - padding-top: 0.55rem; -} - -/* Continuous winner highlight band */ -.cmp-col.winner { - background: rgba(52, 84, 232, 0.06); - color: var(--accent); -} - -[data-theme="dark"] .cmp-col.winner { - background: rgba(91, 120, 245, 0.12); -} - -.cmp-head .cmp-col.winner { - border-top-left-radius: 12px; - border-top-right-radius: 12px; - color: var(--accent); -} - -.cmp-row:last-child .cmp-col.winner { - border-bottom-left-radius: 12px; - border-bottom-right-radius: 12px; -} - -.cmp-win-tag { - align-self: flex-start; - font-size: 0.62rem; - font-weight: 700; - color: white; - background: var(--accent); - border-radius: 9999px; - padding: 0.08rem 0.5rem; - letter-spacing: 0.02em; -} - -/* ---- account hero / usage ---- */ -.account-hero { - display: flex; - flex-direction: column; - align-items: center; - gap: 0.6rem; - text-align: center; - margin: 1rem 0 1.4rem; -} - -.account-avatar { - width: 64px; - height: 64px; - border-radius: 20px; - display: inline-flex; - align-items: center; - justify-content: center; - background: linear-gradient(135deg, #3454e8, #7c5cff); - color: white; - font-size: 1.8rem; - font-weight: 700; -} - -.nickname-view { - display: flex; - align-items: center; - gap: 0.6rem; -} - -.nickname-view h1 { - margin: 0; - font-size: 1.7rem; -} - -.nickname-edit { - border: 1px solid var(--border); - background: white; - border-radius: 9999px; - font-size: 0.8rem; - color: var(--muted); - padding: 0.25rem 0.7rem; - cursor: pointer; - transition: border-color 0.16s ease, color 0.16s ease; -} - -.nickname-edit:hover { - border-color: var(--accent); - color: var(--accent); -} - -.nickname-edit-row { - width: 100%; - max-width: 320px; -} - -.nickname-edit-row input { - width: 100%; - border: 1px solid var(--border); - border-radius: 12px; - padding: 0.8rem; - font-size: 1.1rem; - text-align: center; -} - -.nickname-edit-row input:focus { - outline: none; - border-color: var(--accent); - box-shadow: 0 0 0 4px var(--ring); -} - -.nickname-actions { - display: flex; - gap: 0.5rem; - margin-top: 0.6rem; -} - -.nickname-actions .btn-primary, -.nickname-actions .btn-outline { - margin-top: 0; -} - -.usage-head { - display: flex; - justify-content: space-between; - align-items: baseline; -} - -.usage-head h2 { margin: 0; } - -.usage-count { - font-weight: 700; - color: var(--accent); -} - -.usage-bar { - height: 10px; - border-radius: 9999px; - background: var(--bg); - overflow: hidden; - margin: 0.8rem 0 0.6rem; -} - -.usage-fill { - display: block; - height: 100%; - border-radius: 9999px; - background: linear-gradient(90deg, #3454e8, #7c5cff); - transition: width 0.3s ease; -} - -.usage-sub { - margin: 0; - color: var(--muted); - font-size: 0.88rem; -} - -/* ---- history ---- */ -.history-head { - display: flex; - align-items: center; - gap: 0.55rem; - padding: 0.85rem 1.2rem; - border-bottom: 1px solid var(--border); -} - -.history-head h3 { - margin: 0; - font-size: 0.92rem; - font-weight: 700; -} - -.history-count { - font-size: 0.7rem; - font-weight: 700; - color: var(--muted); - background: var(--bg); - border-radius: 9999px; - padding: 0.1rem 0.5rem; -} - -.history-items { - list-style: none; - padding: 0; - margin: 0; -} - -.history-item { - display: flex; - align-items: center; - gap: 0; - border-bottom: 1px solid var(--border); - transition: background 0.14s ease; -} - -.history-item:last-child { border-bottom: none; } - -.history-item:hover { - background: rgba(52, 84, 232, 0.03); -} - -[data-theme="dark"] .history-item:hover { - background: rgba(255, 255, 255, 0.03); -} - -/* Link area fills the row */ -.history-item-main { - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - gap: 0.4rem; - padding: 0.95rem 0.5rem 0.95rem 1.2rem; - text-decoration: none; -} - -/* Matchup: what was compared */ -.history-matchup { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 0.4rem; - font-size: 0.96rem; - line-height: 1.3; -} - -.hm-part { - display: inline-flex; - align-items: center; - gap: 0.4rem; -} - -.hm-opt { - color: var(--text); - font-weight: 600; -} - -.hm-win { - color: var(--text); - font-weight: 600; -} - -.hm-vs { - font-size: 0.64rem; - font-weight: 700; - letter-spacing: 0.05em; - text-transform: uppercase; - color: #c4c9d4; -} - -/* Verdict: which one AXIS picked */ -.history-verdict { - display: inline-flex; - align-items: center; - gap: 0.38rem; - font-size: 0.82rem; - font-weight: 700; - color: var(--accent); - transition: opacity 0.14s ease; -} - -.verdict-mark { - display: inline-flex; - align-items: center; - justify-content: center; - width: 16px; - height: 16px; - flex-shrink: 0; - border-radius: 9999px; - background: rgba(52, 84, 232, 0.12); - font-size: 0.58rem; - font-weight: 800; -} - -[data-theme="dark"] .verdict-mark { - background: rgba(91, 120, 245, 0.2); -} - -.history-item:hover .history-matchup { - opacity: 0.85; -} - -/* Date + delete button */ -.history-item-meta { - display: flex; - align-items: center; - gap: 0.3rem; - flex-shrink: 0; - padding-right: 0.6rem; -} - -.history-item-date { - color: var(--muted); - font-size: 0.75rem; - white-space: nowrap; - padding-right: 0.2rem; -} - -@media (max-width: 400px) { - .history-item-date { display: none; } -} - -.history-delete { - width: 30px; - height: 30px; - border: 1px solid transparent; - border-radius: 8px; - background: transparent; - color: #b8c0d0; - font-size: 1.1rem; - line-height: 1; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0; - transition: background 0.14s ease, border-color 0.14s ease, color 0.14s ease; -} - -.history-delete:hover { - background: rgba(220, 38, 38, 0.08); - border-color: rgba(220, 38, 38, 0.2); - color: #dc2626; -} - -.history-delete:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -/* Empty / login prompt */ -.history-empty { - display: flex; - flex-direction: column; - align-items: center; - gap: 0.3rem; - padding: 2rem 1.5rem; - text-align: center; -} - -.history-empty p { - margin: 0; - color: var(--muted); - font-size: 0.88rem; -} - -.history-empty-cta { - margin-top: 0.2rem; - color: var(--accent); - font-weight: 600; - font-size: 0.9rem; -} - -@media (max-width: 820px) { - .example-grid { - grid-template-columns: 1fr; - } - - .home-method { - grid-template-columns: 1fr; - } - - .feature-grid { - grid-template-columns: repeat(2, 1fr); - } -} - -@media (max-width: 560px) { - .container { - width: 100%; - padding: 0.75rem 1.2rem 3rem; - } - - .topbar { - margin-bottom: 1.2rem; - } - - .hero { - margin: 0.6rem 0 1rem; - } - - .hero h1 { - font-size: 1.65rem; - } - - .sub { - font-size: 0.93rem; - } - - .hero-proof { - gap: 0.4rem; - } - - .proof-pill { - font-size: 0.76rem; - padding: 0.32rem 0.65rem; - } - - .home-section { - margin-top: 1.8rem; - } - - /* Verdict hero: tighter on mobile */ - .verdict-hero { - padding: 1.4rem 1.25rem 1.3rem; - border-radius: 18px; - } - - .vh-title { - font-size: 1.6rem; - letter-spacing: -0.025em; - } - - .vh-conclusion { - font-size: 0.94rem; - } - - .vh-matchup { - margin-top: 1.3rem; - padding-top: 1.2rem; - gap: 0.85rem; - } - - .detail-card { - padding: 1rem 1.1rem; - border-radius: 16px; - margin-top: 0.75rem; - } - - .detail-card h2 { - font-size: 0.82rem; - margin-bottom: 0.85rem; - } - - /* Buttons: not too huge on mobile */ - .btn-buy { - padding: 0.9rem 1rem; - font-size: 0.92rem; - border-radius: 12px; - } - - .btn-share { - align-items: flex-start; - flex-direction: column; - gap: 0.2rem; - padding: 0.65rem 1rem; - font-size: 0.88rem; - border-radius: 12px; - } - - .share-hint { - text-align: left; - } - - .share-actions-wrap { - padding: 0.9rem 1.1rem 1.1rem; - } - - .btn-primary { - padding: 0.82rem 1rem; - font-size: 0.95rem; - border-radius: 12px; - margin-top: 0.9rem; - } - - .buy-store { - font-size: 0.75rem; - } - - /* Input on mobile */ - .vs-card { - padding: 0.9rem; - min-height: 148px; - } - - .opt-submit { - margin-top: 0.9rem; - } - - /* Share actions spacing */ - .share-actions { - gap: 0.55rem; - } - - /* Reason list */ - .reason-list li { - font-size: 0.9rem; - padding: 0.65rem 0; - } - - /* Back button */ - .btn-back { - font-size: 0.85rem; - margin-bottom: 0.7rem; - } - - /* Results body spacing */ - .results-body { - padding-bottom: 2.5rem; - } - - /* Topbar: tighten brand + nav gap */ - .brand { - font-size: 1.45rem; - } - - .topbar-right { - gap: 0.3rem; - } - - /* Input cards: compact */ - .opt-card { - min-height: 128px; - padding: 0.9rem 1rem; - } - - .vs-card input, - .opt-card input { - font-size: 1.15rem; - } - - .buy-timing-block { - border-radius: 16px; - } - - .ts-verdict { - font-size: 1rem; - } -} - -/* ── compare pages ─────────────────────────────────────────────────────── */ - -.compare-breadcrumb { - display: flex; - align-items: center; - gap: 0.4rem; - padding: 0.9rem 1.2rem 0; - font-size: 0.8rem; - color: var(--muted); - max-width: 680px; - margin: 0 auto; -} - -.compare-breadcrumb a { - color: var(--muted); - text-decoration: none; -} - -.compare-breadcrumb a:hover { color: var(--fg); } - -/* related comparisons */ -.compare-related { - max-width: 680px; - margin: 0 auto 2rem; - padding: 1.4rem 1.2rem; - background: var(--card); - border: 1px solid var(--border); - border-radius: 12px; -} - -.compare-related-title { - font-size: 1rem; - font-weight: 600; - margin: 0 0 1rem; -} - -.compare-related-list { - list-style: none; - margin: 0; - padding: 0; - display: flex; - flex-direction: column; - gap: 0.8rem; -} - -.compare-related-link { - font-size: 0.9rem; - font-weight: 500; - color: var(--fg); - text-decoration: none; -} - -.compare-related-link:hover { text-decoration: underline; } - -.compare-related-desc { - margin: 0.15rem 0 0; - font-size: 0.78rem; - color: var(--muted); -} - -/* CTA */ -.compare-cta { - max-width: 680px; - margin: 0 auto 3rem; - padding: 1.4rem 1.2rem; - background: var(--card); - border: 1px solid var(--border); - border-radius: 12px; - text-align: center; -} - -.compare-cta-copy { - margin: 0 0 1rem; - font-size: 0.9rem; - color: var(--muted); -} - -.compare-cta-btn { - display: inline-block; - padding: 0.7rem 1.4rem; - font-size: 0.9rem; -} - -/* index page */ -.compare-index-section { - margin-bottom: 2rem; -} - -.compare-index-category { - font-size: 0.78rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: .07em; - color: var(--muted); - margin: 0 0 0.8rem; -} - -.compare-index-list { - list-style: none; - margin: 0; - padding: 0; - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.compare-index-item { - border: 1px solid var(--border); - border-radius: 10px; - background: var(--card); -} - -.compare-index-link { - display: block; - padding: 0.85rem 1rem; - text-decoration: none; - color: var(--fg); -} - -.compare-index-link:hover { background: var(--hover, rgba(0,0,0,.03)); border-radius: 10px; } - -.compare-index-title { - font-size: 0.92rem; - font-weight: 600; -} - -.compare-index-desc { - margin: 0.2rem 0 0; - font-size: 0.78rem; - color: var(--muted); -} - -/* ═══════════════════════════════════════════════════════════════════════════ - Analyze Overlay — "Axis에게 맡기기" 클릭 후 프리미엄 분석 로딩 화면 - ═══════════════════════════════════════════════════════════════════════════ */ - -@keyframes overlayFadeIn { - from { opacity: 0; } - to { opacity: 1; } -} - -@keyframes modalRise { - from { - opacity: 0; - transform: translateY(28px) scale(0.96); - } - to { - opacity: 1; - transform: translateY(0) scale(1); - } -} - -@keyframes indeterminate { - 0% { transform: translateX(-100%) scaleX(0.4); } - 40% { transform: translateX(30%) scaleX(0.8); } - 100% { transform: translateX(350%) scaleX(0.4); } -} - -@keyframes pillGlow { - 0%, 100% { border-color: rgba(255,255,255,0.12); } - 50% { border-color: rgba(91,120,245,0.5); box-shadow: 0 0 18px rgba(91,120,245,0.25); } -} - -.analyze-overlay { - position: fixed; - inset: 0; - background: rgba(6, 8, 16, 0.90); - backdrop-filter: blur(18px); - -webkit-backdrop-filter: blur(18px); - z-index: 9999; - display: flex; - align-items: center; - justify-content: center; - animation: overlayFadeIn 0.22s ease; -} - -.analyze-modal { - width: min(440px, 88vw); - padding: 2.8rem 2rem 2.4rem; - background: rgba(255,255,255,0.03); - border: 1px solid rgba(255,255,255,0.07); - border-radius: 28px; - text-align: center; - animation: modalRise 0.32s cubic-bezier(0.18, 0, 0.08, 1); -} - -.analyze-logo { - font-size: 0.78rem; - font-weight: 800; - letter-spacing: 0.16em; - text-transform: uppercase; - color: rgba(255,255,255,0.22); - margin-bottom: 2rem; -} - -.analyze-comparing { - display: flex; - align-items: center; - justify-content: center; - flex-wrap: wrap; - gap: 0.8rem; - margin-bottom: 2.2rem; - min-height: 2.6rem; -} - -.analyze-pill { - display: inline-block; - padding: 0.55rem 1.05rem; - background: rgba(255,255,255,0.05); - border: 1px solid rgba(255,255,255,0.12); - border-radius: 100px; - font-size: 0.95rem; - font-weight: 600; - color: #e8ecf8; - animation: pillGlow 2.4s ease-in-out infinite; -} - -.analyze-vs-badge { - flex-shrink: 0; - font-size: 0.62rem; - font-weight: 700; - letter-spacing: 0.12em; - text-transform: uppercase; - color: rgba(255,255,255,0.25); - padding: 0 0.1rem; -} - -.analyze-bar { - width: 100%; - height: 2px; - background: rgba(255,255,255,0.05); - border-radius: 2px; - overflow: hidden; - margin: 0 auto 1.8rem; - max-width: 240px; -} - -.analyze-progress { - height: 100%; - width: 40%; - background: linear-gradient(90deg, transparent 0%, #5b78f5 40%, #9474ff 70%, transparent 100%); - border-radius: 2px; - transform-origin: left; - animation: indeterminate 1.5s ease-in-out infinite; -} - -.analyze-status { - color: rgba(255,255,255,0.3); - font-size: 0.82rem; - letter-spacing: 0.02em; -} - -/* ── 버튼 로딩 상태 ──────────────────────────────────────────────────── */ - -@keyframes spinBtn { - to { transform: rotate(360deg); } -} - -.btn-loading { - opacity: 0.7 !important; - cursor: not-allowed; -} - -.btn-loading-inner { - display: inline-flex; - align-items: center; - gap: 0.55rem; -} - -.btn-spinner { - display: inline-block; - width: 16px; - height: 16px; - border: 2px solid rgba(255,255,255,0.25); - border-top-color: white; - border-radius: 50%; - animation: spinBtn 0.7s linear infinite; - flex-shrink: 0; -} - -/* ── 결과 없는 상태 (/results 직접 접근) ───────────────────────────── */ - -.results-empty-state { - min-height: 60vh; - display: flex; - align-items: center; - justify-content: center; -} - -.results-empty-inner { - text-align: center; - padding: 3rem 1.5rem; - max-width: 380px; -} - -.results-empty-icon { - font-size: 2.5rem; - display: block; - margin-bottom: 1.2rem; - opacity: 0.7; -} - -.results-empty-title { - font-size: 1.4rem; - font-weight: 700; - margin-bottom: 0.75rem; - color: var(--fg); -} - -.results-empty-sub { - color: var(--muted); - font-size: 0.95rem; - line-height: 1.65; - margin-bottom: 2rem; -} - -.results-empty-cta { - display: inline-block; - width: auto; - padding: 0.85rem 2rem; - font-size: 1rem; - text-decoration: none; - margin-top: 0; -} - -/* ── Fit score bars (스펙 우위 비율) ─────────────────────────────────────── */ -.fit-bars { - margin-top: 1.6rem; - padding-top: 1.4rem; - border-top: 1px solid var(--border); - display: flex; - flex-direction: column; - gap: 0.65rem; -} - -.fit-bar-row { - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(0, 140px); - align-items: center; - gap: 0.75rem; -} - -@media (max-width: 480px) { - .fit-bar-row { - grid-template-columns: minmax(0, 1fr) minmax(0, 90px); - gap: 0.5rem; - } -} - -.fit-bar-name { - font-size: 0.82rem; - font-weight: 600; - color: var(--muted); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - min-width: 0; -} - -.fit-winner .fit-bar-name { - color: var(--fg); - font-weight: 700; -} - -.fit-bar-track { - width: 100%; - height: 6px; - background: var(--border); - border-radius: 99px; - overflow: hidden; -} - -.fit-bar-fill { - height: 100%; - background: var(--muted); - border-radius: 99px; - transition: width 0.6s cubic-bezier(0.22, 1, 0.36, 1); -} - -.fit-winner .fit-bar-fill { - background: linear-gradient(90deg, var(--accent), #6c8fff); -} - -.fit-bar-note { - font-size: 0.7rem; - color: var(--muted); - opacity: 0.55; - margin: 0.4rem 0 0; - line-height: 1.4; -} - - -/* ---- coverage note ---- */ -.coverage-note { - margin: 0.55rem 0 0; - color: color-mix(in srgb, var(--text) 62%, var(--muted)); - font-size: 0.84rem; - font-weight: 700; - line-height: 1.5; - letter-spacing: 0; -} - -.coverage-note-inline { - margin: 0.7rem 0 0.35rem; -} - -.coverage-note-home { - margin: 0.75rem auto 0; - max-width: 36rem; - text-align: center; - padding: 0.55rem 0.85rem; - border: 1px solid var(--border-light); - border-radius: 10px; - background: color-mix(in srgb, var(--bg-subtle) 88%, transparent); -} - -/* ---- spec graphs (compare bars) ---- */ -.spec-graph-section { - display: grid; - gap: 1rem; -} - -.spec-graph-list { - display: grid; - gap: 1.05rem; -} - -.spec-graph-selector { - display: grid; - gap: 0.55rem; -} - -.spec-graph-selector-label { - font-size: 0.8rem; - font-weight: 700; - color: color-mix(in srgb, var(--text) 62%, var(--muted)); -} - -.spec-graph-chips { - display: flex; - flex-wrap: wrap; - gap: 0.45rem; -} - -.spec-graph-chip { - border: 1px solid var(--border-light); - background: color-mix(in srgb, var(--bg) 88%, var(--bg-subtle)); - color: color-mix(in srgb, var(--text) 72%, var(--muted)); - border-radius: 9999px; - padding: 0.38rem 0.72rem; - font-size: 0.78rem; - font-weight: 700; - cursor: pointer; - transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease; -} - -.spec-graph-chip:hover { - border-color: color-mix(in srgb, var(--accent) 35%, var(--border-light)); - color: var(--text); -} - -.spec-graph-chip.is-on { - background: color-mix(in srgb, var(--accent) 14%, var(--bg)); - border-color: color-mix(in srgb, var(--accent) 45%, var(--border-light)); - color: var(--accent); -} - -.spec-graph-chip:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 2px; -} - -.spec-bar-card { - padding: 0.95rem 1rem 0.85rem; - border: 1px solid var(--border-light); - border-radius: 12px; - background: var(--bg-subtle); -} - -.spec-graph-label-row { - display: flex; - align-items: baseline; - justify-content: space-between; - gap: 0.75rem; - margin-bottom: 0.85rem; -} - -.spec-graph-label { - margin: 0; - font-size: 0.95rem; - font-weight: 800; - color: var(--text); -} - -.spec-bar-compare { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(4.5rem, 1fr)); - gap: 0.85rem; - align-items: end; - min-height: 11.5rem; -} - -.spec-bar-col { - display: grid; - grid-template-rows: auto 1fr auto; - gap: 0.4rem; - justify-items: center; - min-width: 0; -} - -.spec-bar-value { - font-size: 0.8rem; - font-weight: 800; - color: color-mix(in srgb, var(--text) 62%, var(--muted)); - font-variant-numeric: tabular-nums; - text-align: center; - line-height: 1.2; -} - -.spec-bar-track { - width: 100%; - max-width: 3.2rem; - height: 8.5rem; - border-radius: 9999px; - background: color-mix(in srgb, var(--border-light) 88%, var(--border)); - display: flex; - align-items: flex-end; - overflow: hidden; - padding: 0.22rem; -} - -.spec-bar-fill { - width: 100%; - border-radius: 9999px; - background: color-mix(in srgb, var(--text) 28%, var(--border)); - transition: height 0.7s cubic-bezier(0.22, 1, 0.36, 1); - min-height: 0.55rem; -} - -.spec-bar-name { - font-size: 0.78rem; - font-weight: 700; - color: var(--text); - text-align: center; - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - - - - - -@media (max-width: 640px) { - .spec-bar-card { - padding: 0.8rem 0.75rem 0.7rem; - } - - .spec-graph-label-row { - flex-direction: column; - align-items: flex-start; - gap: 0.25rem; - } - - .spec-bar-compare { - gap: 0.55rem; - min-height: 10.5rem; - } - - .spec-bar-track { - height: 7.6rem; - max-width: 2.7rem; - } - - .spec-bar-value { - font-size: 0.74rem; - } - - .spec-bar-name { - font-size: 0.72rem; - } -} +@import "./styles/home.css"; +@import "./styles/results.css"; +@import "./styles/auth.css"; +@import "./styles/commerce.css"; diff --git a/app/page.tsx b/app/page.tsx index 7fa2512..ee6d784 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -66,14 +66,6 @@ export default async function Home() { {t.home.hero2}

    {t.home.sub}

    -
    - {t.home.proof.map((item) => ( - - {item.value} - {item.label} - - ))} -
diff --git a/app/styles/auth.css b/app/styles/auth.css new file mode 100644 index 0000000..7279f05 --- /dev/null +++ b/app/styles/auth.css @@ -0,0 +1,796 @@ +/* ════════════════════════════════════════ + Auth page — redesigned + ════════════════════════════════════════ */ + +.auth-page { + min-height: 100svh; + display: flex; + align-items: center; + justify-content: center; + padding: 2rem 1.5rem; + background: var(--bg); +} + +.auth-wrap { + position: relative; + z-index: 1; + width: 100%; + max-width: 360px; + display: flex; + flex-direction: column; + align-items: center; + gap: 2rem; +} + +/* Brand */ +.auth-brand { + text-align: center; +} + +.auth-logo { + display: inline-flex; + align-items: center; + gap: 0.5rem; + font-size: 1.95rem; + font-weight: 800; + color: var(--text); + letter-spacing: -0.04em; + text-decoration: none; +} + +.auth-logo-mark { + display: flex; + align-items: center; + color: var(--accent); + flex-shrink: 0; +} + +.auth-tagline { + margin: 0.55rem 0 0; + font-size: 1rem; + font-weight: 600; + color: var(--text); + letter-spacing: -0.01em; +} + +/* Subtle dot-grid background */ +.auth-page::before { + content: ""; + position: fixed; + inset: 0; + background-image: radial-gradient(var(--border) 1px, transparent 1px); + background-size: 28px 28px; + opacity: 0.45; + pointer-events: none; + z-index: 0; +} + +/* Form wrapper — open, no card border */ +.auth-form-wrap { + width: 100%; + display: flex; + flex-direction: column; + gap: 0; + background: var(--card); + border: 1px solid var(--border); + border-radius: 20px; + padding: 2rem 1.8rem; + box-shadow: 0 4px 24px rgba(0,0,0,0.07), 0 1px 4px rgba(0,0,0,0.04); +} + +[data-theme="dark"] .auth-form-wrap { + box-shadow: 0 4px 24px rgba(0,0,0,0.3); +} + +/* Fields */ +.auth-fields { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.auth-field { + display: flex; + flex-direction: column; + gap: 0.4rem; +} + +.auth-field label { + font-size: 0.84rem; + font-weight: 600; + color: var(--text); +} + +.auth-field-row { + display: flex; + align-items: center; + justify-content: space-between; +} + +.auth-field input { + width: 100%; + padding: 0.78rem 0.95rem; + border: 1.5px solid var(--border); + border-radius: 10px; + background: var(--bg); + color: var(--text); + font-size: 0.96rem; + font-family: inherit; + transition: border-color 0.15s, box-shadow 0.15s; + box-sizing: border-box; +} + +.auth-field input:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--ring); +} + +[data-theme="dark"] .auth-field input { + background: rgba(255,255,255,0.05); + border-color: rgba(255,255,255,0.12); + color: var(--text); +} + +/* Error */ +.auth-error { + margin: 0; + padding: 0.6rem 0.8rem; + background: rgba(220, 38, 38, 0.06); + border: 1px solid rgba(220, 38, 38, 0.2); + border-radius: 8px; + color: #dc2626; + font-size: 0.84rem; + line-height: 1.45; +} + +/* Forgot password — inline next to label */ +.auth-forgot { + background: none; + border: none; + color: var(--muted); + font-size: 0.82rem; + cursor: pointer; + padding: 0; + transition: color 0.15s; +} + +.auth-forgot:hover { color: var(--accent); } + +/* Primary CTA */ +.btn-auth-primary { + margin-top: 0.5rem; + width: 100%; + padding: 0.82rem 1rem; + background: var(--accent); + border: none; + border-radius: 11px; + color: white; + font-size: 0.97rem; + font-weight: 700; + cursor: pointer; + transition: opacity 0.15s, box-shadow 0.15s; + box-shadow: 0 2px 10px rgba(52, 84, 232, 0.3); +} + +.btn-auth-primary:hover:not(:disabled) { + opacity: 0.92; + box-shadow: 0 4px 16px rgba(52, 84, 232, 0.4); +} + +.btn-auth-primary:disabled { + opacity: 0.45; + cursor: default; + box-shadow: none; +} + +/* Mode switch link */ +.auth-switch { + margin: 1rem 0 0; + text-align: center; + font-size: 0.86rem; + color: var(--muted); +} + +.auth-switch button { + background: none; + border: none; + color: var(--accent); + font-size: 0.86rem; + font-weight: 600; + cursor: pointer; + padding: 0; +} + +.auth-switch button:hover { text-decoration: underline; } + +/* Separator */ +.auth-sep { + display: flex; + align-items: center; + gap: 0.75rem; + margin: 1.4rem 0 1rem; + color: var(--muted); + font-size: 0.78rem; + letter-spacing: 0.04em; +} + +.auth-sep::before, +.auth-sep::after { + content: ""; + flex: 1; + height: 1px; + background: var(--border); +} + +/* Google button — secondary style */ +.btn-auth-google { + display: flex; + align-items: center; + justify-content: center; + gap: 0.6rem; + width: 100%; + padding: 0.72rem 1rem; + background: transparent; + border: 1.5px solid var(--border); + border-radius: 11px; + color: var(--text); + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; + transition: background 0.15s, border-color 0.15s; +} + +.btn-auth-google:hover:not(:disabled) { + background: var(--bg-subtle); + border-color: var(--accent); +} + +.btn-auth-google:disabled { opacity: 0.45; cursor: default; } + +[data-theme="dark"] .btn-auth-google { + border-color: rgba(255,255,255,0.14); +} + +/* Guest link */ +.auth-guest { + font-size: 0.84rem; + color: var(--muted); + text-decoration: none; + transition: color 0.15s; +} + +.auth-guest:hover { color: var(--text); } + +/* Done states (email sent / reset sent) */ +.auth-done { + width: 100%; + background: var(--card); + border: 1px solid var(--border); + border-radius: 20px; + padding: 2.4rem 1.8rem; + box-shadow: 0 4px 24px rgba(0,0,0,0.07); + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.6rem; +} + +.auth-done-icon { font-size: 2.4rem; } + +.auth-done h2 { + margin: 0.2rem 0 0; + font-size: 1.1rem; + font-weight: 700; +} + +.auth-done p { + margin: 0; + color: var(--muted); + font-size: 0.9rem; + line-height: 1.6; +} + +.auth-done button { + margin-top: 0.6rem; + background: none; + border: none; + color: var(--accent); + font-size: 0.88rem; + font-weight: 600; + cursor: pointer; + text-decoration: underline; +} + +.upgrade-cta { + display: block; + margin-top: 0.8rem; + color: var(--accent); + font-weight: 600; +} + +/* ---- option input (multi-way) ---- */ +.opt-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 1rem; +} + +.opt-card { + position: relative; + display: flex; + flex-direction: column; + gap: 0.55rem; + min-height: 150px; + padding: 1.2rem; + border: 1px solid var(--border); + border-radius: 22px; + background: var(--card); + box-shadow: var(--shadow-md); + overflow: hidden; + transition: border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease; +} + +.opt-card:focus-within { + border-color: var(--accent); + box-shadow: 0 18px 44px rgba(52, 84, 232, 0.18); + transform: translateY(-2px); +} + +.opt-card-top { + display: flex; + align-items: center; + gap: 0.5rem; + z-index: 1; +} + +.opt-tag { + width: 28px; + height: 28px; + border-radius: 9999px; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 0.85rem; + font-weight: 700; + color: white; + background: var(--accent); +} + +.opt-0 .opt-tag { background: #3454e8; } +.opt-1 .opt-tag { background: #7c5cff; } +.opt-2 .opt-tag { background: #0ea5a3; } +.opt-3 .opt-tag { background: #f59e0b; } +.opt-4 .opt-tag { background: #ec4899; } + +.opt-label { + color: var(--muted); + font-size: 0.9rem; + flex: 1; +} + +.opt-remove { + border: none; + background: transparent; + color: #c7cdd8; + font-size: 1.2rem; + line-height: 1; + cursor: pointer; + padding: 0 0.2rem; +} + +.opt-remove:hover { color: #dc2626; } + +.opt-card input { + border: none; + outline: none; + font-size: 1.3rem; + font-weight: 600; + background: transparent; + padding: 0; + z-index: 1; +} + +.opt-card input::placeholder { + color: #c7cdd8; + font-weight: 500; +} + +.opt-watermark { + position: absolute; + right: 0.2rem; + bottom: -1.4rem; + font-size: 7rem; + font-weight: 800; + line-height: 1; + opacity: 0.07; + pointer-events: none; + color: var(--accent); +} + +.opt-0 .opt-watermark { color: #3454e8; } +.opt-1 .opt-watermark { color: #7c5cff; } +.opt-2 .opt-watermark { color: #0ea5a3; } +.opt-3 .opt-watermark { color: #f59e0b; } +.opt-4 .opt-watermark { color: #ec4899; } + +.opt-add-btn { + display: flex; + align-items: center; + gap: 0.5rem; + width: fit-content; + margin: 1rem auto 0; + border: 1px solid var(--border); + border-radius: 9999px; + background: white; + color: var(--muted); + font-size: 0.9rem; + font-weight: 600; + padding: 0.5rem 1.05rem; + cursor: pointer; + transition: border-color 0.16s ease, color 0.16s ease, background 0.16s ease; +} + +.opt-add-btn:hover { + border-color: var(--accent); + color: var(--accent); + background: rgba(52, 84, 232, 0.04); +} + +.opt-add-count { + font-size: 0.78rem; + opacity: 0.6; +} + +/* ---- result option chips ---- */ +.result-chips { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 0.45rem; + margin: 0.2rem 0 1rem; +} + +.result-chip { + font-size: 0.85rem; + font-weight: 600; + color: var(--muted); + background: white; + border: 1px solid var(--border); + border-radius: 9999px; + padding: 0.3rem 0.8rem; +} + +.result-chip.win { + color: var(--accent); + border-color: rgba(52, 84, 232, 0.4); + background: rgba(52, 84, 232, 0.07); +} + +.result-chip.link { + display: inline-flex; + align-items: center; + gap: 0.3rem; + transition: border-color 0.16s ease, color 0.16s ease, box-shadow 0.16s ease; +} + +.result-chip.link:hover { + border-color: var(--accent); + color: var(--accent); + box-shadow: 0 4px 12px rgba(52, 84, 232, 0.14); +} + +.chip-ext { + font-size: 0.72rem; + opacity: 0.6; +} + +.result-vs { + font-size: 0.72rem; + font-weight: 700; + color: #c4c9d4; +} + +/* ---- per-option analysis ---- */ +.analysis-list { + display: flex; + flex-direction: column; + gap: 0.8rem; +} + +.analysis-item { + border: 1px solid var(--border); + border-radius: 16px; + padding: 1rem 1.1rem; + scroll-margin-top: 1.2rem; + transition: border-color 0.3s ease, box-shadow 0.3s ease; +} + +.analysis-item.winner-item { + border-color: rgba(52, 84, 232, 0.25); + border-left: 3px solid var(--accent); + background: linear-gradient(to right, rgba(52, 84, 232, 0.03), transparent 40%); +} + +[data-theme="dark"] .analysis-item.winner-item { + border-color: rgba(91, 120, 245, 0.3); + border-left: 3px solid #7c5cff; + background: linear-gradient(to right, rgba(124, 92, 255, 0.06), transparent 40%); +} + +.analysis-item:target { + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--ring); +} + +.analysis-head { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.5rem; +} + +.analysis-letter { + width: 24px; + height: 24px; + flex-shrink: 0; + border-radius: 7px; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 0.78rem; + font-weight: 700; + color: white; + background: linear-gradient(135deg, #3454e8, #7c5cff); +} + +.analysis-name { + font-weight: 700; + font-size: 1.02rem; +} + +.analysis-pick { + font-size: 0.72rem; + font-weight: 700; + color: var(--accent); + background: rgba(52, 84, 232, 0.1); + border-radius: 9999px; + padding: 0.15rem 0.5rem; +} + +.analysis-official { + margin-left: auto; + font-size: 0.82rem; + color: var(--muted); +} + +.analysis-official:hover { + color: var(--accent); +} + +.analysis-item p { + margin: 0; + line-height: 1.65; + word-break: break-word; + overflow-wrap: break-word; + color: var(--muted); + font-size: 0.9rem; +} + +/* ---- share + affiliate CTA ---- */ +.share-actions { + display: flex; + flex-direction: column; + gap: 0.8rem; + margin: 0; +} + +.share-card-preview { + position: relative; + overflow: hidden; + padding: 1rem; + border: 1px solid var(--border); + border-radius: 14px; + background: + linear-gradient(135deg, rgba(52, 84, 232, 0.1), rgba(15, 23, 42, 0.02) 48%, rgba(52, 84, 232, 0.12)), + var(--card); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.62); +} + +[data-theme="dark"] .share-card-preview { + background: + linear-gradient(135deg, rgba(91, 120, 245, 0.16), rgba(255, 255, 255, 0.04) 52%, rgba(91, 120, 245, 0.1)), + var(--card); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.07); +} + +.share-card-eyebrow { + display: block; + margin-bottom: 0.5rem; + color: var(--accent); + font-size: 0.68rem; + font-weight: 800; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.share-card-pick { + display: block; + color: var(--fg); + font-size: 1.02rem; + line-height: 1.25; + overflow-wrap: anywhere; +} + +.share-card-preview p { + margin: 0.45rem 0 0; + color: var(--muted); + font-size: 0.85rem; + line-height: 1.45; + overflow-wrap: anywhere; +} + +.btn-buy { + display: flex; + align-items: center; + gap: 0.65rem; + width: 100%; + padding: 1rem 1.15rem; + border-radius: 14px; + border: none; + background: #0f172a; + color: white; + font-size: 0.98rem; + font-weight: 700; + text-align: left; + cursor: pointer; + text-decoration: none; + transition: opacity 0.15s ease; +} + +[data-theme="dark"] .btn-buy { + background: #e2e8f4; + color: #0d0f14; +} + +[data-theme="dark"] .btn-buy .buy-icon { + background: rgba(0, 0, 0, 0.12); + color: #0d0f14; +} + +[data-theme="dark"] .btn-buy .buy-store { + background: rgba(0, 0, 0, 0.1); + color: #0d0f14; +} + +.btn-buy:hover { + opacity: 0.88; + text-decoration: none; +} + +.btn-buy:focus-visible, +.btn-share:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 3px; +} + +.buy-icon { + width: 22px; + height: 22px; + border-radius: 7px; + display: inline-flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.15); + flex-shrink: 0; + font-size: 0.8rem; +} + +.buy-text { + display: block; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.buy-copy { + flex: 1; + min-width: 0; +} + +.buy-hint { + display: block; + margin-top: 0.2rem; + color: rgba(255, 255, 255, 0.7); + font-size: 0.76rem; + font-weight: 500; + line-height: 1.2; +} + +[data-theme="dark"] .btn-buy .buy-hint { + color: rgba(13, 15, 20, 0.62); +} + +.buy-copy, +.buy-text { + overflow: hidden; + text-overflow: ellipsis; +} + +.buy-official-badge { + display: inline-block; + font-size: 0.65rem; + font-weight: 700; + background: rgba(255,255,255,0.22); + border-radius: 4px; + padding: 1px 5px; + margin-left: 5px; + vertical-align: middle; + letter-spacing: 0.02em; +} + +.buy-store { + margin-left: auto; + font-size: 0.8rem; + font-weight: 600; + opacity: 0.88; + background: rgba(255, 255, 255, 0.2); + border-radius: 8px; + padding: 0.2rem 0.55rem; + white-space: nowrap; +} + +.share-row { + display: flex; + gap: 0.55rem; +} + +.btn-share { + flex: 1; + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.85rem; + padding: 0.8rem 1rem; + border: 1px solid var(--border); + border-radius: 14px; + background: var(--card); + color: var(--fg); + font-size: 0.93rem; + font-weight: 600; + cursor: pointer; + transition: border-color 0.16s ease, color 0.16s ease, box-shadow 0.16s ease; +} + +.btn-share:disabled { + cursor: wait; + opacity: 0.72; +} + +.btn-share:hover { + border-color: var(--accent); + color: var(--accent); + box-shadow: 0 2px 8px rgba(52, 84, 232, 0.1); +} + +.share-hint { + flex-shrink: 0; + color: var(--muted); + font-size: 0.76rem; + font-weight: 500; + text-align: right; +} + +.affiliate-note { + display: grid; + gap: 0.25rem; + margin: 0; + font-size: 0.78rem; + color: var(--muted); + text-align: left; + line-height: 1.4; + overflow-wrap: anywhere; +} diff --git a/app/styles/commerce.css b/app/styles/commerce.css new file mode 100644 index 0000000..eb19221 --- /dev/null +++ b/app/styles/commerce.css @@ -0,0 +1,1967 @@ +/* ════════════════════════════════════════ + TimingSection — 구매 타이밍 + ════════════════════════════════════════ */ + +.timing-section { + padding: 1.35rem 1.5rem 1.2rem; +} + +/* 헤더 행: 라벨 + 신호 점 */ +.ts-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 1rem; +} + +.ts-label { + font-size: .72rem; + font-weight: 600; + letter-spacing: .08em; + text-transform: uppercase; + color: var(--muted); +} + +/* 신호 점 — 이모지 없이 CSS로 */ +.ts-signal { + width: 8px; + height: 8px; + border-radius: 50%; + flex-shrink: 0; +} +.ts-green { background: #22c55e; box-shadow: 0 0 0 3px rgba(34,197,94,.18); } +.ts-amber { background: #f59e0b; box-shadow: 0 0 0 3px rgba(245,158,11,.18); } +.ts-red { background: #ef4444; box-shadow: 0 0 0 3px rgba(239,68,68,.18); } +.ts-gray { background: var(--muted); } + +[data-theme="dark"] .ts-green { box-shadow: 0 0 0 3px rgba(34,197,94,.25); } +[data-theme="dark"] .ts-amber { box-shadow: 0 0 0 3px rgba(245,158,11,.25); } +[data-theme="dark"] .ts-red { box-shadow: 0 0 0 3px rgba(239,68,68,.25); } + +/* 판정 텍스트 */ +.ts-verdict { + margin: 0 0 .3rem; + font-size: 1.12rem; + font-weight: 800; + letter-spacing: -.025em; + line-height: 1.2; + color: var(--fg); +} + +.ts-sub { + margin: 0 0 1.2rem; + font-size: .82rem; + color: var(--muted); + line-height: 1.6; +} + +/* 가격 게이지 */ +.ts-gauge-wrap { margin-bottom: .6rem; } + +.ts-gauge-track { + position: relative; + height: 3px; + background: var(--border); + border-radius: 99px; + overflow: visible; + margin-bottom: .55rem; +} + +.ts-gauge-fill { + height: 100%; + border-radius: 99px; + transition: width .5s cubic-bezier(.22,1,.36,1); +} + +.ts-fill-green { background: #22c55e; } +.ts-fill-amber { background: #f59e0b; } +.ts-fill-red { background: #ef4444; } +.ts-fill-gray { background: var(--border); } + +/* 현재 가격 마커 */ +.ts-gauge-cursor { + position: absolute; + top: 50%; + transform: translate(-50%, -50%); + width: 11px; + height: 11px; + border-radius: 50%; + background: var(--card); + border: 2px solid var(--fg); + box-shadow: 0 1px 4px rgba(0,0,0,.15); + transition: left .5s cubic-bezier(.22,1,.36,1); +} + +[data-theme="dark"] .ts-gauge-cursor { + box-shadow: 0 1px 6px rgba(0,0,0,.4); +} + +/* 최저가 / 현재 / 평균 레이블 */ +.ts-price-row { + display: flex; + justify-content: space-between; + font-size: .72rem; + color: var(--muted); + line-height: 1.3; + gap: .2rem; +} + +.ts-price-row span { + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.ts-price-row span:nth-child(2) { + text-align: center; +} + +.ts-price-row span:last-child { + text-align: right; +} + +.ts-price-current { + font-weight: 700; + color: var(--fg); +} + +@media (max-width: 400px) { + .ts-price-row { + font-size: .66rem; + } +} + +/* 다음 모델 힌트 */ +.ts-cycle { + display: flex; + align-items: center; + gap: .45rem; + padding-top: 1rem; + border-top: 1px solid var(--border-light); + font-size: .76rem; + color: var(--muted); + line-height: 1.4; +} + +.ts-cycle svg { flex-shrink: 0; opacity: .6; } + +/* ════════════════════════════════════════ + ContextCard — 맞춤 재분석 + ════════════════════════════════════════ */ + +/* 닫힌 상태: 미니멀 텍스트 버튼 */ +.cc-trigger { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 1rem 1.4rem; + background: var(--card); + border: 1px solid var(--border); + border-radius: 20px; + cursor: pointer; + text-align: left; + transition: border-color .18s, background .18s; + margin-top: .9rem; +} + +.cc-trigger:hover { + border-color: var(--muted); + background: var(--bg-subtle); +} + +[data-theme="dark"] .cc-trigger:hover { + background: rgba(255,255,255,.03); +} + +.cc-trigger-text { + display: flex; + flex-direction: column; + gap: .2rem; +} + +.cc-trigger-title { + font-size: .88rem; + font-weight: 700; + color: var(--fg); + line-height: 1.2; +} + +.cc-trigger-hint { + font-size: .76rem; + color: var(--muted); + font-weight: 400; +} + +.cc-chevron { + color: var(--muted); + flex-shrink: 0; + opacity: .6; +} + +/* 열린 상태 */ +.cc-card { + margin-top: .9rem; + border: 1px solid var(--border); + border-radius: 20px; + background: var(--card); + overflow: hidden; + box-shadow: var(--shadow-sm); +} + +.cc-head { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1rem 1.4rem; + border-bottom: 1px solid var(--border-light); +} + +.cc-head-title { + font-size: .88rem; + font-weight: 700; + color: var(--fg); +} + +.cc-close { + display: flex; + align-items: center; + justify-content: center; + width: 26px; + height: 26px; + background: var(--bg-subtle); + border: 1px solid var(--border); + border-radius: 50%; + cursor: pointer; + color: var(--muted); + transition: border-color .15s, color .15s; + flex-shrink: 0; +} + +.cc-close:hover { + border-color: var(--fg); + color: var(--fg); +} + +[data-theme="dark"] .cc-close { + background: rgba(255,255,255,.05); +} + +.cc-form { + display: flex; + flex-direction: column; + gap: 1.1rem; + padding: 1.2rem 1.4rem 1.4rem; +} + +.cc-field { + display: flex; + flex-direction: column; + gap: .5rem; +} + +.cc-field-label { + font-size: .7rem; + font-weight: 700; + letter-spacing: .07em; + text-transform: uppercase; + color: var(--muted); + margin: 0; +} + +.cc-optional { + font-weight: 400; + text-transform: none; + letter-spacing: 0; +} + +.cc-chips { + display: flex; + flex-wrap: wrap; + gap: .4rem; +} + +.cc-chip { + padding: .38rem .82rem; + border: 1px solid var(--border); + border-radius: 99px; + background: transparent; + cursor: pointer; + font-size: .8rem; + color: var(--muted); + font-weight: 500; + transition: all .13s; + line-height: 1.4; +} + +.cc-chip:hover { + border-color: var(--fg); + color: var(--fg); +} + +.cc-chip-on { + border-color: var(--accent); + color: var(--accent); + background: color-mix(in srgb, var(--accent) 8%, var(--card)); + font-weight: 700; +} + +[data-theme="dark"] .cc-chip-on { + background: color-mix(in srgb, var(--accent) 14%, var(--card)); +} + +.cc-input { + padding: .6rem .9rem; + border: 1px solid var(--border); + border-radius: 10px; + background: var(--bg-subtle); + color: var(--fg); + font-size: .84rem; + line-height: 1.5; + transition: border-color .15s, background .15s; + font-family: inherit; +} + +.cc-input:focus { + outline: none; + border-color: var(--accent); + background: var(--card); +} + +.cc-input::placeholder { + color: var(--muted); + opacity: .7; +} + +[data-theme="dark"] .cc-input { + background: rgba(255,255,255,.05); +} + +[data-theme="dark"] .cc-input:focus { + background: rgba(255,255,255,.07); +} + +.cc-submit { + display: flex; + align-items: center; + justify-content: center; + gap: .5rem; + padding: .82rem 1rem; + background: var(--fg); + color: var(--bg); + border: none; + border-radius: 12px; + cursor: pointer; + font-size: .88rem; + font-weight: 700; + letter-spacing: -.01em; + transition: opacity .15s; + font-family: inherit; + margin-top: .1rem; +} + +.cc-submit:hover { opacity: .82; } + +[data-theme="dark"] .cc-submit { + background: var(--fg); + color: #0d0f14; +} + +/* ---- price comparison (live price + history) ---- */ +.price-head { + display: flex; + align-items: center; + gap: 0.6rem; + margin-bottom: 0.7rem; +} + +.price-head h2 { + margin: 0; +} + +.price-demo { + font-size: 0.7rem; + font-weight: 700; + color: var(--muted); + background: var(--bg); + border: 1px solid var(--border); + border-radius: 9999px; + padding: 0.12rem 0.5rem; +} + +.price-list { + list-style: none; + margin: 0; + padding: 0; +} + +.price-row { + display: flex; + align-items: center; + gap: 0.9rem; + padding: 0.75rem 0.6rem; + border-bottom: 1px solid var(--border); + border-radius: 10px; + flex-wrap: wrap; +} + +.price-row:last-child { + border-bottom: none; +} + +.price-row.cheapest { + background: rgba(52, 84, 232, 0.05); +} + +[data-theme="dark"] .price-row.cheapest { + background: rgba(91, 120, 245, 0.1); +} + +.price-main { + display: flex; + flex-direction: column; + gap: 0.3rem; + flex: 1; + min-width: 0; +} + +.price-name { + font-weight: 700; + font-size: 0.98rem; +} + +.price-tags { + display: flex; + gap: 0.35rem; +} + +.price-tag { + font-size: 0.68rem; + font-weight: 700; + border-radius: 9999px; + padding: 0.1rem 0.45rem; +} + +.cheapest-tag { + color: var(--accent); + background: rgba(52, 84, 232, 0.12); +} + +.deal-tag { + color: #0f9d6b; + background: rgba(15, 157, 107, 0.12); +} + +[data-theme="dark"] .deal-tag { + color: #34d399; + background: rgba(52, 211, 153, 0.14); +} + +.spark { + color: var(--muted); + flex-shrink: 0; +} + +.price-figures { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 0.15rem; + flex-shrink: 0; +} + +.price-now { + font-size: 1.05rem; + font-weight: 800; + letter-spacing: -0.01em; + color: var(--text); + transition: color 0.14s ease; +} + +.price-now:hover { + color: var(--accent); +} + +.price-go { + font-size: 0.75rem; + color: var(--muted); +} + +.price-sub { + font-size: 0.76rem; + color: var(--muted); +} + +@media (max-width: 480px) { + .price-figures { + align-items: flex-start; + width: 100%; + } +} + +/* ---- watch / track ---- */ +.watch-btn-wrap { + position: relative; + display: inline-block; +} + +.watch-btn { + display: inline-flex; + align-items: center; + gap: 0.3rem; + flex-shrink: 0; + border: 1px solid var(--border); + border-radius: 9999px; + background: var(--card); + color: var(--muted); + font-size: 0.78rem; + font-weight: 600; + padding: 0.34rem 0.7rem; + cursor: pointer; + transition: border-color 0.14s ease, color 0.14s ease, background 0.14s ease; +} + +.watch-btn:hover { + border-color: var(--accent); + color: var(--accent); +} + +.watch-btn.on { + border-color: var(--accent); + color: var(--accent); + background: rgba(52, 84, 232, 0.08); +} + +[data-theme="dark"] .watch-btn.on { + background: rgba(91, 120, 245, 0.14); +} + +/* Home watchlist card */ +.watch-card { + margin-top: 1.5rem; + border: 1px solid var(--border); + border-radius: 24px; + background: var(--card); + box-shadow: var(--shadow-sm); + overflow: hidden; +} + +.watch-head { + display: flex; + align-items: center; + gap: 0.55rem; + padding: 0.85rem 1.2rem; + border-bottom: 1px solid var(--border); +} + +.watch-head h3 { + margin: 0; + font-size: 0.92rem; + font-weight: 700; +} + +.watch-count { + font-size: 0.7rem; + font-weight: 700; + color: var(--accent); + background: rgba(52, 84, 232, 0.1); + border-radius: 9999px; + padding: 0.1rem 0.5rem; +} + +.watch-items { + list-style: none; + margin: 0; + padding: 0; +} + +.watch-item { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 0.85rem 1.2rem; + border-bottom: 1px solid var(--border); +} + +.watch-item:last-child { + border-bottom: none; +} + +.watch-name { + font-weight: 600; + font-size: 0.95rem; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.watch-right { + display: flex; + align-items: center; + gap: 0.8rem; + flex-shrink: 0; +} + +.watch-price { + font-weight: 800; + font-size: 0.96rem; + color: var(--text); + transition: color 0.14s ease; +} + +.watch-price:hover { + color: var(--accent); +} + +.watch-remove { + width: 28px; + height: 28px; + border: 1px solid transparent; + border-radius: 8px; + background: transparent; + color: #b8c0d0; + font-size: 1.05rem; + line-height: 1; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + transition: background 0.14s ease, color 0.14s ease, border-color 0.14s ease; +} + +.watch-remove:hover { + background: rgba(220, 38, 38, 0.08); + border-color: rgba(220, 38, 38, 0.2); + color: #dc2626; +} + +.watch-note { + margin: 0; + padding: 0.7rem 1.2rem 0.9rem; + font-size: 0.8rem; + color: var(--muted); + border-top: 1px solid var(--border); +} + +/* ---- watch push prompt ---- */ +.watch-push-prompt { + position: absolute; + top: calc(100% + 8px); + left: 50%; + transform: translateX(-50%); + z-index: 200; + background: var(--card); + border: 1px solid var(--border); + border-radius: 12px; + padding: 1rem 1.1rem 0.9rem; + width: 240px; + box-shadow: 0 8px 24px rgba(0,0,0,0.12); +} + +[data-theme="dark"] .watch-push-prompt { + box-shadow: 0 8px 24px rgba(0,0,0,0.4); +} + +.watch-push-sub { + margin: 0 0 0.8rem; + font-size: 0.76rem; + color: var(--muted); + padding-right: 1rem; + line-height: 1.4; +} + +.watch-push-denied { + padding: 0.7rem 1rem; +} + +.watch-push-denied-msg { + margin: 0; + font-size: 0.78rem; + color: var(--muted); + padding-right: 1.4rem; + line-height: 1.4; +} + +/* ---- watch email modal ---- */ +.watch-email-modal { + position: absolute; + top: calc(100% + 8px); + left: 50%; + transform: translateX(-50%); + z-index: 200; + background: var(--card); + border: 1px solid var(--border); + border-radius: 12px; + padding: 1rem 1.1rem 0.9rem; + width: 260px; + box-shadow: 0 8px 24px rgba(0,0,0,0.12); +} + +[data-theme="dark"] .watch-email-modal { + box-shadow: 0 8px 24px rgba(0,0,0,0.4); +} + +.watch-email-close { + position: absolute; + top: 0.5rem; + right: 0.6rem; + background: none; + border: none; + font-size: 1rem; + color: var(--muted); + cursor: pointer; + padding: 0 0.2rem; + line-height: 1; +} + +.watch-email-label { + margin: 0 0 0.7rem; + font-size: 0.82rem; + font-weight: 600; + color: var(--fg); + padding-right: 1rem; +} + +.watch-email-form { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.watch-email-input { + width: 100%; + padding: 0.45rem 0.6rem; + border: 1px solid var(--border); + border-radius: 7px; + background: var(--bg); + color: var(--fg); + font-size: 0.82rem; + box-sizing: border-box; +} + +.watch-email-input:focus { + outline: none; + border-color: var(--accent, #3b82f6); +} + +.watch-email-confirm { + width: 100%; + padding: 0.45rem; + background: var(--fg); + color: var(--bg); + border: none; + border-radius: 7px; + font-size: 0.82rem; + font-weight: 600; + cursor: pointer; +} + +.watch-email-confirm:disabled { + opacity: 0.5; + cursor: default; +} + +.watch-email-skip { + display: block; + width: 100%; + margin-top: 0.4rem; + background: none; + border: none; + font-size: 0.75rem; + color: var(--muted); + cursor: pointer; + text-align: center; + padding: 0.2rem 0; +} + +.watch-email-skip:hover { + color: var(--fg); +} + +/* ---- share watermark (Free shared pages) ---- */ +.share-watermark { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: white; + border-top: 1px solid var(--border); + padding: 1rem 1.2rem 1.4rem; + text-align: center; + box-shadow: 0 -8px 32px rgba(17, 24, 39, 0.1); + z-index: 10; +} + +.share-watermark p { + margin: 0 0 0.1rem; + font-size: 0.95rem; + color: var(--muted); +} + +.share-watermark strong { + color: var(--text); +} + +.vs-upsell { + text-align: center; + color: var(--muted); + font-size: 0.9rem; + margin: 0.9rem 0 0; +} + +.vs-upsell a { + color: var(--accent); + font-weight: 600; +} + +/* ---- comparison table (N columns) ---- */ +.cmp-table { + display: flex; + flex-direction: column; + width: 100%; +} + +.cmp-row { + display: grid; + grid-template-columns: minmax(80px, 0.85fr) repeat(var(--cols), minmax(0, 1fr)); + column-gap: 0.5rem; + align-items: start; +} + +.cmp-key, +.cmp-col { + padding: 0.7rem 0.6rem; + border-bottom: 1px solid var(--border); +} + +.cmp-row:last-child .cmp-key, +.cmp-row:last-child .cmp-col { + border-bottom: none; +} + +.cmp-key { + color: var(--muted); + font-size: 0.85rem; + font-weight: 600; + align-self: start; + padding-top: 0.9rem; + min-width: 0; + word-break: keep-all; +} + +.cmp-col { + font-size: 0.9rem; + line-height: 1.4; + word-break: break-word; + overflow-wrap: break-word; + min-width: 0; +} + +.cmp-head .cmp-col { + font-weight: 700; + color: var(--text); + display: flex; + flex-direction: column; + gap: 0.3rem; + padding-top: 0.55rem; +} + +/* Continuous winner highlight band */ +.cmp-col.winner { + background: rgba(52, 84, 232, 0.06); + color: var(--accent); +} + +[data-theme="dark"] .cmp-col.winner { + background: rgba(91, 120, 245, 0.12); +} + +.cmp-head .cmp-col.winner { + border-top-left-radius: 12px; + border-top-right-radius: 12px; + color: var(--accent); +} + +.cmp-row:last-child .cmp-col.winner { + border-bottom-left-radius: 12px; + border-bottom-right-radius: 12px; +} + +.cmp-win-tag { + align-self: flex-start; + font-size: 0.62rem; + font-weight: 700; + color: white; + background: var(--accent); + border-radius: 9999px; + padding: 0.08rem 0.5rem; + letter-spacing: 0.02em; +} + +/* ---- account hero / usage ---- */ +.account-hero { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.6rem; + text-align: center; + margin: 1rem 0 1.4rem; +} + +.account-avatar { + width: 64px; + height: 64px; + border-radius: 20px; + display: inline-flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #3454e8, #7c5cff); + color: white; + font-size: 1.8rem; + font-weight: 700; +} + +.nickname-view { + display: flex; + align-items: center; + gap: 0.6rem; +} + +.nickname-view h1 { + margin: 0; + font-size: 1.7rem; +} + +.nickname-edit { + border: 1px solid var(--border); + background: white; + border-radius: 9999px; + font-size: 0.8rem; + color: var(--muted); + padding: 0.25rem 0.7rem; + cursor: pointer; + transition: border-color 0.16s ease, color 0.16s ease; +} + +.nickname-edit:hover { + border-color: var(--accent); + color: var(--accent); +} + +.nickname-edit-row { + width: 100%; + max-width: 320px; +} + +.nickname-edit-row input { + width: 100%; + border: 1px solid var(--border); + border-radius: 12px; + padding: 0.8rem; + font-size: 1.1rem; + text-align: center; +} + +.nickname-edit-row input:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 4px var(--ring); +} + +.nickname-actions { + display: flex; + gap: 0.5rem; + margin-top: 0.6rem; +} + +.nickname-actions .btn-primary, +.nickname-actions .btn-outline { + margin-top: 0; +} + +.usage-head { + display: flex; + justify-content: space-between; + align-items: baseline; +} + +.usage-head h2 { margin: 0; } + +.usage-count { + font-weight: 700; + color: var(--accent); +} + +.usage-bar { + height: 10px; + border-radius: 9999px; + background: var(--bg); + overflow: hidden; + margin: 0.8rem 0 0.6rem; +} + +.usage-fill { + display: block; + height: 100%; + border-radius: 9999px; + background: linear-gradient(90deg, #3454e8, #7c5cff); + transition: width 0.3s ease; +} + +.usage-sub { + margin: 0; + color: var(--muted); + font-size: 0.88rem; +} + +/* ---- history ---- */ +.history-head { + display: flex; + align-items: center; + gap: 0.55rem; + padding: 0.85rem 1.2rem; + border-bottom: 1px solid var(--border); +} + +.history-head h3 { + margin: 0; + font-size: 0.92rem; + font-weight: 700; +} + +.history-count { + font-size: 0.7rem; + font-weight: 700; + color: var(--muted); + background: var(--bg); + border-radius: 9999px; + padding: 0.1rem 0.5rem; +} + +.history-items { + list-style: none; + padding: 0; + margin: 0; +} + +.history-item { + display: flex; + align-items: center; + gap: 0; + border-bottom: 1px solid var(--border); + transition: background 0.14s ease; +} + +.history-item:last-child { border-bottom: none; } + +.history-item:hover { + background: rgba(52, 84, 232, 0.03); +} + +[data-theme="dark"] .history-item:hover { + background: rgba(255, 255, 255, 0.03); +} + +/* Link area fills the row */ +.history-item-main { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 0.4rem; + padding: 0.95rem 0.5rem 0.95rem 1.2rem; + text-decoration: none; +} + +/* Matchup: what was compared */ +.history-matchup { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.4rem; + font-size: 0.96rem; + line-height: 1.3; +} + +.hm-part { + display: inline-flex; + align-items: center; + gap: 0.4rem; +} + +.hm-opt { + color: var(--text); + font-weight: 600; +} + +.hm-win { + color: var(--text); + font-weight: 600; +} + +.hm-vs { + font-size: 0.64rem; + font-weight: 700; + letter-spacing: 0.05em; + text-transform: uppercase; + color: #c4c9d4; +} + +/* Verdict: which one AXIS picked */ +.history-verdict { + display: inline-flex; + align-items: center; + gap: 0.38rem; + font-size: 0.82rem; + font-weight: 700; + color: var(--accent); + transition: opacity 0.14s ease; +} + +.verdict-mark { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + flex-shrink: 0; + border-radius: 9999px; + background: rgba(52, 84, 232, 0.12); + font-size: 0.58rem; + font-weight: 800; +} + +[data-theme="dark"] .verdict-mark { + background: rgba(91, 120, 245, 0.2); +} + +.history-item:hover .history-matchup { + opacity: 0.85; +} + +/* Date + delete button */ +.history-item-meta { + display: flex; + align-items: center; + gap: 0.3rem; + flex-shrink: 0; + padding-right: 0.6rem; +} + +.history-item-date { + color: var(--muted); + font-size: 0.75rem; + white-space: nowrap; + padding-right: 0.2rem; +} + +@media (max-width: 400px) { + .history-item-date { display: none; } +} + +.history-delete { + width: 30px; + height: 30px; + border: 1px solid transparent; + border-radius: 8px; + background: transparent; + color: #b8c0d0; + font-size: 1.1rem; + line-height: 1; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + transition: background 0.14s ease, border-color 0.14s ease, color 0.14s ease; +} + +.history-delete:hover { + background: rgba(220, 38, 38, 0.08); + border-color: rgba(220, 38, 38, 0.2); + color: #dc2626; +} + +.history-delete:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +/* Empty / login prompt */ +.history-empty { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.3rem; + padding: 2rem 1.5rem; + text-align: center; +} + +.history-empty p { + margin: 0; + color: var(--muted); + font-size: 0.88rem; +} + +.history-empty-cta { + margin-top: 0.2rem; + color: var(--accent); + font-weight: 600; + font-size: 0.9rem; +} + +@media (max-width: 820px) { + .example-grid { + grid-template-columns: 1fr; + } + + .home-method { + grid-template-columns: 1fr; + } + + .feature-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 560px) { + .container { + width: 100%; + padding: 0.75rem 1.2rem 3rem; + } + + .topbar { + margin-bottom: 1.2rem; + } + + .hero { + margin: 0.6rem 0 1rem; + } + + .hero h1 { + font-size: 1.65rem; + } + + .sub { + font-size: 0.93rem; + } + + .home-section { + margin-top: 1.8rem; + } + + /* Verdict hero: tighter on mobile */ + .verdict-hero { + padding: 1.4rem 1.25rem 1.3rem; + border-radius: 18px; + } + + .vh-title { + font-size: 1.6rem; + letter-spacing: -0.025em; + } + + .vh-conclusion { + font-size: 0.94rem; + } + + .vh-matchup { + margin-top: 1.3rem; + padding-top: 1.2rem; + gap: 0.85rem; + } + + .detail-card { + padding: 1rem 1.1rem; + border-radius: 16px; + margin-top: 0.75rem; + } + + .detail-card h2 { + font-size: 0.82rem; + margin-bottom: 0.85rem; + } + + /* Buttons: not too huge on mobile */ + .btn-buy { + padding: 0.9rem 1rem; + font-size: 0.92rem; + border-radius: 12px; + } + + .btn-share { + align-items: flex-start; + flex-direction: column; + gap: 0.2rem; + padding: 0.65rem 1rem; + font-size: 0.88rem; + border-radius: 12px; + } + + .share-hint { + text-align: left; + } + + .share-actions-wrap { + padding: 0.9rem 1.1rem 1.1rem; + } + + .btn-primary { + padding: 0.82rem 1rem; + font-size: 0.95rem; + border-radius: 12px; + margin-top: 0.9rem; + } + + .buy-store { + font-size: 0.75rem; + } + + /* Input on mobile */ + .vs-card { + padding: 0.9rem; + min-height: 148px; + } + + .opt-submit { + margin-top: 0.9rem; + } + + /* Share actions spacing */ + .share-actions { + gap: 0.55rem; + } + + /* Reason list */ + .reason-list li { + font-size: 0.9rem; + padding: 0.65rem 0; + } + + /* Back button */ + .btn-back { + font-size: 0.85rem; + margin-bottom: 0.7rem; + } + + /* Results body spacing */ + .results-body { + padding-bottom: 2.5rem; + } + + /* Topbar: tighten brand + nav gap */ + .brand { + font-size: 1.45rem; + } + + .topbar-right { + gap: 0.3rem; + } + + /* Input cards: compact */ + .opt-card { + min-height: 128px; + padding: 0.9rem 1rem; + } + + .vs-card input, + .opt-card input { + font-size: 1.15rem; + } + + .buy-timing-block { + border-radius: 16px; + } + + .ts-verdict { + font-size: 1rem; + } +} + +/* ── compare pages ─────────────────────────────────────────────────────── */ + +.compare-breadcrumb { + display: flex; + align-items: center; + gap: 0.4rem; + padding: 0.9rem 1.2rem 0; + font-size: 0.8rem; + color: var(--muted); + max-width: 680px; + margin: 0 auto; +} + +.compare-breadcrumb a { + color: var(--muted); + text-decoration: none; +} + +.compare-breadcrumb a:hover { color: var(--fg); } + +/* related comparisons */ +.compare-related { + max-width: 680px; + margin: 0 auto 2rem; + padding: 1.4rem 1.2rem; + background: var(--card); + border: 1px solid var(--border); + border-radius: 12px; +} + +.compare-related-title { + font-size: 1rem; + font-weight: 600; + margin: 0 0 1rem; +} + +.compare-related-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 0.8rem; +} + +.compare-related-link { + font-size: 0.9rem; + font-weight: 500; + color: var(--fg); + text-decoration: none; +} + +.compare-related-link:hover { text-decoration: underline; } + +.compare-related-desc { + margin: 0.15rem 0 0; + font-size: 0.78rem; + color: var(--muted); +} + +/* CTA */ +.compare-cta { + max-width: 680px; + margin: 0 auto 3rem; + padding: 1.4rem 1.2rem; + background: var(--card); + border: 1px solid var(--border); + border-radius: 12px; + text-align: center; +} + +.compare-cta-copy { + margin: 0 0 1rem; + font-size: 0.9rem; + color: var(--muted); +} + +.compare-cta-btn { + display: inline-block; + padding: 0.7rem 1.4rem; + font-size: 0.9rem; +} + +/* index page */ +.compare-index-section { + margin-bottom: 2rem; +} + +.compare-index-category { + font-size: 0.78rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: .07em; + color: var(--muted); + margin: 0 0 0.8rem; +} + +.compare-index-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.compare-index-item { + border: 1px solid var(--border); + border-radius: 10px; + background: var(--card); +} + +.compare-index-link { + display: block; + padding: 0.85rem 1rem; + text-decoration: none; + color: var(--fg); +} + +.compare-index-link:hover { background: var(--hover, rgba(0,0,0,.03)); border-radius: 10px; } + +.compare-index-title { + font-size: 0.92rem; + font-weight: 600; +} + +.compare-index-desc { + margin: 0.2rem 0 0; + font-size: 0.78rem; + color: var(--muted); +} + +/* ═══════════════════════════════════════════════════════════════════════════ + Analyze Overlay — "Axis에게 맡기기" 클릭 후 프리미엄 분석 로딩 화면 + ═══════════════════════════════════════════════════════════════════════════ */ + +@keyframes overlayFadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes modalRise { + from { + opacity: 0; + transform: translateY(28px) scale(0.96); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +@keyframes indeterminate { + 0% { transform: translateX(-100%) scaleX(0.4); } + 40% { transform: translateX(30%) scaleX(0.8); } + 100% { transform: translateX(350%) scaleX(0.4); } +} + +@keyframes pillGlow { + 0%, 100% { border-color: rgba(255,255,255,0.12); } + 50% { border-color: rgba(91,120,245,0.5); box-shadow: 0 0 18px rgba(91,120,245,0.25); } +} + +.analyze-overlay { + position: fixed; + inset: 0; + background: rgba(6, 8, 16, 0.90); + backdrop-filter: blur(18px); + -webkit-backdrop-filter: blur(18px); + z-index: 9999; + display: flex; + align-items: center; + justify-content: center; + animation: overlayFadeIn 0.22s ease; +} + +.analyze-modal { + width: min(440px, 88vw); + padding: 2.8rem 2rem 2.4rem; + background: rgba(255,255,255,0.03); + border: 1px solid rgba(255,255,255,0.07); + border-radius: 28px; + text-align: center; + animation: modalRise 0.32s cubic-bezier(0.18, 0, 0.08, 1); +} + +.analyze-logo { + font-size: 0.78rem; + font-weight: 800; + letter-spacing: 0.16em; + text-transform: uppercase; + color: rgba(255,255,255,0.22); + margin-bottom: 2rem; +} + +.analyze-comparing { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + gap: 0.8rem; + margin-bottom: 2.2rem; + min-height: 2.6rem; +} + +.analyze-pill { + display: inline-block; + padding: 0.55rem 1.05rem; + background: rgba(255,255,255,0.05); + border: 1px solid rgba(255,255,255,0.12); + border-radius: 100px; + font-size: 0.95rem; + font-weight: 600; + color: #e8ecf8; + animation: pillGlow 2.4s ease-in-out infinite; +} + +.analyze-vs-badge { + flex-shrink: 0; + font-size: 0.62rem; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; + color: rgba(255,255,255,0.25); + padding: 0 0.1rem; +} + +.analyze-bar { + width: 100%; + height: 2px; + background: rgba(255,255,255,0.05); + border-radius: 2px; + overflow: hidden; + margin: 0 auto 1.8rem; + max-width: 240px; +} + +.analyze-progress { + height: 100%; + width: 40%; + background: linear-gradient(90deg, transparent 0%, #5b78f5 40%, #9474ff 70%, transparent 100%); + border-radius: 2px; + transform-origin: left; + animation: indeterminate 1.5s ease-in-out infinite; +} + +.analyze-status { + color: rgba(255,255,255,0.3); + font-size: 0.82rem; + letter-spacing: 0.02em; +} + +/* ── 버튼 로딩 상태 ──────────────────────────────────────────────────── */ + +@keyframes spinBtn { + to { transform: rotate(360deg); } +} + +.btn-loading { + opacity: 0.7 !important; + cursor: not-allowed; +} + +.btn-loading-inner { + display: inline-flex; + align-items: center; + gap: 0.55rem; +} + +.btn-spinner { + display: inline-block; + width: 16px; + height: 16px; + border: 2px solid rgba(255,255,255,0.25); + border-top-color: white; + border-radius: 50%; + animation: spinBtn 0.7s linear infinite; + flex-shrink: 0; +} + +/* ── 결과 없는 상태 (/results 직접 접근) ───────────────────────────── */ + +.results-empty-state { + min-height: 60vh; + display: flex; + align-items: center; + justify-content: center; +} + +.results-empty-inner { + text-align: center; + padding: 3rem 1.5rem; + max-width: 380px; +} + +.results-empty-icon { + font-size: 2.5rem; + display: block; + margin-bottom: 1.2rem; + opacity: 0.7; +} + +.results-empty-title { + font-size: 1.4rem; + font-weight: 700; + margin-bottom: 0.75rem; + color: var(--fg); +} + +.results-empty-sub { + color: var(--muted); + font-size: 0.95rem; + line-height: 1.65; + margin-bottom: 2rem; +} + +.results-empty-cta { + display: inline-block; + width: auto; + padding: 0.85rem 2rem; + font-size: 1rem; + text-decoration: none; + margin-top: 0; +} + +/* ── Fit score bars (스펙 우위 비율) ─────────────────────────────────────── */ +.fit-bars { + margin-top: 1.6rem; + padding-top: 1.4rem; + border-top: 1px solid var(--border); + display: flex; + flex-direction: column; + gap: 0.65rem; +} + +.fit-bar-row { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 140px); + align-items: center; + gap: 0.75rem; +} + +@media (max-width: 480px) { + .fit-bar-row { + grid-template-columns: minmax(0, 1fr) minmax(0, 90px); + gap: 0.5rem; + } +} + +.fit-bar-name { + font-size: 0.82rem; + font-weight: 600; + color: var(--muted); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; +} + +.fit-winner .fit-bar-name { + color: var(--fg); + font-weight: 700; +} + +.fit-bar-track { + width: 100%; + height: 6px; + background: var(--border); + border-radius: 99px; + overflow: hidden; +} + +.fit-bar-fill { + height: 100%; + background: var(--muted); + border-radius: 99px; + transition: width 0.6s cubic-bezier(0.22, 1, 0.36, 1); +} + +.fit-winner .fit-bar-fill { + background: linear-gradient(90deg, var(--accent), #6c8fff); +} + +.fit-bar-note { + font-size: 0.7rem; + color: var(--muted); + opacity: 0.55; + margin: 0.4rem 0 0; + line-height: 1.4; +} + + +/* ---- coverage note ---- */ +.coverage-note { + margin: 0.45rem 0 0; + color: var(--muted); + font-size: 0.78rem; + font-weight: 500; + line-height: 1.4; + letter-spacing: 0.01em; +} + +.coverage-note-inline { + margin: 0.7rem 0 0.35rem; +} + +.coverage-note-home { + margin: 0.55rem auto 0; + max-width: 36rem; + text-align: center; +} + +/* ---- spec graphs (compare bars) ---- */ +.spec-graph-section { + display: grid; + gap: 1rem; +} + +.spec-graph-list { + display: grid; + gap: 1.05rem; +} + +.spec-graph-selector { + display: grid; + gap: 0.55rem; +} + +.spec-graph-selector-label { + font-size: 0.8rem; + font-weight: 700; + color: color-mix(in srgb, var(--text) 62%, var(--muted)); +} + +.spec-graph-chips { + display: flex; + flex-wrap: wrap; + gap: 0.45rem; +} + +.spec-graph-chip { + border: 1px solid var(--border-light); + background: color-mix(in srgb, var(--bg) 88%, var(--bg-subtle)); + color: color-mix(in srgb, var(--text) 72%, var(--muted)); + border-radius: 9999px; + padding: 0.38rem 0.72rem; + font-size: 0.78rem; + font-weight: 700; + cursor: pointer; + transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease; +} + +.spec-graph-chip:hover { + border-color: color-mix(in srgb, var(--accent) 35%, var(--border-light)); + color: var(--text); +} + +.spec-graph-chip.is-on { + background: color-mix(in srgb, var(--accent) 14%, var(--bg)); + border-color: color-mix(in srgb, var(--accent) 45%, var(--border-light)); + color: var(--accent); +} + +.spec-graph-chip:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; +} + +.spec-bar-card { + padding: 0.95rem 1rem 0.85rem; + border: 1px solid var(--border-light); + border-radius: 12px; + background: var(--bg-subtle); +} + +.spec-graph-label-row { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 0.75rem; + margin-bottom: 0.85rem; +} + +.spec-graph-label { + margin: 0; + font-size: 0.95rem; + font-weight: 800; + color: var(--text); +} + +.spec-bar-compare { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(4.5rem, 1fr)); + gap: 0.85rem; + align-items: end; + min-height: 11.5rem; +} + +.spec-bar-col { + display: grid; + grid-template-rows: auto 1fr auto; + gap: 0.4rem; + justify-items: center; + min-width: 0; +} + +.spec-bar-value { + font-size: 0.8rem; + font-weight: 800; + color: color-mix(in srgb, var(--text) 62%, var(--muted)); + font-variant-numeric: tabular-nums; + text-align: center; + line-height: 1.2; +} + +.spec-bar-track { + width: 100%; + max-width: 3.2rem; + height: 8.5rem; + border-radius: 9999px; + background: color-mix(in srgb, var(--border-light) 88%, var(--border)); + display: flex; + align-items: flex-end; + overflow: hidden; + padding: 0.22rem; +} + +.spec-bar-fill { + width: 100%; + border-radius: 9999px; + background: color-mix(in srgb, var(--text) 22%, var(--border)); + transition: height 0.7s cubic-bezier(0.22, 1, 0.36, 1); + min-height: 0.55rem; +} + +/* Better value for this metric (higher or lower, per schema) */ +.spec-bar-fill.is-best { + background: linear-gradient(180deg, var(--accent-2), var(--accent)); +} + +.spec-bar-name { + font-size: 0.78rem; + font-weight: 700; + color: var(--text); + text-align: center; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + + + + + +@media (max-width: 640px) { + .spec-bar-card { + padding: 0.8rem 0.75rem 0.7rem; + } + + .spec-graph-label-row { + flex-direction: column; + align-items: flex-start; + gap: 0.25rem; + } + + .spec-bar-compare { + gap: 0.55rem; + min-height: 10.5rem; + } + + .spec-bar-track { + height: 7.6rem; + max-width: 2.7rem; + } + + .spec-bar-value { + font-size: 0.74rem; + } + + .spec-bar-name { + font-size: 0.72rem; + } +} diff --git a/app/styles/home.css b/app/styles/home.css new file mode 100644 index 0000000..f67effb --- /dev/null +++ b/app/styles/home.css @@ -0,0 +1,664 @@ +/* ===== Home: compact, input-first hero ===== */ +.hero { + text-align: center; + margin: 1.4rem 0 1.6rem; +} + +.hero.compact { + margin-top: 2rem; +} + +.badge { + display: inline-flex; + align-items: center; + gap: 0.45rem; + background: rgba(255, 255, 255, 0.72); + border: 1px solid var(--border); + color: var(--muted); + padding: 0.34rem 0.85rem 0.34rem 0.7rem; + border-radius: 9999px; + font-size: 0.8rem; + font-weight: 600; + box-shadow: var(--shadow-sm); +} + +.badge::before { + content: ""; + width: 6px; + height: 6px; + border-radius: 9999px; + background: var(--accent); + box-shadow: 0 0 0 3px rgba(52, 84, 232, 0.18); +} + +[data-theme="dark"] .badge { + background: rgba(255, 255, 255, 0.05); +} + +.hero h1 { + margin: 0.85rem 0 0.7rem; + font-size: clamp(1.85rem, 4.4vw, 2.9rem); + font-weight: 700; + line-height: 1.18; + letter-spacing: -0.025em; +} + +.hero h1 span { + color: var(--accent); +} + +.sub { + color: var(--muted); + font-size: 1.02rem; + max-width: 520px; + margin: 0 auto; + line-height: 1.65; + white-space: pre-line; +} + +/* ===== Home sections ===== */ +.home-section { + margin-top: 2.6rem; +} + +.section-copy { + max-width: 640px; + margin-bottom: 1.1rem; +} + +.section-copy.center { + margin-left: auto; + margin-right: auto; + text-align: center; +} + +.section-kicker { + margin: 0 0 0.4rem; + color: var(--accent); + font-size: 0.74rem; + font-weight: 800; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.section-copy h2 { + margin: 0; + font-size: clamp(1.35rem, 2.6vw, 1.75rem); + font-weight: 800; + line-height: 1.25; + letter-spacing: -0.02em; +} + +.section-copy p:not(.section-kicker) { + margin: 0.55rem 0 0; + color: var(--muted); + font-size: 0.95rem; + line-height: 1.6; +} + +/* ===== Example chips (one-tap prefill) ===== */ +.example-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 0.8rem; +} + +.example-card { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.3rem; + width: 100%; + text-align: left; + font: inherit; + cursor: pointer; + border: 1px solid var(--border); + border-radius: 18px; + background: rgba(255, 255, 255, 0.74); + box-shadow: var(--shadow-sm); + padding: 1rem 1.1rem 0.9rem; + transition: transform 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease; +} + +[data-theme="dark"] .example-card { + background: rgba(255, 255, 255, 0.04); +} + +.example-card:hover { + transform: translateY(-2px); + border-color: var(--accent); + box-shadow: 0 14px 30px rgba(52, 84, 232, 0.16); +} + +.example-cat { + color: var(--accent); + font-size: 0.72rem; + font-weight: 800; + letter-spacing: 0.02em; +} + +.example-query { + font-size: 1.02rem; + font-weight: 700; + line-height: 1.3; + letter-spacing: -0.01em; +} + +.example-note { + color: var(--muted); + font-size: 0.86rem; + line-height: 1.5; +} + +.example-go { + margin-top: 0.55rem; + color: var(--muted); + font-size: 0.82rem; + font-weight: 600; + transition: color 0.16s ease, transform 0.16s ease; +} + +.example-card:hover .example-go { + color: var(--accent); + transform: translateX(2px); +} + +/* ===== Method band ===== */ +/* ── popular comparison grid ── */ +.home-compare-grid { + list-style: none; + margin: 0; + padding: 0; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); + gap: 0.65rem; +} + +.home-compare-card { + border: 1px solid var(--border); + border-radius: 12px; + background: var(--card); + transition: box-shadow .15s; +} + +.home-compare-card:hover { + box-shadow: 0 2px 12px rgba(0,0,0,.08); +} + +[data-theme="dark"] .home-compare-card:hover { + box-shadow: 0 2px 12px rgba(0,0,0,.3); +} + +.home-compare-link { + display: flex; + flex-direction: column; + gap: 0.25rem; + padding: 0.9rem 1rem; + text-decoration: none; + color: var(--fg); +} + +.home-compare-cat { + font-size: 0.7rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: .06em; + color: var(--muted); +} + +.compare-data-note { + margin: 0.4rem 0 0; + font-size: 0.85rem; + color: var(--muted); +} + +.home-compare-rank { + font-size: 0.7rem; + font-weight: 800; + color: var(--accent); + opacity: 0.7; +} + +.home-compare-title { + font-size: 0.88rem; + font-weight: 700; + line-height: 1.35; +} + +.home-compare-arrow { + font-size: 0.8rem; + color: var(--muted); + margin-top: 0.1rem; +} + +.home-compare-more { + margin-top: 0.9rem; + text-align: center; + font-size: 0.85rem; +} + +.home-compare-more a { + color: var(--muted); + text-decoration: none; +} + +.home-compare-more a:hover { + color: var(--fg); +} + +/* ── end popular comparison grid ── */ + +.home-method { + display: grid; + grid-template-columns: minmax(220px, 0.8fr) 1.2fr; + gap: 1.4rem; + align-items: start; +} + +.method-grid { + display: grid; + gap: 0.7rem; +} + +.method-item { + display: grid; + grid-template-columns: auto 1fr; + column-gap: 0.85rem; + row-gap: 0.25rem; + padding: 0.95rem 1.1rem; + border: 1px solid var(--border); + border-radius: 16px; + background: rgba(255, 255, 255, 0.74); + box-shadow: var(--shadow-sm); +} + +[data-theme="dark"] .method-item { + background: rgba(255, 255, 255, 0.04); +} + +.method-item span { + grid-row: span 2; + align-self: center; + display: inline-flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + border-radius: 9px; + background: rgba(52, 84, 232, 0.1); + color: var(--accent); + font-size: 0.82rem; + font-weight: 800; +} + +[data-theme="dark"] .method-item span { + background: rgba(91, 120, 245, 0.16); +} + +.method-item h3 { + margin: 0; + font-size: 0.98rem; +} + +.method-item p { + margin: 0; + color: var(--muted); + line-height: 1.55; + font-size: 0.9rem; +} + +.method-em { + font-weight: 600; + color: var(--fg); +} + +/* ── Feature grid (replaces method 01/02/03 cards) ── */ +.feature-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.55rem; + align-content: start; +} + +.feature-item { + display: flex; + flex-direction: column; + gap: 0.18rem; + padding: 0.75rem 0.9rem; + border: 1px solid var(--border); + border-radius: 12px; + background: rgba(52, 84, 232, 0.04); +} + +[data-theme="dark"] .feature-item { + background: rgba(91, 120, 245, 0.07); + border-color: var(--border); +} + +.feature-item strong { + font-size: 0.86rem; + font-weight: 700; + color: var(--fg); + line-height: 1.3; +} + +.feature-item span { + font-size: 0.78rem; + color: var(--muted); + line-height: 1.4; +} + +/* ── Rank list (popular comparisons) ── */ +.home-rank-wrap { + position: relative; + max-height: 305px; + overflow: hidden; +} + +.home-rank-list { + list-style: none; + margin: 0; + padding: 0; +} + +.home-rank-item { + border-bottom: 1px solid var(--border-light); +} + +.home-rank-item:first-child { + border-top: 1px solid var(--border-light); +} + +.home-rank-link { + display: flex; + align-items: center; + gap: 0.9rem; + padding: 0.78rem 0.4rem; + color: var(--fg); + text-decoration: none; + background: none; + border: none; + width: 100%; + cursor: pointer; + font-family: inherit; + text-align: left; + transition: color 0.15s ease; +} + +.home-rank-link:hover { + color: var(--accent); +} + +.home-rank-link:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.home-rank-link.home-rank-loading { + color: var(--accent); +} + +.home-rank-num { + font-size: 0.78rem; + font-weight: 800; + color: var(--accent); + min-width: 18px; + text-align: right; + opacity: 0.8; + flex-shrink: 0; +} + +.home-rank-text { + font-size: 0.93rem; + font-weight: 600; + line-height: 1.35; + flex: 1; +} + +.home-rank-arrow { + font-size: 0.8rem; + color: var(--muted); + flex-shrink: 0; + transition: transform 0.15s ease; +} + +.home-rank-link:hover .home-rank-arrow { + transform: translateX(3px); + color: var(--accent); +} + +@keyframes rankSpin { + to { transform: rotate(360deg); } +} + +.home-rank-spinner { + display: inline-block; + width: 14px; + height: 14px; + border: 2px solid var(--accent); + border-top-color: transparent; + border-radius: 50%; + animation: rankSpin 0.7s linear infinite; + flex-shrink: 0; +} + +.home-rank-fade { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 88px; + background: linear-gradient(to bottom, transparent, var(--bg) 90%); + pointer-events: none; +} + +.vs-shell { + margin-top: 0.5rem; +} + +.vs-grid { + display: grid; + grid-template-columns: 1fr auto 1fr; + gap: 1rem; + align-items: stretch; +} + +.vs-divider { + align-self: center; + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--muted); + opacity: 0.5; + padding: 0 0.25rem; + user-select: none; +} + +.vs-card { + position: relative; + display: flex; + flex-direction: column; + gap: 0.55rem; + min-height: 180px; + padding: 1.2rem; + border: 1px solid var(--border); + border-radius: 24px; + background: var(--card); + box-shadow: var(--shadow-md); + overflow: hidden; + transition: border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease; +} + +.vs-card:focus-within { + border-color: var(--accent); + box-shadow: 0 18px 44px rgba(52, 84, 232, 0.18); + transform: translateY(-2px); +} + +.vs-card input { + border: none; + outline: none; + font-size: 1.35rem; + font-weight: 600; + background: transparent; + padding: 0; + z-index: 1; +} + +.vs-card input::placeholder { + color: #c7cdd8; + font-weight: 500; +} + +.vs-tag { + width: 28px; + height: 28px; + border-radius: 9999px; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 0.85rem; + font-weight: 700; + color: white; + z-index: 1; +} + +.vs-card-a .vs-tag { + background: #3454e8; +} + +.vs-card-b .vs-tag { + background: #7c5cff; +} + +.vs-label { + color: var(--muted); + font-size: 0.9rem; + z-index: 1; +} + +.vs-watermark { + position: absolute; + right: 0.2rem; + bottom: -1.4rem; + font-size: 8rem; + font-weight: 800; + line-height: 1; + opacity: 0.08; + pointer-events: none; +} + +.vs-card-a .vs-watermark { + color: #3454e8; +} + +.vs-card-b .vs-watermark { + color: #7c5cff; +} + +@media (max-width: 560px) { + .vs-grid { + grid-template-columns: 1fr; + } + + .vs-divider { + text-align: center; + padding: 0.2rem 0; + } +} + +.btn-primary { + width: 100%; + margin-top: 1.2rem; + border: none; + border-radius: 14px; + padding: 0.9rem 1rem; + color: white; + font-size: 1rem; + font-weight: 700; + background: linear-gradient(135deg, #1a2238 0%, #2d3f86 55%, #4b54c9 100%); + cursor: pointer; + transition: transform 0.16s ease, box-shadow 0.16s ease, opacity 0.16s ease; + box-shadow: 0 8px 20px rgba(45, 63, 134, 0.22); +} + +.btn-primary:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 14px 28px rgba(45, 63, 134, 0.3); +} + +.btn-primary:active:not(:disabled) { + transform: translateY(0); +} + +.btn-primary.large { + padding: 1.05rem 1rem; + font-size: 1.05rem; +} + +.btn-primary:disabled { + opacity: 0.45; + cursor: not-allowed; + box-shadow: none; +} + +.btn-outline { + width: 100%; + margin-top: 0.8rem; + border: 1px solid var(--border); + border-radius: 14px; + padding: 0.85rem 1rem; + text-align: center; + background: white; + cursor: pointer; + font-weight: 600; + transition: border-color 0.16s ease, color 0.16s ease, transform 0.16s ease; +} + +.btn-outline:hover { + border-color: var(--accent); + color: var(--accent); + transform: translateY(-1px); +} + +.btn-primary.block { + display: block; + text-align: center; +} + +.history-card, +.result-card, +.detail-card { + border: 1px solid var(--border); + border-radius: 20px; + background: var(--card); + box-shadow: var(--shadow-sm); +} + +[data-theme="dark"] .history-card, +[data-theme="dark"] .result-card, +[data-theme="dark"] .detail-card { + background: var(--card); +} + +.history-card { + margin-top: 1.5rem; + padding: 0; + overflow: hidden; +} + +.result-card, +.detail-card { + margin-top: 1rem; + padding: 1.4rem 1.5rem; +} + +.detail-card h2 { + margin: 0 0 1.1rem; + font-size: 0.82rem; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--muted); +} diff --git a/app/styles/results.css b/app/styles/results.css new file mode 100644 index 0000000..c0b7c18 --- /dev/null +++ b/app/styles/results.css @@ -0,0 +1,915 @@ +/* ════════════════════════════════════════ + Verdict hero — 결과 첫 블록 (프리미엄) + ════════════════════════════════════════ */ +.verdict-hero { + position: relative; + overflow: hidden; + margin-top: 0; + padding: 1.8rem 1.9rem 1.7rem; + border: 1px solid var(--border); + border-radius: 22px; + background: var(--card); + box-shadow: var(--shadow-md); +} + +/* 상단 accent 라인 */ +.verdict-hero::before { + content: ""; + position: absolute; + inset: 0 0 auto 0; + height: 3px; + background: linear-gradient(90deg, var(--accent), var(--accent-2)); +} + +/* AXIS 선택 배지 */ +.vh-badge { + display: inline-flex; + align-items: center; + gap: 0.45rem; + padding: 0.34rem 0.75rem 0.34rem 0.6rem; + border-radius: 9999px; + background: color-mix(in srgb, var(--accent) 10%, transparent); + color: var(--accent); + font-size: 0.72rem; + font-weight: 800; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.vh-badge-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--accent); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent); +} + +.vh-title { + margin: 0.85rem 0 0.5rem; + font-size: clamp(1.7rem, 5vw, 2.55rem); + font-weight: 900; + line-height: 1.12; + letter-spacing: -0.035em; + color: var(--text); + word-break: break-word; + overflow-wrap: break-word; +} + +.vh-conclusion { + margin: 0; + max-width: 34rem; + color: var(--muted); + font-size: 1.02rem; + line-height: 1.62; + word-break: break-word; + overflow-wrap: break-word; +} + +/* 승부 막대 영역 */ +.vh-matchup { + margin-top: 1.6rem; + padding-top: 1.5rem; + border-top: 1px solid var(--border-light); + display: flex; + flex-direction: column; + gap: 1rem; +} + +.vh-row { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.vh-row-head { + display: flex; + align-items: center; + gap: 0.55rem; + min-width: 0; +} + +.vh-row-name { + font-size: 0.92rem; + font-weight: 600; + color: var(--muted); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; +} + +.vh-row-win .vh-row-name { + color: var(--text); + font-weight: 800; +} + +.vh-row-tag { + flex-shrink: 0; + padding: 0.12rem 0.5rem; + border-radius: 9999px; + background: var(--accent); + color: #fff; + font-size: 0.64rem; + font-weight: 800; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.vh-bar-track { + height: 8px; + border-radius: 9999px; + background: var(--border-light); + overflow: hidden; +} + +.vh-bar-fill { + height: 100%; + border-radius: 9999px; + background: var(--border); + transition: width 0.7s cubic-bezier(0.22, 1, 0.36, 1); +} + +.vh-row-win .vh-bar-track { + background: color-mix(in srgb, var(--accent) 14%, transparent); +} + +.vh-row-win .vh-bar-fill { + background: linear-gradient(90deg, var(--accent), var(--accent-2)); +} + +.vh-bar-note { + margin: 0.1rem 0 0; + font-size: 0.72rem; + color: var(--muted); + opacity: 0.6; + line-height: 1.4; +} + +/* result-card / result-conclusion — admin hero 등에서 재사용되는 기본형 */ +.result-card { + padding: 1.7rem 1.8rem; + text-align: center; +} + +.result-conclusion { + margin: 0.5rem auto 0; + max-width: 30rem; + color: var(--muted); + font-size: 1rem; + line-height: 1.62; +} + +.detail-card ul { + margin: 0; + padding-left: 1.1rem; +} + +/* Reason list */ +.reason-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 0; +} + +.reason-list li { + display: flex; + align-items: flex-start; + gap: 0.75rem; + line-height: 1.6; + font-size: 0.95rem; + padding: 0.75rem 0; + border-bottom: 1px solid var(--border-light); +} + +.reason-list li:last-child { border-bottom: none; } + +.reason-mark { + flex-shrink: 0; + width: 20px; + height: 20px; + margin-top: 0.2rem; + border-radius: 50%; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 0.58rem; + font-weight: 900; + color: white; + background: linear-gradient(135deg, #3454e8 0%, #7c5cff 100%); + box-shadow: 0 2px 6px rgba(52, 84, 232, 0.3); +} + + +/* ---- spec section ---- */ +.spec-section { + padding-bottom: 0; +} + +.spec-source-note { + margin: 0 0 0.9rem; + color: var(--muted); + font-size: 0.82rem; + font-weight: 500; +} + +/* Scroll wrapper for horizontal overflow — extends to card edges */ +.spec-table-scroll { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + margin: 0 -1.5rem; + padding: 0 1.5rem 1.4rem; +} + +/* Official link icon inside column header */ +.cmp-head-name { + display: flex; + align-items: center; + gap: 0.35rem; +} + +.cmp-head-link { + font-size: 0.75rem; + color: var(--muted); + opacity: 0.7; + flex-shrink: 0; + transition: opacity 0.15s; +} + +.cmp-head-link:hover { + opacity: 1; + color: var(--accent); +} + +.cmp-source-chip { + display: inline-flex; + align-items: center; + gap: 0.18rem; + max-width: 9.5rem; + padding: 0.16rem 0.45rem; + border-radius: 9999px; + border: 1px solid rgba(52, 84, 232, 0.16); + background: rgba(52, 84, 232, 0.08); + color: var(--accent); + font-size: 0.66rem; + font-weight: 800; + line-height: 1.2; + white-space: nowrap; +} + +.cmp-source-chip.source-authorized_importer { + border-color: rgba(16, 185, 129, 0.2); + background: rgba(16, 185, 129, 0.09); + color: #059669; +} + +.cmp-source-chip:hover { + border-color: currentColor; +} + +.admin-hero h1 { + font-size: clamp(2rem, 4vw, 3rem); +} + +.admin-source-card { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.admin-grid { + display: grid; + grid-template-columns: minmax(0, 1fr) 8rem; + gap: 0.8rem; +} + +.admin-field { + display: flex; + flex-direction: column; + gap: 0.45rem; + font-size: 0.84rem; + font-weight: 700; + color: var(--text); +} + +.admin-field input, +.admin-field select, +.admin-field textarea { + width: 100%; + border: 1.5px solid var(--border); + border-radius: 12px; + background: var(--bg); + color: var(--text); + font: inherit; + box-sizing: border-box; +} + +.admin-field input, +.admin-field select { + min-height: 3rem; + padding: 0 0.9rem; +} + +.admin-field textarea { + min-height: 22rem; + padding: 0.9rem; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; + font-size: 0.86rem; + line-height: 1.55; + resize: vertical; +} + +.admin-field input:focus, +.admin-field select:focus, +.admin-field textarea:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--ring); +} + +.admin-actions { + display: grid; + grid-template-columns: 1fr auto; + gap: 0.8rem; + align-items: center; +} + +.admin-actions .btn-primary, +.admin-actions .btn-outline { + margin-top: 0; +} + +.admin-validity { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 2.5rem; + border-radius: 9999px; + padding: 0 0.85rem; + font-size: 0.84rem; + font-weight: 800; + white-space: nowrap; +} + +.admin-validity.valid { + color: #059669; + background: rgba(16, 185, 129, 0.09); +} + +.admin-validity.invalid { + color: #dc2626; + background: rgba(220, 38, 38, 0.08); +} + +.admin-source-summary { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + align-items: center; + color: var(--muted); + font-size: 0.88rem; +} + +.admin-source-summary span, +.admin-source-summary a { + display: inline-flex; + align-items: center; + border: 1px solid var(--border); + border-radius: 9999px; + padding: 0.34rem 0.7rem; + background: rgba(52, 84, 232, 0.06); + font-weight: 700; +} + +.admin-source-summary a { + color: var(--accent); +} + +.admin-message { + margin: 0; + color: var(--muted); + font-size: 0.92rem; + line-height: 1.5; +} + +/* Empty spec state */ +.spec-empty { + padding: 1.2rem 0 1.4rem; + text-align: center; +} + +.spec-empty p { + margin: 0 0 0.9rem; + color: var(--muted); + font-size: 0.92rem; + line-height: 1.55; +} + +.spec-empty-links { + display: flex; + flex-wrap: wrap; + gap: 0.6rem; + justify-content: center; +} + +.spec-empty-links a { + display: inline-flex; + align-items: center; + padding: 0.4rem 0.85rem; + border-radius: 9999px; + border: 1px solid var(--border); + font-size: 0.85rem; + font-weight: 600; + color: var(--text); + transition: border-color 0.15s, color 0.15s; +} + +.spec-empty-links a:hover { + border-color: var(--accent); + color: var(--accent); +} + +.hint { + margin: 0.8rem 0 0; + color: var(--muted); + font-size: 0.9rem; + text-align: center; +} + +.hint.error { + color: #dc2626; +} + +.guest { + display: inline-block; +} + +/* ---- back button ---- */ +.btn-back { + display: inline-flex; + align-items: center; + gap: 0.25rem; + font-size: 0.92rem; + font-weight: 500; + color: var(--muted); + margin-bottom: 1.2rem; + transition: color 0.16s ease; +} + +.btn-back:hover { + color: var(--text); +} + +/* ---- settings bar ---- */ +.settings-bar { + display: flex; + align-items: center; + gap: 0.5rem; +} + +/* Language dropdown */ +.lang-dropdown { + position: relative; +} + +.lang-trigger { + display: flex; + align-items: center; + gap: 0.3rem; + border: 1px solid var(--border); + border-radius: 10px; + padding: 0.38rem 0.7rem; + background: white; + font-size: 0.82rem; + font-weight: 600; + color: var(--text); + cursor: pointer; + transition: border-color 0.14s ease, color 0.14s ease; + white-space: nowrap; +} + +.lang-trigger:hover { + border-color: var(--accent); + color: var(--accent); +} + +.lang-globe { + font-size: 0.9rem; + opacity: 0.6; +} + +.lang-caret { + font-size: 0.55rem; + color: var(--muted); + margin-left: 0.05rem; +} + +.lang-menu { + position: absolute; + right: 0; + top: calc(100% + 0.4rem); + background: var(--card); + border: 1px solid var(--border); + border-radius: 12px; + box-shadow: var(--shadow-md); + padding: 0.3rem; + min-width: 110px; + z-index: 30; +} + +.lang-option { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + text-align: left; + border: none; + background: transparent; + padding: 0.5rem 0.7rem; + border-radius: 8px; + font-size: 0.85rem; + font-weight: 500; + color: var(--text); + cursor: pointer; + transition: background 0.12s ease; +} + +.lang-option:hover { + background: var(--bg); +} + +.lang-option.active { + font-weight: 700; + color: var(--accent); +} + +.lang-check { + font-size: 0.72rem; + font-weight: 800; + color: var(--accent); +} + +[data-theme="dark"] .lang-trigger { + background: var(--card); +} + +.theme-toggle { + width: 34px; + height: 34px; + border: 1px solid var(--border); + border-radius: 10px; + background: white; + font-size: 1rem; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + transition: border-color 0.14s ease; +} + +[data-theme="dark"] .theme-toggle { + background: var(--card); + color: var(--text); +} + +.theme-toggle:hover { + border-color: var(--accent); +} + +/* ---- user dropdown menu ---- */ +.user-nav { + position: relative; +} + +.user-trigger { + display: flex; + align-items: center; + gap: 0.5rem; + border: 1px solid var(--border); + border-radius: 9999px; + padding: 0.35rem 0.7rem 0.35rem 0.4rem; + background: white; + cursor: pointer; +} + +.user-avatar { + width: 28px; + height: 28px; + border-radius: 9999px; + display: inline-flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #3454e8, #7c5cff); + color: white; + font-size: 0.85rem; + font-weight: 700; + flex-shrink: 0; +} + +.user-avatar.lg { + width: 40px; + height: 40px; + font-size: 1.1rem; +} + +.user-caret { + color: var(--muted); + font-size: 0.7rem; +} + +.user-dropdown { + position: absolute; + right: 0; + top: calc(100% + 0.5rem); + width: 250px; + background: white; + border: 1px solid var(--border); + border-radius: 16px; + box-shadow: 0 16px 40px rgba(17, 24, 39, 0.12); + padding: 0.5rem; + z-index: 20; +} + +.user-dropdown-head { + display: flex; + align-items: center; + gap: 0.7rem; + padding: 0.6rem 0.6rem 0.8rem; + border-bottom: 1px solid var(--border); +} + +.user-dropdown-id { + display: flex; + flex-direction: column; + min-width: 0; +} + +.user-dropdown-id strong { + font-size: 0.9rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.user-dropdown-id span { + color: var(--muted); + font-size: 0.78rem; +} + +.user-dropdown-list { + display: flex; + flex-direction: column; + padding: 0.4rem 0; +} + +.user-dropdown-list a { + padding: 0.6rem 0.6rem; + border-radius: 10px; + font-size: 0.92rem; +} + +.user-dropdown-list a:hover { + background: var(--bg); +} + +.user-dropdown-signout { + width: 100%; + text-align: left; + border: none; + border-top: 1px solid var(--border); + background: transparent; + color: #dc2626; + font-size: 0.92rem; + padding: 0.7rem 0.6rem 0.5rem; + cursor: pointer; +} + +/* ---- account page ---- */ +.account-card { + display: flex; + flex-direction: column; + gap: 0; + padding: 0.4rem 1.2rem; +} + +.account-row { + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; + padding: 0.9rem 0; + border-bottom: 1px solid var(--border); +} + +.account-row:last-child { + border-bottom: none; +} + +.account-key { + color: var(--muted); + font-size: 0.9rem; +} + +.account-val { + font-weight: 600; + text-align: right; +} + +.hint-inline { + color: var(--muted); + font-weight: 400; + font-size: 0.85rem; +} + +.plan-badge { + display: inline-block; + padding: 0.2rem 0.6rem; + border-radius: 9999px; + font-size: 0.8rem; + font-weight: 700; +} + +.plan-badge.free { + background: var(--bg); + color: var(--muted); +} + +.plan-badge.pro { + background: linear-gradient(135deg, #3454e8, #7c5cff); + color: white; +} + +.upsell { + text-align: center; +} + +.account-actions-row { + margin-top: 1.2rem; +} + +/* ---- membership / pricing ---- */ +.membership .feature-table, +.membership > .hint { + max-width: 720px; + margin-left: auto; + margin-right: auto; +} + +.plan-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; + margin-top: 1rem; +} + +.plan-grid.three { + grid-template-columns: repeat(3, 1fr); + align-items: stretch; +} + +.plan-grid.three .plan-card { + display: flex; + flex-direction: column; +} + +.plan-grid.three .plan-points { + flex: 1; +} + +@media (max-width: 760px) { + .plan-grid, + .plan-grid.three { + grid-template-columns: 1fr; + } +} + +.plan-badge.plus { + background: rgba(52, 84, 232, 0.12); + color: var(--accent); +} + +.plan-card { + position: relative; + border: 1px solid var(--border); + border-radius: 20px; + background: white; + padding: 1.4rem; + box-shadow: 0 10px 30px rgba(17, 24, 39, 0.06); +} + +.plan-card.featured { + border-color: #3454e8; + box-shadow: 0 16px 40px rgba(52, 84, 232, 0.18); +} + +.plan-ribbon { + position: absolute; + top: -0.7rem; + right: 1.2rem; + background: linear-gradient(135deg, #3454e8, #7c5cff); + color: white; + font-size: 0.72rem; + font-weight: 700; + padding: 0.25rem 0.7rem; + border-radius: 9999px; +} + +.plan-head h2 { + margin: 0; + font-size: 1.3rem; +} + +.plan-price { + margin: 0.5rem 0 0.2rem; + font-size: 2rem; + font-weight: 800; +} + +.plan-price span { + font-size: 0.95rem; + font-weight: 500; + color: var(--muted); +} + +.plan-tagline { + margin: 0 0 1rem; + color: var(--muted); + font-size: 0.9rem; +} + +.plan-points { + list-style: none; + padding: 0; + margin: 0 0 1.2rem; + display: flex; + flex-direction: column; + gap: 0.55rem; +} + +.plan-points li { + position: relative; + padding-left: 1.4rem; + font-size: 0.92rem; +} + +.plan-points li::before { + content: "✓"; + position: absolute; + left: 0; + color: #3454e8; + font-weight: 700; +} + +.plan-current { + text-align: center; + color: var(--muted); + font-size: 0.9rem; + margin: 0; +} + +.feature-table { + margin-top: 1.2rem; +} + +.feat-grid { + display: flex; + flex-direction: column; +} + +.feat-row { + display: grid; + grid-template-columns: 2fr 1fr 1fr; + align-items: center; + gap: 0.6rem; + padding: 0.7rem 0; + border-bottom: 1px solid var(--border); + font-size: 0.92rem; +} + +.feat-grid.three .feat-row { + grid-template-columns: 1.6fr 1fr 1fr 1fr; +} + +.feat-row:last-child { + border-bottom: none; +} + +.feat-row > span:not(:first-child) { + text-align: center; +} + +.feat-header { + font-weight: 700; + color: var(--muted); +} + +.feat-yes { + color: #3454e8; + font-weight: 700; +} + +.feat-no { + color: #c7cdd8; +} diff --git a/components/results-view.tsx b/components/results-view.tsx index c4cfd89..9c6eede 100644 --- a/components/results-view.tsx +++ b/components/results-view.tsx @@ -7,8 +7,8 @@ import ContextCard from "@/components/context-card"; import TimingSection from "@/components/timing-section"; import PriceComparison from "@/components/price-comparison"; import { getDictionary, type Locale } from "@/lib/i18n"; -import { resolveFieldByLabel } from "@/lib/specs/schema"; -import { coverageNoteYear } from "@/lib/specs/coverage"; +import { fieldLabelForLocale, resolveFieldByLabel } from "@/lib/specs/schema"; +import { localizeSpecValue } from "@/lib/specs/localize-value"; import SpecGraphs, { type SpecGraph } from "@/components/spec-graphs"; import type { Category } from "@/lib/types"; @@ -47,6 +47,42 @@ function normalize(result: ComparisonResult, query: string) { return { options, rows, sources, sourceMeta, analyses }; } +/** Remap row labels + values to the active UI locale (works even on stale session payloads). */ +function localizeRows( + rows: NormalizedRow[], + category: Category, + locale: Locale +): NormalizedRow[] { + return rows.map((row) => { + const field = resolveFieldByLabel(category, row.key); + if (!field) return row; + return { + key: fieldLabelForLocale(field, locale), + values: row.values.map((v) => { + const localized = localizeSpecValue(field.key, v ?? "—", locale); + return localizeEmbeddedUnits(localized, locale); + }), + }; + }); +} + +function localizeEmbeddedUnits(value: string, locale: Locale): string { + if (locale === "ko" || !value || value === "—") return value; + let next = value; + if (locale === "en") { + next = next + .replace(/(\d+(?:\.\d+)?)\s*인치/g, "$1 in") + .replace(/(\d+(?:\.\d+)?)\s*시간/g, "$1 h") + .replace(/(\d+(?:\.\d+)?)\s*원/g, "₩$1"); + } else if (locale === "ja") { + next = next + .replace(/(\d+(?:\.\d+)?)\s*인치/g, "$1インチ") + .replace(/(\d+(?:\.\d+)?)\s*시간/g, "$1時間") + .replace(/원/g, "ウォン"); + } + return next.replace(/\s+/g, " ").trim(); +} + function computeFitScores( options: string[], comparison: ComparisonRow[], @@ -103,6 +139,7 @@ function buildSpecGraphs( graphs.push({ key: row.key, + better: field.better, values: values.map((num, i) => ({ label: options[i] ?? `Option ${i + 1}`, raw: row.values[i] ?? "—", @@ -129,7 +166,8 @@ export default function ResultsView({ slug, region, }: Props) { - const { options, rows, sources, sourceMeta, analyses } = normalize(result, query); + const { options, rows: rawRows, sources, sourceMeta, analyses } = normalize(result, query); + const rows = localizeRows(rawRows, result.category, locale); const selectedIndex = options.findIndex((o) => o === result.selectedOption); const cols = options.length; const t = getDictionary(locale).results; @@ -139,7 +177,6 @@ export default function ResultsView({ const specGraphs = isBlockedResult ? [] : buildSpecGraphs(options, rows, result.category); - const coverageYear = coverageNoteYear(); const showVerifyBadge = result.verification != null && @@ -170,7 +207,6 @@ export default function ResultsView({

{result.selectedOption}

{result.oneLineConclusion ?? t.defaultConclusion}

- {/* 승부 막대: 각 옵션의 스펙 우위를 % 없이 막대 길이로만 표현 */} {options.length > 1 && !isBlockedResult && (
{options.map((opt, i) => ( @@ -217,7 +253,6 @@ export default function ResultsView({ )} - {specGraphs.length > 0 && ( g.key).join("|")} @@ -225,12 +260,11 @@ export default function ResultsView({ labels={{ title: t.specGraphs, select: t.specGraphSelect, - numericOnly: t.specGraphNumericOnly, }} /> )} - {/* ── 3. Spec table ── */} + {/* ── Spec table ── */}

{showVerifyBadge ? t.specComparison : t.specComparisonPending}

@@ -245,7 +279,6 @@ export default function ResultsView({ )}
-

{t.coverageNote(coverageYear)}

{rows.length > 0 ? (
@@ -302,11 +335,7 @@ export default function ResultsView({ )}
- - {/* ── 3b. Spec graphs ── */} - - - {/* ── 4. Per-option analysis ── */} + {/* ── Per-option analysis ── */} {analyses.some(Boolean) && (

{t.perItemAnalysis}

@@ -343,7 +372,7 @@ export default function ResultsView({
)} - {/* ── 5. Why chosen (추천이유 — 아래로 이동) ── */} + {/* Why chosen */} {!isBlockedResult && (

{t.whyChosen}

diff --git a/components/session-results.tsx b/components/session-results.tsx index 00dc363..8ec7d37 100644 --- a/components/session-results.tsx +++ b/components/session-results.tsx @@ -1,7 +1,6 @@ "use client"; import { useEffect, useState } from "react"; -import { useRouter } from "next/navigation"; import Link from "next/link"; import ResultsView from "@/components/results-view"; import type { ComparisonResult } from "@/lib/types"; @@ -30,6 +29,10 @@ function optionsFromPayload(payload: Payload): string[] { .filter(Boolean); } +function payloadLocale(payload: Payload): Locale { + return payload.locale ?? payload.result.locale ?? "ko"; +} + export default function SessionResults({ locale: localeProp }: { locale?: Locale }) { const { locale: themeLocale } = useTheme(); const locale = localeProp ?? themeLocale; @@ -38,7 +41,8 @@ export default function SessionResults({ locale: localeProp }: { locale?: Locale const [payload, setPayload] = useState(null); const [ready, setReady] = useState(false); const [isRefreshingLocale, setIsRefreshingLocale] = useState(false); - const router = useRouter(); + const [refreshError, setRefreshError] = useState(false); + const [retryToken, setRetryToken] = useState(0); useEffect(() => { let cancelled = false; @@ -63,14 +67,27 @@ export default function SessionResults({ locale: localeProp }: { locale?: Locale }, []); useEffect(() => { - if (!ready || !payload || payload.locale === locale) return; + if (!ready || !payload) return; + if (payloadLocale(payload) === locale) { + queueMicrotask(() => { + setRefreshError(false); + setIsRefreshingLocale(false); + }); + return; + } const options = optionsFromPayload(payload); - if (options.length < 2) return; + if (options.length < 2) { + queueMicrotask(() => setRefreshError(true)); + return; + } let cancelled = false; queueMicrotask(() => { - if (!cancelled) setIsRefreshingLocale(true); + if (!cancelled) { + setIsRefreshingLocale(true); + setRefreshError(false); + } }); fetch("/api/compare", { @@ -83,16 +100,24 @@ export default function SessionResults({ locale: localeProp }: { locale?: Locale return (await response.json()) as CompareResponse; }) .then((body) => { - if (cancelled || !body?.result) return; - const next = { + if (cancelled) return; + if (!body?.result) { + setRefreshError(true); + return; + } + const next: Payload = { query: body.result.options.join(" vs "), options: body.result.options, locale, result: body.result }; setPayload(next); + setRefreshError(false); sessionStorage.setItem(SESSION_RESULT_KEY, JSON.stringify(next)); }) + .catch(() => { + if (!cancelled) setRefreshError(true); + }) .finally(() => { if (!cancelled) setIsRefreshingLocale(false); }); @@ -100,14 +125,13 @@ export default function SessionResults({ locale: localeProp }: { locale?: Locale return () => { cancelled = true; }; - }, [locale, payload, ready]); + }, [locale, payload, ready, retryToken]); if (!ready) { return null; } if (!payload) { - // 결과 데이터 없이 /results 직접 접근한 경우 → 홈으로 부드럽게 유도 return (
@@ -122,7 +146,32 @@ export default function SessionResults({ locale: localeProp }: { locale?: Locale ); } - if (isRefreshingLocale) { + const localeMismatch = payloadLocale(payload) !== locale; + + if (localeMismatch && refreshError) { + return ( +
+
+

{dictionary.error.title}

+

{t.localeRefreshFailed}

+
+ + + {dictionary.error.backHome} + +
+
+
+ ); + } + + if (localeMismatch || isRefreshingLocale) { return (

{dictionary.error.loading}

diff --git a/components/settings-bar.tsx b/components/settings-bar.tsx index 93c1b37..6655a11 100644 --- a/components/settings-bar.tsx +++ b/components/settings-bar.tsx @@ -19,7 +19,6 @@ export default function SettingsBar() { return (
- {/* Language dropdown */}