diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 43faabb..b15dc09 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -2,650 +2,67 @@ @tailwind components; @tailwind utilities; -/* ==================== InfiPlot — low-fi prototype tokens ==================== */ -:root { - --ink: #3a3a38; - --ink-soft: #6f6e69; - --ink-faint: #a9a7a0; - --line: #cfccc4; - --paper: #f3f1ec; - --paper-2: #e9e6df; - --fill: #ddd9d0; - --accent: #d4824a; - --jit: 1; - --sketch-filter: url(#s2); -} - @layer base { html { font-feature-settings: "ss01", "kern", "liga"; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - background: var(--paper); } body { - background: - repeating-linear-gradient(0deg, transparent 0 38px, rgba(0, 0, 0, 0.018) 38px 39px), - var(--paper); - color: var(--ink); - font-family: "Noto Sans SC", "PingFang SC", system-ui, sans-serif; + background-image: + radial-gradient(rgba(133, 79, 37, 0.025) 1px, transparent 1px), + radial-gradient(rgba(133, 79, 37, 0.018) 1px, transparent 1px); + background-size: 28px 28px, 38px 38px; + background-position: 0 0, 14px 19px; } ::selection { - background-color: rgb(212 130 74 / 0.28); - color: var(--ink); + background-color: rgb(217 122 46 / 0.28); + color: #2d1810; + } + + textarea::placeholder { + color: rgb(168 105 59 / 0.45); } } @layer utilities { - .latin { - font-family: "Patrick Hand", "Caveat", "Cormorant Garamond", cursive; + .hairline { + background-image: linear-gradient( + to right, + transparent, + rgba(45, 24, 16, 0.18) 18%, + rgba(45, 24, 16, 0.18) 82%, + transparent + ); + height: 1px; + } + + .hairline-full { + height: 1px; + background: rgba(45, 24, 16, 0.14); + } + + .num { + font-variant-numeric: tabular-nums lining-nums; + } + + .smallcaps { + text-transform: uppercase; + letter-spacing: 0.32em; } } -/* ==================== hand-drawn frame ==================== */ -.frame { - position: absolute; - inset: 0; - border: 2px solid var(--ink); - border-radius: 12px; - filter: var(--sketch-filter); - background: transparent; - pointer-events: none; -} -.frame.soft { - border-color: var(--ink-soft); -} - -/* ==================== logo ==================== */ -.ip-logo { - display: inline-flex; - align-items: center; - gap: 12px; -} -.ip-logo .mark { - position: relative; - width: 30px; - height: 30px; -} -.ip-logo .mark .frame { - border-radius: 50%; -} -.ip-logo .mark span { - position: absolute; - inset: 0; - display: grid; - place-items: center; - font-size: 15px; - color: var(--ink-soft); -} -.ip-logo .word { - font-family: "Patrick Hand", "Caveat", cursive; - font-size: 24px; - letter-spacing: 2px; - color: var(--ink); - font-weight: 400; -} - -/* ==================== input bar (prompt + start) ==================== */ -.ip-tagline { - font-size: clamp(22px, 2.6vw, 33px); - color: var(--ink); - font-weight: 500; - letter-spacing: 1px; - text-align: center; -} -.ip-bar { - display: flex; - gap: 16px; - align-items: stretch; - width: min(1100px, 92vw); - height: 68px; -} -.ip-field { - position: relative; - flex: 1; -} -.ip-field .frame { - border-radius: 16px; -} -.ip-field input { - position: relative; - width: 100%; - height: 100%; - padding: 0 28px; - background: transparent; - border: none; - outline: none; - font: inherit; - font-size: 20px; - color: var(--ink); - z-index: 1; -} -.ip-field input::placeholder { - color: transparent; -} -.ip-field .ph { - position: absolute; - inset: 0; - display: flex; - align-items: center; - padding: 0 28px; - font-size: 20px; - color: var(--ink-faint); - font-weight: 300; - white-space: nowrap; - overflow: hidden; - pointer-events: none; - z-index: 0; -} -.ip-cursor { - display: inline-block; - width: 2px; - height: 23px; - background: var(--ink-faint); - margin: 0 1px 0 3px; - vertical-align: -4px; - animation: ip-blink 1.1s steps(1) infinite; -} -@keyframes ip-blink { - 50% { opacity: 0; } -} - -.ip-start { - position: relative; - width: 176px; - flex: none; - display: grid; - place-items: center; - cursor: pointer; - border: none; - background: transparent; - padding: 0; -} -.ip-start .frame { - border-radius: 16px; - background: var(--accent); - border-color: var(--accent); - z-index: 0; -} -.ip-start span { - position: relative; - z-index: 1; - color: #fff; - font-size: 22px; - letter-spacing: 6px; - font-weight: 500; - padding-left: 6px; -} -.ip-start:disabled { - opacity: 0.55; - cursor: not-allowed; -} - -/* ==================== collapsible category pills ==================== */ -.ip-cat { - position: relative; -} -.ip-catbtn { - position: relative; - height: 42px; - padding: 0 16px; - display: flex; - align-items: center; - gap: 9px; - cursor: pointer; - white-space: nowrap; - border: none; - background: transparent; -} -.ip-catbtn .frame { - border-radius: 21px; - border-color: var(--line); -} -.ip-catname { - position: relative; - z-index: 2; - font-size: 12.5px; - color: var(--ink-faint); -} -.ip-catval { - position: relative; - z-index: 2; - font-size: 15px; - color: var(--ink); - font-weight: 600; -} -.ip-caret { - position: relative; - z-index: 2; - font-size: 11px; - color: var(--ink-soft); - transition: transform 0.15s; -} -.ip-cat.open .ip-caret { - transform: rotate(180deg); -} -.ip-cat.open .ip-catbtn .frame { - border-color: var(--accent); -} -.ip-cat.open .ip-catval { - color: var(--accent); -} -.ip-catmenu { - position: absolute; - top: 50px; - left: 0; - min-width: calc(100% + 8px); - padding: 7px; - z-index: 20; - display: flex; - flex-direction: column; - gap: 2px; - background: var(--paper); -} -.ip-catmenu .frame { - border-radius: 12px; - border-color: var(--ink-soft); -} -.ip-catopt { - position: relative; - z-index: 1; - padding: 8px 16px; - border-radius: 8px; - font-size: 14px; - color: var(--ink-soft); - cursor: pointer; - white-space: nowrap; - background: transparent; - border: none; - text-align: left; - font: inherit; -} -.ip-catopt:hover { - background: var(--paper-2); -} -.ip-catopt.on { - color: var(--accent); - font-weight: 600; -} -.ip-catopt.on::after { - content: "\2713"; - margin-left: 8px; - font-size: 12px; -} - -/* ==================== scattered story cards ==================== */ -.ip-card { - position: absolute; - cursor: pointer; - transition: transform 0.25s ease; -} -.ip-card:hover { - transform: rotate(0deg) translateY(-4px) !important; -} -.ip-card .inner { - position: absolute; - inset: 0; - border-radius: 12px; - overflow: hidden; -} -.ip-card .img { - position: absolute; - inset: 0; - background: var(--fill); - display: grid; - place-items: center; -} -.ip-card .img svg { - width: 40%; - max-width: 120px; - opacity: 0.55; -} -.ip-card .img img.card-photo { - width: 100%; - height: 100%; - object-fit: cover; - display: block; -} -.ip-card .frame { - z-index: 3; -} -/* hover overlay: bottom-up dark gradient with title + outline */ -.ip-hover { - position: absolute; - inset: 0; - z-index: 2; - display: flex; - flex-direction: column; - justify-content: flex-end; - padding: 16px 18px 16px; - color: #fff; - background: linear-gradient(to top, rgba(0, 0, 0, 0.9), rgba(0, 0, 0, 0.45) 45%, rgba(0, 0, 0, 0) 100%); - opacity: 0; - transition: opacity 0.3s ease; - user-select: none; - border-radius: 12px; -} -.ip-card:hover .ip-hover { - opacity: 1; -} -.ip-hover-title { - margin: 0 0 6px; - font-size: 16px; - font-weight: 700; - letter-spacing: 0.08em; - color: #fff; -} -.ip-hover-outline { - margin: 0; - font-size: 12px; - line-height: 1.55; - color: rgba(231, 226, 215, 0.92); - font-style: italic; - display: -webkit-box; - -webkit-line-clamp: 4; - -webkit-box-orient: vertical; - overflow: hidden; -} - -/* ==================== gallery masonry ==================== */ -.ip-gallery { - width: min(1640px, 96vw); - margin: 14px auto 0; - column-count: 4; - column-gap: 24px; -} -@media (max-width: 1100px) { - .ip-gallery { column-count: 3; } -} -@media (max-width: 780px) { - .ip-gallery { column-count: 2; } -} -@media (max-width: 480px) { - .ip-gallery { column-count: 1; } -} -.ip-card.gcard { - position: relative; - width: 100%; - display: inline-block; - margin: 0 0 24px; - break-inside: avoid; - transform: rotate(calc(var(--gr, 0deg) * var(--jit))); -} - -.ip-sectionnote { - width: 100%; - margin: 34px auto 6px; - text-align: center; - font-family: "Patrick Hand", "Caveat", cursive; - font-size: 19px; - color: var(--accent); -} -.ip-sectionnote .arr { - display: block; - font-size: 22px; - line-height: 1; - margin-bottom: 2px; -} - -/* ==================== project intro ==================== */ -.ip-intro { - position: relative; - width: min(1500px, 94vw); - margin: 70px auto 110px; - padding: 58px clamp(28px, 5vw, 80px) 64px; -} -.ip-intro .frame { - border-radius: 16px; - border-color: var(--ink-soft); -} -.ip-intro .imark { - position: relative; - z-index: 1; - display: flex; - align-items: center; - gap: 14px; - margin-bottom: 6px; -} -.ip-intro .imark .gl { - width: 34px; - height: 34px; - display: grid; - place-items: center; - font-size: 18px; - color: var(--ink-soft); - border: 2px solid var(--ink-soft); - border-radius: 50%; -} -.ip-intro h2 { - position: relative; - z-index: 1; - margin: 0; - font-family: "Patrick Hand", "Caveat", cursive; - font-size: 38px; - letter-spacing: 1px; - color: var(--ink); - font-weight: 400; -} -.ip-intro .kicker { - position: relative; - z-index: 1; - font-size: 15px; - color: var(--ink-soft); - letter-spacing: 3px; - margin: 2px 0 26px; - font-family: "Patrick Hand", "Caveat", cursive; -} -.ip-intro p { - position: relative; - z-index: 1; - max-width: 1180px; - font-size: 18px; - line-height: 1.9; - color: var(--ink-soft); - margin: 0 0 18px; - font-weight: 300; - text-wrap: pretty; -} -.ip-intro .label { - position: relative; - z-index: 1; - font-size: 13px; - color: var(--ink-faint); - letter-spacing: 2px; - margin: 26px 0 8px; - font-weight: 500; -} -.ip-intro b { - color: var(--ink); - font-weight: 600; -} -.ip-intro .mail { - color: var(--accent); - font-weight: 500; -} - -/* ==================== style picker modal ==================== */ -.ip-modal-ov { - position: fixed; - inset: 0; - z-index: 60; - display: flex; - align-items: center; - justify-content: center; - padding: 40px; - background: rgba(40, 38, 34, 0); - backdrop-filter: blur(0px); - -webkit-backdrop-filter: blur(0px); - transition: background 0.28s ease, backdrop-filter 0.28s ease, -webkit-backdrop-filter 0.28s ease; -} -.ip-modal-ov.show { - background: rgba(40, 38, 34, 0.34); - backdrop-filter: blur(7px); - -webkit-backdrop-filter: blur(7px); -} -.ip-modal { - position: relative; - width: 1120px; - max-width: 94vw; - max-height: 88vh; - display: flex; - flex-direction: column; - background: var(--paper); - border-radius: 16px; - overflow: hidden; - box-shadow: 0 30px 80px rgba(0, 0, 0, 0.32); - transform: scale(0.92); - opacity: 0; - transition: transform 0.3s cubic-bezier(0.2, 0.82, 0.25, 1), opacity 0.24s ease; -} -.ip-modal-ov.show .ip-modal { - transform: scale(1); - opacity: 1; -} -.ip-modal .frame { - border-radius: 16px; - border-color: var(--ink-soft); - z-index: 0; -} -.ip-modal-hd { - position: relative; - z-index: 1; - display: flex; - align-items: center; - gap: 20px; - padding: 22px 26px; - border-bottom: 1.5px dashed var(--line); -} -.ip-modal-ttl { - display: flex; - flex-direction: column; - font-size: 22px; - font-weight: 600; - color: var(--ink); - white-space: nowrap; -} -.ip-modal-sub { - font-size: 13px; - font-weight: 400; - color: var(--ink-faint); - margin-top: 3px; -} -.ip-modal-search { - position: relative; - margin-left: auto; - width: 320px; - max-width: 46vw; -} -.ip-modal-search input { - width: 100%; - height: 42px; - padding: 0 40px 0 16px; - border-radius: 21px; - border: 1.5px solid var(--line); - background: var(--paper-2); - font: inherit; - font-size: 15px; - color: var(--ink); - outline: none; -} -.ip-modal-search input::placeholder { - color: var(--ink-faint); -} -.ip-modal-search .si { - position: absolute; - right: 15px; - top: 50%; - transform: translateY(-50%); - color: var(--ink-faint); - font-size: 18px; - pointer-events: none; -} -.ip-modal-x { - font-size: 28px; - color: var(--ink-soft); - cursor: pointer; - line-height: 1; - padding: 0 2px; - background: transparent; - border: none; -} -.ip-modal-grid { - position: relative; - z-index: 1; - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 18px; - padding: 24px 26px 28px; - overflow-y: auto; -} -@media (max-width: 780px) { - .ip-modal-grid { grid-template-columns: repeat(2, 1fr); } -} -.ip-scard { - position: relative; - cursor: pointer; -} -.ip-scard .sthumb { - position: relative; - height: 160px; - background: var(--fill); - display: grid; - place-items: center; - border-radius: 11px; - overflow: hidden; -} -.ip-scard .sthumb svg { - width: 32%; - max-width: 84px; - opacity: 0.5; -} -.ip-scard .sthumb::after { - content: ""; - position: absolute; - inset: 0; - border-radius: 11px; - border: 2px solid transparent; -} -.ip-scard.on .sthumb::after { - border-color: var(--accent); -} -.ip-scard .sname { - text-align: center; - padding: 10px 4px 2px; - font-size: 15px; - color: var(--ink); -} -.ip-scard.on .sname { - color: var(--accent); - font-weight: 600; -} -.ip-noresult { - grid-column: 1 / -1; - text-align: center; - color: var(--ink-faint); - padding: 48px 0; - font-size: 15px; -} - -/* ==================== avatar (bottom-left of hero) ==================== */ -.ip-avatar { - position: relative; - width: 46px; - height: 46px; -} -.ip-avatar .frame { - border-radius: 50%; - border-color: var(--ink-soft); -} -.ip-avatar span { - position: absolute; - inset: 0; - display: grid; - place-items: center; - font-family: "Patrick Hand", "Caveat", cursive; - font-size: 22px; - color: var(--ink-soft); +@keyframes yume-ripple { + 0% { + width: 14px; + height: 14px; + opacity: 0.95; + } + 100% { + width: 110px; + height: 110px; + opacity: 0; + } } diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index e7ae148..ecccb0f 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -1,6 +1,24 @@ import type { Metadata } from "next"; +import { Cormorant_Garamond, Inter } from "next/font/google"; import "./globals.css"; +// Editorial 云梦 fonts: drive tailwind `font-serif`/`font-sans` via +// --font-serif / --font-sans across every page (home, /play, /new, CustomForm). +const cormorant = Cormorant_Garamond({ + subsets: ["latin"], + weight: ["300", "400", "500", "600"], + style: ["normal", "italic"], + variable: "--font-serif", + display: "swap", +}); + +const inter = Inter({ + subsets: ["latin"], + weight: ["300", "400", "500"], + variable: "--font-sans", + display: "swap", +}); + export const metadata: Metadata = { title: "InfiPlot — AI 实时交互剧情游戏", description: "InfiPlot 是一款用 AI 实时生成图片、语音与剧情分支的交互式剧情游戏 Demo。", @@ -12,31 +30,19 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( - +
- - + {/* Font Awesome — fa-solid icons used by home, /play, /new, CustomForm. */} - - {/* Hand-drawn jitter filters used by every .frame element */} - + {children} diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index 3b96ae4..fde43ab 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -1,18 +1,19 @@ "use client"; import { useRouter } from "next/navigation"; -import { useEffect, useLayoutEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; /* ============================================================================ - InfiPlot · 低保真原型首页 - - 1900px 设计画布 + 等比缩放至视口宽度,最大程度还原原型版式 - - 顶部 Hero 浮动散落卡片;下方瀑布流;尾部项目介绍 + InfiPlot · 首页(云梦编辑式视觉风格 · 居中构图,呼应低保真原型) + - 顶部 Header:左上角衬线 wordmark logo + - Hero 控制区(居中):标题 / prompt 输入框 + 开始 / 5 个类别选择器 + - 统一瀑布流(居中定宽):7 张主推 + 16 张画廊,按性向整体 crossfade 切换 + - 项目介绍(题跋式排版) ========================================================================== */ -const HERO_CANVAS_W = 1900; -const HERO_CANVAS_H = 980; +type Gender = "男性向" | "女性向"; -const EXAMPLE_PHRASES: Record<"男性向" | "女性向", string[]> = { +const EXAMPLE_PHRASES: Record{outline}
-