Merge branch 'visual-direction-assets'
# Conflicts: # src/App.css # src/ui/StoryPlayer.tsx
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
# 视觉方向与影像来源
|
||||
|
||||
## 当前界面
|
||||
|
||||
剧情采用“考古档案影像”作为视觉语言:深墨绿背景、青铜绿的档案标注、朱红的主操作,宋体标题与清晰的正文字体。图片铺满场景,文本区保持不透明度与对比度,供长篇阅读和选择操作。手机上内容自然滚动,不截断长文本。
|
||||
|
||||
当前四张照片均为**殷墟相关历史参考影像**,不是虚构“苍梧”遗址、人物或剧情现场照片。画面内标注“影像参考 / 殷墟考古资料”。这是可用的临时影像方案,不应作为正式章节插画向用户宣称。正式美术需求见 [插图生成清单](illustration-prompts.md),其 57 张插画尚未生成;后续应按剧情节点及分支逐张替换,不可用同一张史料图误导性地代表所有分支。
|
||||
|
||||
## 已加载的图片
|
||||
|
||||
图片置于 `public/images/`,均取自 Wikimedia Commons 的 CC0 文件页面;下载的是缩放至 1280px 宽的 JPEG,应用从本地路径加载,不依赖运行时第三方请求。
|
||||
|
||||
| 本地路径 | 原始文件页面 | 用途 |
|
||||
|---|---|---|
|
||||
| `ruins.jpg` | [Yinxu Palace Foundation](https://commons.wikimedia.org/wiki/File:Yinxu_Palace_Foundation_(45144971564).jpg) | 第一章背景 |
|
||||
| `excavation.jpg` | [Sacrificial Pits of the Palace-Temple Complex of Yinxu](https://commons.wikimedia.org/wiki/File:Sacrificial_Pits_of_the_Palace-Temple_Complex_of_Yinxu_(45144967314).jpg) | 第二章背景 |
|
||||
| `bronze.jpg` | [Shang Bronze Gui, Yinxu](https://commons.wikimedia.org/wiki/File:Shang_Bronze_Gui,_Yinxu.jpg) | 过场与特定剧情节点 |
|
||||
| `inscription.jpg` | [Ancient Chinese Writing on Ox Scapula](https://commons.wikimedia.org/wiki/File:Ancient_Chinese_Writing_on_Ox_Scapula,_Shang_Dynasty_Oracle_Bone,_Yinxu,_Anyang.jpg) | 第三章与特定剧情节点 |
|
||||
|
||||
正式发布前应再次核对各来源页面的授权与署名元数据,并让 UI 的图片来源说明可直接访问。现阶段 UI 使用概括性说明,此表提供精确来源。
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 221 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 181 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 411 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 230 KiB |
+94
-404
@@ -1,406 +1,96 @@
|
||||
.app {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.app { min-height: 100svh; display: flex; flex-direction: column; }
|
||||
.scene { position: relative; isolation: isolate; min-height: 100svh; display: flex; flex-direction: column; overflow: hidden; background: #17201d; }
|
||||
.scene-media, .scene-shade { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
|
||||
.scene-media { object-fit: cover; object-position: center; z-index: -2; filter: saturate(.75) contrast(1.06); }
|
||||
.scene-shade { z-index: -1; background: linear-gradient(90deg, rgba(11,22,20,.72), rgba(11,22,20,.14) 80%), linear-gradient(0deg, #101917 0%, rgba(16,25,23,.9) 20%, rgba(16,25,23,.08) 72%); }
|
||||
.scene-content { width: min(100% - 64px, 1100px); min-height: 100svh; margin-inline: auto; display: flex; flex-direction: column; padding-bottom: clamp(28px,5vh,64px); }
|
||||
.scene-header { display: flex; align-items: center; justify-content: space-between; gap: 24px; border-bottom: 1px solid rgba(233,231,223,.34); padding-block: 20px; font-size: 13px; color: #e8e8dd; }
|
||||
.scene-brand { font-family: 'Noto Serif CJK SC','Songti SC',serif; font-size: 18px; white-space: nowrap; }
|
||||
.scene-brand small { display: inline-block; margin-left: 16px; font: 11px/1.5 'Noto Sans CJK SC','PingFang SC',sans-serif; color: #b7c8bb; }
|
||||
.scene-chapter { color: #d6d3c8; text-align: right; }
|
||||
.scene-stage { margin-top: auto; margin-bottom: 32px; }
|
||||
.scene-index, .scene-credit { font-size: 12px; color: #b9d2c6; }
|
||||
.scene-index { letter-spacing: 1px; }
|
||||
.scene-stage h1 { margin: 6px 0 10px; font-size: clamp(36px,5vw,68px); line-height: 1.2; color: #f5f0e4; text-shadow: 0 2px 24px rgba(0,0,0,.38); }
|
||||
.scene-credit { display: block; margin-top: 8px; }
|
||||
.dialogue-box, .choice-box, .narration-box, .identity-box, .cutscene, .chapter-end { width: min(100%,760px); padding: 24px 28px; border: 1px solid rgba(201,219,203,.34); border-left: 3px solid #a8bda5; border-radius: 3px; background: rgba(13,25,23,.94); box-shadow: 0 16px 40px rgba(0,0,0,.2); backdrop-filter: blur(12px); }
|
||||
.dialogue-box { display: flex; align-items: flex-start; gap: 18px; text-align: left; color: inherit; }
|
||||
.dialogue-box:hover { border-color: #d9bc94; }
|
||||
.speaker-mark { flex: 0 0 46px; width: 46px; height: 46px; display: grid; place-items: center; border: 1px solid #8ca696; border-radius: 50%; font-family: 'Noto Serif CJK SC','Songti SC',serif; color: #d2d7c8; }
|
||||
.dialogue-body { display: block; min-width: 0; flex: 1; }
|
||||
.speaker-name { display: block; margin-bottom: 8px; color: #d8b58a; font-weight: 600; }
|
||||
.dialogue-text { display: block; line-height: 1.9; white-space: pre-line; }
|
||||
.continue-hint { display: block; margin-top: 16px; color: #adc5b4; font-size: 12px; text-align: right; }
|
||||
.choice-prompt, .narration-prompt, .cutscene p { color: #f2f0e7; font-size: 18px; line-height: 1.75; }
|
||||
.choice-options { display: grid; gap: 9px; margin-top: 18px; }
|
||||
.choice-btn { width: 100%; padding: 14px 18px; border: 1px solid #546d60; border-radius: 3px; background: rgba(34,54,47,.7); color: #f1eee4; text-align: left; transition: background .2s,border-color .2s; }
|
||||
.choice-btn:hover { background: #344d40; border-color: #d8b58a; }
|
||||
.mechanism-tag { display: inline-block; margin-bottom: 12px; padding: 3px 8px; border: 1px solid #799b82; color: #c2d8c5; font-size: 12px; }
|
||||
.hint { color: #aabbb0; font-size: 12px; line-height: 1.6; }
|
||||
button.primary, button.secondary, .hold-btn { min-height: 44px; padding: 10px 20px; border-radius: 3px; font-size: 15px; transition: background .2s,border-color .2s; }
|
||||
button.primary { border: 1px solid #c36e55; background: #a94732; color: #fff9ed; font-weight: 600; }
|
||||
button.primary:hover { background: #c05d43; }
|
||||
button.secondary { border: 1px solid #829989; background: transparent; color: #e6eae0; }
|
||||
button.secondary:hover { border-color: #d6b48b; background: rgba(168,189,165,.12); }
|
||||
.cutscene .primary, .chapter-end .primary { margin-top: 12px; }
|
||||
.voice-input { margin-top: 16px; }
|
||||
.voice-controls { max-width: 280px; }
|
||||
.hold-btn { width: 100%; border: 1px solid #91ae99; background: #294638; color: #edf1e7; touch-action: none; }
|
||||
.hold-btn.holding { background: #a94732; border-color: #c36e55; }
|
||||
.voice-live { padding: 10px 14px; border-left: 2px solid #b2c6ac; color: #d8e3d8; background: rgba(74,106,85,.23); }
|
||||
.voice-textarea, .identity-field input { width: 100%; padding: 12px 14px; border: 1px solid #718f7c; border-radius: 3px; background: #172b25; color: #f0f0e7; line-height: 1.6; }
|
||||
.voice-textarea { display: block; margin-bottom: 12px; resize: vertical; }
|
||||
.voice-textarea::placeholder, .identity-field input::placeholder { color: #9eb1a3; }
|
||||
.voice-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
||||
.voice-actions > button:not(.primary) { border: 0; background: transparent; color: #c5d2c2; text-decoration: underline; }
|
||||
.identity-field { display: grid; grid-template-columns: 80px minmax(0,1fr); align-items: center; gap: 12px; margin: 10px 0; }
|
||||
.identity-box .primary, .narration-box .secondary { margin-top: 12px; }
|
||||
.consent-box { width: min(100% - 40px,700px); margin: auto; padding: clamp(24px,5vw,56px); border-top: 2px solid #9bb69b; background: #1b2a24; box-shadow: 0 16px 60px rgba(0,0,0,.25); }
|
||||
.app:has(.consent-box) { padding-block: 32px; background: radial-gradient(ellipse at 80% 0%,#335444,#101917 65%); }
|
||||
.consent-box h2 { font-size: clamp(24px,3vw,36px); margin: 8px 0 20px; }
|
||||
.consent-list { padding-left: 22px; line-height: 1.8; color: #d7e0d6; }
|
||||
.consent-list li { margin-bottom: 10px; }
|
||||
.consent-note { color: #d7e0d6; }
|
||||
.consent-box .primary { margin-right: 12px; }
|
||||
.consent-box .hint { margin-top: 20px; }
|
||||
.report { width: min(100% - 40px,880px); margin-inline: auto; padding: clamp(32px,5vw,72px) 0; }
|
||||
.report h1 { font-size: clamp(27px,4vw,42px); color: #f1eee3; }
|
||||
.badge-banner { display: flex; align-items: center; gap: 24px; margin: 28px 0 36px; padding: 22px 0; border-block: 1px solid #5a7762; }
|
||||
.badge-banner h2 { color: #e7c297; margin-bottom: 8px; }
|
||||
.badge-banner p { margin-bottom: 6px; }
|
||||
.badge-medal { flex: 0 0 70px; height: 70px; display: grid; place-items: center; border: 2px solid #bd9570; border-radius: 50%; color: #eed7af; font: 34px 'Noto Serif CJK SC','Songti SC',serif; }
|
||||
.badge-note { color: #a9baad; font-size: 13px; }
|
||||
.dimension-list { display: grid; gap: 18px; margin-bottom: 42px; }
|
||||
.dimension-row { display: grid; grid-template-columns: 140px minmax(60px,1fr) 185px; align-items: center; gap: 14px; }
|
||||
.dimension-label { color: #e6ede1; }
|
||||
.dimension-bar { height: 8px; background: #35463b; }
|
||||
.dimension-fill { height: 100%; background: #a8c1a3; }
|
||||
.dimension-value { color: #bdcfc0; font-size: 13px; text-align: right; }
|
||||
.share-card { text-align: center; margin-bottom: 28px; }
|
||||
.share-canvas { display: block; width: min(100%,320px); height: auto; margin: 0 auto 16px; border: 1px solid #698573; }
|
||||
.report > .secondary { display: block; margin: auto; }
|
||||
@media (max-width: 640px) {
|
||||
.scene-content { width: min(100% - 32px,480px); padding-bottom: 20px; }
|
||||
.scene-header { padding-block: 14px; align-items: flex-start; }
|
||||
.scene-brand { font-size: 15px; }
|
||||
.scene-brand small { display: block; margin-left: 0; margin-top: 3px; font-size: 10px; }
|
||||
.scene-chapter { font-size: 11px; }
|
||||
.scene-stage { margin-bottom: 20px; }
|
||||
.scene-stage h1 { font-size: 38px; }
|
||||
.dialogue-box, .choice-box, .narration-box, .identity-box, .cutscene, .chapter-end { padding: 18px; }
|
||||
.speaker-mark { flex-basis: 36px; width: 36px; height: 36px; }
|
||||
.dialogue-box { gap: 12px; }
|
||||
.choice-prompt, .narration-prompt { font-size: 16px; }
|
||||
.dimension-row { grid-template-columns: minmax(0,1fr) auto; gap: 4px 10px; }
|
||||
.dimension-bar { grid-column: 1 / -1; grid-row: 2; }
|
||||
.dimension-value { font-size: 12px; }
|
||||
}
|
||||
@media (max-height: 700px) { .scene-stage { margin-top: 48px; } }
|
||||
@media (prefers-reduced-motion: reduce) { .choice-btn, button.primary, button.secondary, .hold-btn { transition: none; } }
|
||||
|
||||
/* ---- 影游场景 ---- */
|
||||
.scene {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
padding: 24px;
|
||||
background: linear-gradient(160deg, #14101e 30%, #2a2338 100%);
|
||||
border-radius: 12px;
|
||||
min-height: 80vh;
|
||||
transition: background 0.8s ease;
|
||||
}
|
||||
|
||||
.scene-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 12px 20px;
|
||||
font-size: 13px;
|
||||
color: #b0a490;
|
||||
background: rgba(20, 16, 30, 0.8);
|
||||
}
|
||||
|
||||
.variant-tag {
|
||||
color: #d4a96a;
|
||||
}
|
||||
|
||||
.dialogue-box {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
background: rgba(20, 16, 30, 0.85);
|
||||
border: 1px solid #3a3248;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sprite {
|
||||
flex-shrink: 0;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28px;
|
||||
color: #f0e6d2;
|
||||
}
|
||||
|
||||
.speaker-name {
|
||||
font-weight: 700;
|
||||
color: #d4a96a;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.dialogue-text {
|
||||
line-height: 1.8;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.hint {
|
||||
font-size: 12px;
|
||||
color: #8a7f6a;
|
||||
}
|
||||
|
||||
/* ---- 选择点 ---- */
|
||||
.choice-box,
|
||||
.narration-box {
|
||||
background: rgba(20, 16, 30, 0.85);
|
||||
border: 1px solid #3a3248;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.choice-prompt,
|
||||
.narration-prompt {
|
||||
font-size: 17px;
|
||||
line-height: 1.7;
|
||||
color: #f0e6d2;
|
||||
}
|
||||
|
||||
.choice-options {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.choice-btn {
|
||||
overflow-wrap: anywhere;
|
||||
text-align: left;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #4a3f5a;
|
||||
background: #241e33;
|
||||
color: #e8dcc8;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s, background 0.2s;
|
||||
}
|
||||
|
||||
.choice-btn:hover {
|
||||
border-color: #d4a96a;
|
||||
background: #2e2642;
|
||||
}
|
||||
|
||||
/* ---- 讲述点 ---- */
|
||||
.choice-box fieldset {
|
||||
min-width: 0;
|
||||
border: 0;
|
||||
padding: 12px 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.puzzle-option {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
padding: 8px 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.choice-box progress {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
accent-color: #d4a96a;
|
||||
}
|
||||
|
||||
button:disabled { opacity: 0.6; cursor: default; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.scene { padding: 64px 12px 16px; }
|
||||
.dimension-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
|
||||
.dimension-value { grid-column: 1 / -1; }
|
||||
.dialogue-box > div:last-child { min-width: 0; overflow-wrap: anywhere; }
|
||||
}
|
||||
|
||||
.mechanism-tag {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
color: #1a1626;
|
||||
background: #d4a96a;
|
||||
border-radius: 4px;
|
||||
padding: 2px 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.hold-btn {
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
padding: 18px;
|
||||
border-radius: 999px;
|
||||
border: 2px solid #d4a96a;
|
||||
background: transparent;
|
||||
color: #d4a96a;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.hold-btn.holding {
|
||||
background: #d4a96a;
|
||||
color: #1a1626;
|
||||
}
|
||||
|
||||
/* ---- 过场 ---- */
|
||||
.cutscene {
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cutscene-placeholder {
|
||||
border: 2px dashed #4a3f5a;
|
||||
border-radius: 12px;
|
||||
padding: 80px 20px;
|
||||
font-size: 20px;
|
||||
color: #8a7f6a;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
/* ---- 通用按钮 ---- */
|
||||
button.primary,
|
||||
button.secondary {
|
||||
margin-top: 16px;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button.primary {
|
||||
border: none;
|
||||
background: #d4a96a;
|
||||
color: #1a1626;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
button.secondary {
|
||||
border: 1px solid #4a3f5a;
|
||||
background: transparent;
|
||||
color: #b0a490;
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.chapter-end {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
/* ---- 画像报告 ---- */
|
||||
.report {
|
||||
padding: 32px 24px;
|
||||
}
|
||||
|
||||
.report h1 {
|
||||
color: #d4a96a;
|
||||
}
|
||||
|
||||
.badge-banner {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
background: rgba(212, 169, 106, 0.08);
|
||||
border: 1px solid #d4a96a;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin: 16px 0 24px;
|
||||
}
|
||||
|
||||
.badge-medal {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 50%;
|
||||
background: #d4a96a;
|
||||
color: #1a1626;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.badge-note {
|
||||
font-size: 13px;
|
||||
color: #8a7f6a;
|
||||
}
|
||||
|
||||
.dimension-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.dimension-row {
|
||||
display: grid;
|
||||
grid-template-columns: 140px 1fr 190px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.dimension-label {
|
||||
color: #e8dcc8;
|
||||
}
|
||||
|
||||
.dimension-bar {
|
||||
height: 14px;
|
||||
background: #2a2338;
|
||||
border-radius: 7px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dimension-fill {
|
||||
height: 100%;
|
||||
background: #d4a96a;
|
||||
}
|
||||
|
||||
.dimension-value {
|
||||
font-size: 13px;
|
||||
color: #b0a490;
|
||||
}
|
||||
|
||||
/* ---- 分享卡 ---- */
|
||||
.share-card {
|
||||
text-align: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.share-canvas {
|
||||
max-width: 320px;
|
||||
width: 100%;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #3a3248;
|
||||
display: block;
|
||||
margin: 0 auto 12px;
|
||||
}
|
||||
|
||||
/* ---- 语音输入与身份登记 ---- */
|
||||
.voice-input {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.voice-live {
|
||||
margin-top: 10px;
|
||||
padding: 10px 14px;
|
||||
background: rgba(212, 169, 106, 0.1);
|
||||
border-left: 3px solid #d4a96a;
|
||||
border-radius: 4px;
|
||||
color: #e8dcc8;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.voice-textarea {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 12px 14px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #4a3f5a;
|
||||
background: #241e33;
|
||||
color: #e8dcc8;
|
||||
font-size: 15px;
|
||||
line-height: 1.7;
|
||||
resize: vertical;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.identity-box {
|
||||
background: rgba(20, 16, 30, 0.85);
|
||||
border: 1px solid #3a3248;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.identity-field {
|
||||
display: grid;
|
||||
grid-template-columns: 80px 1fr;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin: 10px 0;
|
||||
color: #d8ccb8;
|
||||
}
|
||||
|
||||
.identity-field input {
|
||||
padding: 10px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #4a3f5a;
|
||||
background: #241e33;
|
||||
color: #e8dcc8;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/* ---- 同意门(ADR-0004) ---- */
|
||||
.consent-box {
|
||||
background: rgba(20, 16, 30, 0.85);
|
||||
border: 1px solid #3a3248;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
max-width: 560px;
|
||||
margin: 48px auto;
|
||||
}
|
||||
|
||||
.consent-box h2 {
|
||||
color: #f0e6d2;
|
||||
font-size: 20px;
|
||||
margin: 8px 0 16px;
|
||||
}
|
||||
|
||||
.consent-list {
|
||||
color: #d8ccb8;
|
||||
font-size: 15px;
|
||||
line-height: 1.8;
|
||||
padding-left: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.consent-list li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.consent-note {
|
||||
color: #d8ccb8;
|
||||
font-size: 15px;
|
||||
line-height: 1.7;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.consent-box .primary,
|
||||
.consent-box .secondary {
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
/* ---- 合并补回(视觉版缺失、main 独有功能的样式) ---- */
|
||||
.puzzle-option { display: flex; align-items: center; gap: 8px; padding: 8px 12px; border: 1px solid #3d5548; background: rgba(23,36,29,.85); font-size: 15px; cursor: pointer; }
|
||||
.puzzle-option:hover { border-color: #698573; }
|
||||
.puzzle-option input { accent-color: #91ae99; }
|
||||
.variant-tag { display: inline-block; margin-left: 10px; padding: 2px 8px; border: 1px solid #3d5548; font-size: 11px; color: #b9c9bd; vertical-align: middle; }
|
||||
.scene-error { color: #d98a7a; font-size: 14px; margin-top: 12px; }
|
||||
|
||||
+14
-24
@@ -1,31 +1,21 @@
|
||||
:root {
|
||||
font: 16px/1.6 system-ui, 'PingFang SC', 'Segoe UI', sans-serif;
|
||||
color: #d8ccb8;
|
||||
background: #14101e;
|
||||
font: 16px/1.6 'Noto Sans CJK SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||
color: #e9e7df;
|
||||
background: #111a19;
|
||||
color-scheme: dark;
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#root {
|
||||
min-height: 100svh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 20px;
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 6px;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; min-width: 320px; }
|
||||
#root { min-height: 100svh; }
|
||||
button, input, textarea { font: inherit; }
|
||||
button { cursor: pointer; }
|
||||
button:disabled { opacity: .48; cursor: not-allowed; }
|
||||
:focus-visible { outline: 2px solid #dfad78; outline-offset: 3px; }
|
||||
h1, h2, p { margin-top: 0; }
|
||||
h1, h2 { font-family: 'Noto Serif CJK SC', 'Songti SC', 'STSong', serif; font-weight: 500; }
|
||||
p { margin-bottom: .65em; }
|
||||
::selection { background: #a44735; color: #fff; }
|
||||
|
||||
+47
-37
@@ -14,14 +14,10 @@ const FOLLOW_UP_LABELS: Record<FollowUpDimension, string> = {
|
||||
emotion: '情绪',
|
||||
};
|
||||
|
||||
/** speaker/sprite → 立绘占位色(无图片素材,CSS 色块) */
|
||||
const SPRITE_COLORS: Record<string, string> = {
|
||||
curator: '#6a5a8a',
|
||||
shenduo: '#4a6a8a',
|
||||
atang: '#c46a5a',
|
||||
wenjian: '#7a9a8a',
|
||||
laogui: '#8a7a4a',
|
||||
scene: '#3a3a4a',
|
||||
const CHAPTER_IMAGES: Record<string, string> = {
|
||||
chapter1: '/images/ruins.jpg',
|
||||
chapter2: '/images/excavation.jpg',
|
||||
chapter3: '/images/inscription.jpg',
|
||||
};
|
||||
|
||||
interface Props {
|
||||
@@ -29,7 +25,7 @@ interface Props {
|
||||
onFinish: () => void;
|
||||
}
|
||||
|
||||
/** 最简影游界面:对话框 / 选项按钮 / 讲述点语音输入 / 身份登记 / 场景变体渐变 */
|
||||
/** 对话 / 选择 / 讲述 / 身份登记与章节过场 */
|
||||
export function StoryPlayer({ engine, onFinish }: Props) {
|
||||
// tick 只是触发重渲染;引擎是唯一事实源
|
||||
const [tick, setTick] = useState(0);
|
||||
@@ -41,6 +37,13 @@ export function StoryPlayer({ engine, onFinish }: Props) {
|
||||
const node = engine.currentNode();
|
||||
const chapter = engine.currentChapter();
|
||||
const variant = engine.state.activeVariant;
|
||||
const image =
|
||||
node.type === 'cutscene' || (chapter.id === 'chapter2' && node.id === 'c2-05')
|
||||
? '/images/bronze.jpg'
|
||||
: node.id === 'c1-05'
|
||||
? '/images/inscription.jpg'
|
||||
: CHAPTER_IMAGES[chapter.id];
|
||||
|
||||
async function advance() {
|
||||
if (advancing) return;
|
||||
setAdvancing(true);
|
||||
@@ -49,34 +52,40 @@ export function StoryPlayer({ engine, onFinish }: Props) {
|
||||
await engine.advance();
|
||||
if (engine.state.finished) onFinish();
|
||||
else rerender();
|
||||
} catch (e) { setError(String(e)); } finally { setAdvancing(false); }
|
||||
} catch (e) {
|
||||
setError(String(e));
|
||||
} finally {
|
||||
setAdvancing(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="scene"
|
||||
style={
|
||||
variant
|
||||
? { background: `linear-gradient(160deg, #1a1626 30%, ${variant.tint} 130%)` }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<header className="scene-header">
|
||||
<span>{chapter.title}</span>
|
||||
{variant && <span className="variant-tag">场景变体:{variant.label}</span>}
|
||||
</header>
|
||||
<main className="scene">
|
||||
<img className="scene-media" src={image} alt="" aria-hidden="true" />
|
||||
<div className="scene-shade" />
|
||||
<div className="scene-content">
|
||||
<header className="scene-header">
|
||||
<span className="scene-brand">苍梧疑冢 <small>时空档案馆 / ACT {chapter.act}</small></span>
|
||||
<span className="scene-chapter">
|
||||
{chapter.title}
|
||||
{variant && <span className="variant-tag">{variant.label}</span>}
|
||||
</span>
|
||||
</header>
|
||||
<div className="scene-stage">
|
||||
<span className="scene-index">ARCHIVE / 0{chapter.act}</span>
|
||||
<h1>{chapter.title}</h1>
|
||||
<span className="scene-credit">影像参考 / 殷墟考古资料</span>
|
||||
</div>
|
||||
|
||||
{node.type === 'dialogue' && (
|
||||
<div className="dialogue-box" onClick={advance}>
|
||||
<div className="sprite" style={{ background: SPRITE_COLORS[node.sprite ?? 'scene'] }}>
|
||||
{node.speaker.slice(0, 1)}
|
||||
</div>
|
||||
<div>
|
||||
<div className="speaker-name">{node.speaker}</div>
|
||||
<p className="dialogue-text">{engine.renderText(node.text)}</p>
|
||||
<span className="hint">点击继续 ▸</span>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" className="dialogue-box" disabled={advancing} onClick={advance}>
|
||||
<span className="speaker-mark" aria-hidden="true">{node.speaker.slice(0, 1)}</span>
|
||||
<span className="dialogue-body">
|
||||
<span className="speaker-name">{node.speaker}</span>
|
||||
<span className="dialogue-text">{engine.renderText(node.text)}</span>
|
||||
<span className="continue-hint">继续 <span aria-hidden="true">→</span></span>
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{node.type === 'choice' && (
|
||||
@@ -108,10 +117,10 @@ export function StoryPlayer({ engine, onFinish }: Props) {
|
||||
{node.type === 'identity' && <IdentityView node={node} engine={engine} onDone={rerender} />}
|
||||
|
||||
{node.type === 'cutscene' && (
|
||||
<div className="cutscene" onClick={advance}>
|
||||
<div className="cutscene-placeholder">▶ {node.video}</div>
|
||||
<div className="cutscene">
|
||||
<span className="mechanism-tag">场景记录 / {chapter.act}</span>
|
||||
<p>{engine.renderText(node.caption)}</p>
|
||||
<span className="hint">点击继续 ▸</span>
|
||||
<button className="primary" disabled={advancing} onClick={advance}>继续 →</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -128,8 +137,9 @@ export function StoryPlayer({ engine, onFinish }: Props) {
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{error && <p role="alert">{error}</p>}
|
||||
</div>
|
||||
{error && <p className="scene-error" role="alert">{error}</p>}
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+16
-17
@@ -29,33 +29,32 @@ export function ShareCard({ badgeDimension, scores, playerName }: Props) {
|
||||
const badge = BADGE_MAP[badgeDimension] ?? { title: '档案采集员', flavor: '' };
|
||||
const dim = PROFILE_DIMENSIONS.find((d) => d.id === badgeDimension);
|
||||
|
||||
// 背景渐变(美术占位)
|
||||
const bg = ctx.createLinearGradient(0, 0, W, H);
|
||||
bg.addColorStop(0, '#1a1626');
|
||||
bg.addColorStop(1, '#3a2a4a');
|
||||
ctx.fillStyle = bg;
|
||||
ctx.fillStyle = '#14231d';
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
ctx.strokeStyle = '#8ba58e';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeRect(32, 32, W - 64, H - 64);
|
||||
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillStyle = '#c9b48a';
|
||||
ctx.fillStyle = '#d0dfcd';
|
||||
ctx.font = '28px serif';
|
||||
ctx.fillText('苍梧疑冢 · 人格画像', W / 2, 80);
|
||||
ctx.font = '22px serif';
|
||||
ctx.fillText(`C-2049${playerName ? `「${playerName}」` : ''}的档案`, W / 2, 118);
|
||||
|
||||
// 勋章圆
|
||||
ctx.beginPath();
|
||||
ctx.arc(W / 2, 220, 70, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#d4a96a';
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#1a1626';
|
||||
ctx.strokeStyle = '#bfa47c';
|
||||
ctx.lineWidth = 3;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#eed6aa';
|
||||
ctx.font = 'bold 44px serif';
|
||||
ctx.fillText('勋', W / 2, 236);
|
||||
|
||||
ctx.fillStyle = '#f0e6d2';
|
||||
ctx.fillStyle = '#f1eee4';
|
||||
ctx.font = 'bold 40px serif';
|
||||
ctx.fillText(badge.title, W / 2, 360);
|
||||
ctx.fillStyle = '#b0a490';
|
||||
ctx.fillStyle = '#b3c6b4';
|
||||
ctx.font = '22px serif';
|
||||
ctx.fillText(badge.flavor, W / 2, 400);
|
||||
if (dim) {
|
||||
@@ -67,19 +66,19 @@ export function ShareCard({ badgeDimension, scores, playerName }: Props) {
|
||||
ctx.textAlign = 'left';
|
||||
scores.forEach((s, i) => {
|
||||
const y = 510 + i * 56;
|
||||
ctx.fillStyle = '#d8ccb8';
|
||||
ctx.fillStyle = '#dce7d9';
|
||||
ctx.font = '22px serif';
|
||||
ctx.fillText(s.label, 80, y);
|
||||
ctx.fillStyle = '#4a3f5a';
|
||||
ctx.fillStyle = '#385044';
|
||||
ctx.fillRect(260, y - 16, 240, 18);
|
||||
ctx.fillStyle = '#d4a96a';
|
||||
ctx.fillStyle = '#a8c1a3';
|
||||
ctx.fillRect(260, y - 16, (240 * s.score) / s.max, 18);
|
||||
ctx.fillStyle = '#d8ccb8';
|
||||
ctx.fillStyle = '#dce7d9';
|
||||
ctx.fillText(s.score.toFixed(1), 520, y);
|
||||
});
|
||||
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillStyle = '#8a7f6a';
|
||||
ctx.fillStyle = '#acc0af';
|
||||
ctx.font = '18px serif';
|
||||
ctx.fillText('每个选择都是实测,没有自报。', W / 2, H - 40);
|
||||
return canvas;
|
||||
|
||||
Reference in New Issue
Block a user