-

+
{contact.name}
diff --git a/src/mobile/ordinaryNav.ts b/src/mobile/ordinaryNav.ts
new file mode 100644
index 0000000..27839c0
--- /dev/null
+++ b/src/mobile/ordinaryNav.ts
@@ -0,0 +1,62 @@
+import { allDeepPages } from '../content/nav'
+
+export type OrdinaryPanelId = 'm-home' | 'm-culture' | 'm-qingxiang' | 'm-haoke' | 'm-contact'
+
+export type OrdinarySubnavItem = {
+ label: string
+ anchor?: string
+}
+
+export type OrdinaryNavMenu = {
+ id: OrdinaryPanelId
+ children: OrdinarySubnavItem[]
+}
+
+const cultureDeep = allDeepPages.filter((p) => p.group === 'culture')
+const poetryDeep = allDeepPages.filter((p) => p.group === 'poetry')
+const qingxiangDeep = allDeepPages.filter((p) => p.group === 'qingxiang')
+
+export function pathToAnchorId(path: string) {
+ return `m${path.replace(/\//g, '-')}`
+}
+
+export const ordinarySubnav: OrdinaryNavMenu[] = [
+ { id: 'm-home', children: [] },
+ {
+ id: 'm-culture',
+ children: [
+ { label: '品牌传承', anchor: 'm-brand' },
+ ...cultureDeep.map((p) => ({ label: p.navLabel, anchor: pathToAnchorId(p.path) })),
+ { label: '诗酒文化', anchor: 'm-poetry' },
+ ...poetryDeep.map((p) => ({ label: p.navLabel, anchor: pathToAnchorId(p.path) })),
+ ],
+ },
+ {
+ id: 'm-qingxiang',
+ children: [
+ { label: '清香', anchor: 'm-qingxiang-main' },
+ ...qingxiangDeep.map((p) => ({ label: p.navLabel, anchor: pathToAnchorId(p.path) })),
+ ],
+ },
+ {
+ id: 'm-haoke',
+ children: [
+ { label: '好客模式', anchor: 'm-haoke-main' },
+ { label: '三端协同', anchor: 'm-system' },
+ { label: '科技驱动', anchor: 'm-tech' },
+ { label: '酒厂实力', anchor: 'm-subsidy' },
+ { label: '科技板块', anchor: 'm-tech-detail' },
+ ],
+ },
+ {
+ id: 'm-contact',
+ children: [
+ { label: '城市合伙人', anchor: 'm-partner' },
+ { label: '联系我们', anchor: 'm-contact-main' },
+ ],
+ },
+]
+
+export function getSubnavForPanel(id: OrdinaryPanelId) {
+ return ordinarySubnav.find((m) => m.id === id)?.children ?? []
+}
diff --git a/src/pages/Contact.tsx b/src/pages/Contact.tsx
index 872161c..487f98d 100644
--- a/src/pages/Contact.tsx
+++ b/src/pages/Contact.tsx
@@ -22,7 +22,7 @@ export function ContactPage() {
-
+
{contact.wechatHint}
diff --git a/src/stage/StageShell.css b/src/stage/StageShell.css
index c47a2fd..46777ee 100644
--- a/src/stage/StageShell.css
+++ b/src/stage/StageShell.css
@@ -1,5 +1,10 @@
/* ===== Stage Shell Ambient Theme & HUD ===== */
.stage-shell {
+ --vw: 1vw;
+ --vh: 1vh;
+ --stage-content-max: 76vw;
+ --brand: #a61c20;
+ --gold: #c4a35a;
position: relative;
width: 100vw;
height: 100vh;
@@ -15,6 +20,30 @@
user-select: none;
}
+[data-theme='nostalgic'] .stage-shell {
+ --stage-brand: #a61c20;
+ --stage-gold: #9f762f;
+ --stage-gold-light: #c4a35a;
+ --stage-glass-bg: rgba(255, 252, 246, 0.82);
+ --stage-glass-border: rgba(166, 28, 32, 0.16);
+ --stage-glow: rgba(166, 28, 32, 0.1);
+ color: #251b16;
+ background:
+ radial-gradient(circle at 15% 20%, rgba(166, 28, 32, 0.08), transparent 38%),
+ linear-gradient(180deg, #fbf7ef 0%, #eee4d5 100%);
+}
+
+[data-theme='nostalgic'] .stage-shell::before {
+ background-image:
+ linear-gradient(rgba(123, 83, 45, 0.035) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(123, 83, 45, 0.035) 1px, transparent 1px);
+}
+
+[data-theme='nostalgic'] .stage-hud-top {
+ background: linear-gradient(180deg, rgba(247, 242, 234, 0.98), rgba(247, 242, 234, 0.72));
+ border-bottom-color: rgba(166, 28, 32, 0.13);
+}
+
/* Subtle grid overlay for tech/exhibition feel */
.stage-shell::before {
content: '';
@@ -23,7 +52,7 @@
background-image:
linear-gradient(rgba(196, 163, 90, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(196, 163, 90, 0.03) 1px, transparent 1px);
- background-size: 4rem 4rem;
+ background-size: 3.333vw 3.333vw;
pointer-events: none;
z-index: 0;
}
@@ -34,100 +63,100 @@
top: 0;
left: 0;
right: 0;
- height: calc(4.5rem + env(safe-area-inset-top, 0px));
+ height: calc(6.667vh + env(safe-area-inset-top, 0px));
padding-top: env(safe-area-inset-top, 0px);
- padding-left: 2.5rem;
- padding-right: 2.5rem;
+ padding-left: 2.083vw;
+ padding-right: 2.083vw;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: nowrap;
- gap: 0.75rem;
- overflow: hidden;
+ gap: 0.625vw;
z-index: 50;
background: linear-gradient(180deg, rgba(14, 12, 16, 0.95) 0%, rgba(14, 12, 16, 0) 100%);
border-bottom: 1px solid rgba(196, 163, 90, 0.15);
- backdrop-filter: blur(8px);
+ backdrop-filter: blur(0.417vw);
}
.stage-brand {
display: flex;
align-items: center;
- gap: 0.85rem;
- text-decoration: none;
- flex-shrink: 1;
+ gap: 0.708vw;
+ flex-shrink: 0;
min-width: 0;
white-space: nowrap;
}
.stage-logo,
.stage-logo-prog {
- width: 2.4rem;
- height: 2.4rem;
+ height: min(3.2vw, 5.2vh);
+ width: auto;
+ max-width: min(12vw, 18vh);
flex-shrink: 0;
}
.stage-logo-prog .stage-prog-img__thumb,
.stage-logo-prog .stage-prog-img__full {
- width: 2.4rem;
- height: 2.4rem;
+ height: min(3.2vw, 5.2vh);
+ width: auto;
+ max-width: min(12vw, 18vh);
object-fit: contain;
- filter: drop-shadow(0 0 8px rgba(196, 163, 90, 0.4));
+ filter: drop-shadow(0 0 0.417vw rgba(196, 163, 90, 0.4));
}
.stage-logo-prog .stage-prog-img__thumb {
- filter: blur(4px) drop-shadow(0 0 8px rgba(196, 163, 90, 0.4));
+ filter: blur(0.208vw) drop-shadow(0 0 0.417vw rgba(196, 163, 90, 0.4));
}
.stage-logo {
object-fit: contain;
- filter: drop-shadow(0 0 8px rgba(196, 163, 90, 0.4));
+ filter: drop-shadow(0 0 0.417vw rgba(196, 163, 90, 0.4));
}
.stage-title {
font-family: 'Noto Serif SC', serif;
- font-size: 1.25rem;
+ font-size: 1.042vw;
font-weight: 600;
- letter-spacing: 0.12em;
+ letter-spacing: 0.102vw;
color: var(--gold, #c4a35a);
background: linear-gradient(135deg, #f3ece4 0%, #c4a35a 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
-.stage-badge {
- padding: 0.2rem 0.6rem;
- border-radius: 0.2rem;
- font-size: 0.75rem;
- letter-spacing: 0.1em;
- background: rgba(166, 28, 32, 0.25);
- border: 1px solid rgba(166, 28, 32, 0.5);
- color: #f3ece4;
-}
-
/* Slide Nav Pills */
.stage-nav-pills {
display: flex;
align-items: center;
- gap: 0.35rem;
+ gap: 0.292vw;
background: rgba(28, 25, 31, 0.7);
- padding: 0.3rem 0.5rem;
- border-radius: 2rem;
+ padding: 0.444vh 0.417vw;
+ border-radius: 1.667vw;
border: 1px solid rgba(196, 163, 90, 0.2);
- flex-shrink: 1;
+ flex: 0 1 auto;
+ width: max-content;
+ max-width: min(var(--stage-content-max), 100%);
min-width: 0;
- overflow: hidden;
+ overflow-x: auto;
+ overflow-y: hidden;
+ scrollbar-width: none;
+ -webkit-overflow-scrolling: touch;
+}
+
+.stage-nav-pills::-webkit-scrollbar {
+ display: none;
}
.stage-nav-pill {
- padding: 0.35rem 0.85rem;
- border-radius: 1.5rem;
- font-size: 0.82rem;
+ padding: 0.519vh 0.708vw;
+ border-radius: 1.25vw;
+ font-size: 0.683vw;
color: rgba(243, 236, 228, 0.65);
text-decoration: none;
transition: all 0.3s cubic-bezier(0.22, 1, 0.36, 1);
white-space: nowrap;
+ flex-shrink: 0;
}
.stage-nav-pill:hover {
@@ -139,7 +168,7 @@
color: #121014;
background: linear-gradient(135deg, #e8d08d 0%, #c4a35a 100%);
font-weight: 600;
- box-shadow: 0 0 12px rgba(196, 163, 90, 0.35);
+ box-shadow: 0 0 0.625vw rgba(196, 163, 90, 0.35);
}
/* AutoPlay Button, Exit & Index Indicator */
@@ -148,23 +177,58 @@
align-items: center;
flex-wrap: nowrap;
flex-shrink: 0;
- gap: 0.85rem;
+ gap: 0.708vw;
}
.stage-autoplay-btn,
.stage-remote-toggle,
-.stage-exit-btn {
+.stage-exit-btn,
+.stage-theme-toggle {
white-space: nowrap;
flex-shrink: 0;
}
+.stage-theme-toggle {
+ min-height: auto;
+ padding: 0.519vh 0.625vw;
+ font-size: 0.65vw;
+ box-shadow: none;
+}
+
+.stage-theme-toggle > span:first-child {
+ width: 1.1vw;
+ height: 1.1vw;
+ font-size: 0.55vw;
+}
+
+.stage-theme-toggle .theme-toggle-label {
+ font-size: 0.65vw;
+}
+
+[data-theme='nostalgic'] .stage-nav-pills,
+[data-theme='nostalgic'] .stage-autoplay-btn,
+[data-theme='nostalgic'] .stage-remote-toggle {
+ color: #5c534c;
+ background: rgba(255, 255, 255, 0.68);
+ border-color: rgba(166, 28, 32, 0.14);
+}
+
+[data-theme='nostalgic'] .stage-nav-pill {
+ color: #6b5344;
+}
+
+[data-theme='nostalgic'] .stage-nav-pill:hover {
+ color: #a61c20;
+ background: rgba(166, 28, 32, 0.08);
+}
+
.stage-autoplay-btn {
display: inline-flex;
align-items: center;
- gap: 0.35rem;
- padding: 0.35rem 0.75rem;
- border-radius: 1.5rem;
- font-size: 0.78rem;
+ gap: 0.292vw;
+ padding: 0.519vh 0.625vw;
+ border-radius: 1.25vw;
+ font-size: 0.65vw;
color: rgba(243, 236, 228, 0.85);
background: rgba(28, 25, 31, 0.8);
border: 1px solid rgba(196, 163, 90, 0.3);
@@ -179,23 +243,23 @@
}
.stage-play-icon {
- font-size: 0.7rem;
+ font-size: 0.583vw;
}
.stage-index-num {
font-family: 'Noto Serif SC', serif;
- font-size: 1.1rem;
- letter-spacing: 0.1em;
+ font-size: 0.917vw;
+ letter-spacing: 0.085vw;
color: rgba(196, 163, 90, 0.85);
}
.stage-exit-btn {
display: inline-flex;
align-items: center;
- gap: 0.4rem;
- padding: 0.4rem 1rem;
- border-radius: 0.2rem;
- font-size: 0.82rem;
+ gap: 0.333vw;
+ padding: 0.593vh 0.833vw;
+ border-radius: 0.167vw;
+ font-size: 0.683vw;
color: #f3ece4;
background: rgba(166, 28, 32, 0.3);
border: 1px solid rgba(166, 28, 32, 0.6);
@@ -207,7 +271,7 @@
.stage-exit-btn:hover {
background: rgba(166, 28, 32, 0.7);
color: #ffffff;
- box-shadow: 0 0 15px rgba(166, 28, 32, 0.4);
+ box-shadow: 0 0 0.781vw rgba(166, 28, 32, 0.4);
}
/* Main Viewport Container */
@@ -216,36 +280,38 @@
inset: 0;
z-index: 10;
display: flex;
- align-items: center;
+ align-items: stretch;
justify-content: center;
- padding: calc(4.5rem + env(safe-area-inset-top, 0px)) 2.5rem calc(2.5rem + env(safe-area-inset-bottom, 0px));
+ padding: calc(6.667vh + env(safe-area-inset-top, 0px)) 2.083vw calc(3.704vh + env(safe-area-inset-bottom, 0px));
+ min-height: 0;
+ box-sizing: border-box;
}
/* Bottom Progress & Hints with Safe Area Bottom */
.stage-hud-bottom {
position: absolute;
- bottom: calc(0.85rem + env(safe-area-inset-bottom, 0px));
+ bottom: calc(1.259vh + env(safe-area-inset-bottom, 0px));
left: 0;
right: 0;
z-index: 50;
display: flex;
flex-direction: column;
align-items: center;
- gap: 0.4rem;
+ gap: 0.333vw;
pointer-events: none;
}
.stage-dots {
display: flex;
align-items: center;
- gap: 0.5rem;
+ gap: 0.417vw;
pointer-events: auto;
}
.stage-dot {
position: relative;
- width: 0.5rem;
- height: 0.5rem;
+ width: min(0.417vw, 0.741vh);
+ height: min(0.417vw, 0.741vh);
border-radius: 50%;
background: rgba(196, 163, 90, 0.25);
border: none;
@@ -256,8 +322,8 @@
}
.stage-dot.is-active {
- width: 2rem;
- border-radius: 0.25rem;
+ width: 1.667vw;
+ border-radius: 0.208vw;
background: rgba(196, 163, 90, 0.3);
}
@@ -268,7 +334,7 @@
bottom: 0;
width: 100%;
background: linear-gradient(90deg, var(--brand, #a61c20), var(--gold, #c4a35a));
- border-radius: 0.25rem;
+ border-radius: 0.208vw;
transform-origin: left;
animation: stageProgressBar var(--stage-progress-dur, 6s) linear forwards;
}
@@ -283,40 +349,34 @@
}
.stage-keyboard-hint {
- font-size: 0.72rem;
+ font-size: 0.6vw;
color: rgba(243, 236, 228, 0.4);
- letter-spacing: 0.08em;
+ letter-spacing: 0.068vw;
}
-/* Mobile Responsive Adjustments for Stage Shell */
-@media (max-width: 72rem) {
- .stage-nav-pills {
- display: none;
- }
-}
-@media (max-width: 48rem) {
+
+@media (max-width: 768px) {
.stage-hud-top {
- padding-left: 0.75rem;
- padding-right: 0.75rem;
- height: calc(3.6rem + env(safe-area-inset-top, 0px));
+ padding-left: 0.625vw;
+ padding-right: 0.625vw;
+ height: calc(5.333vh + env(safe-area-inset-top, 0px));
}
.stage-title,
- .stage-badge,
.stage-index-num {
display: none;
}
.stage-hud-actions {
- gap: 0.4rem;
+ gap: 0.333vw;
}
.stage-viewport {
- padding-top: calc(3.6rem + env(safe-area-inset-top, 0px));
- padding-left: 1rem;
- padding-right: 1rem;
- padding-bottom: calc(3rem + env(safe-area-inset-bottom, 0px));
+ padding-top: calc(5.333vh + env(safe-area-inset-top, 0px));
+ padding-left: 0.833vw;
+ padding-right: 0.833vw;
+ padding-bottom: calc(4.444vh + env(safe-area-inset-bottom, 0px));
}
.stage-exit-btn span:first-child {
@@ -332,14 +392,17 @@
.stage-view-anim {
width: 100%;
height: 100%;
+ min-height: 0;
+ display: flex;
+ align-items: stretch;
animation: stageFadeIn 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
}
@keyframes stageFadeIn {
from {
opacity: 0;
- transform: scale(0.98) translateY(10px);
- filter: blur(4px);
+ transform: scale(0.98) translateY(0.926vh);
+ filter: blur(0.208vw);
}
to {
opacity: 1;
diff --git a/src/stage/StageShell.tsx b/src/stage/StageShell.tsx
index e03ff78..b9560cc 100644
--- a/src/stage/StageShell.tsx
+++ b/src/stage/StageShell.tsx
@@ -1,9 +1,8 @@
import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties } from 'react'
import { Link, useLocation, useNavigate } from 'react-router-dom'
import { stageNavItems } from '../content/nav'
+import { images } from '../content/site'
import { StageViews } from './StageViews'
-import { StageProgressiveImage } from './StageProgressiveImage'
-import { stageImages } from './stageAssets'
import { useStageImagePreload } from './useStageImagePreload'
import { StageRemotePanel } from './remote/StageRemotePanel'
import { useStageHost } from './remote/useStageHost'
@@ -14,6 +13,7 @@ import {
type RemoteToHost,
} from './remote/protocol'
import { assetUrl } from '../lib/assetUrl'
+import { ThemeToggle } from '../hooks/useTheme'
import './StageShell.css'
const BGM_SRC = assetUrl('/assets/stage/bgm.wav')
@@ -30,6 +30,7 @@ export function StageShell() {
const location = useLocation()
const navigate = useNavigate()
const touchStartX = useRef
(null)
+ const navPillsRef = useRef(null)
const bgmRef = useRef(null)
const audioWantedRef = useRef(true)
const [isAutoPlay, setIsAutoPlay] = useState(true)
@@ -224,6 +225,13 @@ export function StageShell() {
return () => window.removeEventListener('keydown', onKey)
}, [handleNext, handlePrev])
+ useEffect(() => {
+ const nav = navPillsRef.current
+ if (!nav) return
+ const activePill = nav.querySelector('.stage-nav-pill.is-active')
+ activePill?.scrollIntoView({ inline: 'center', block: 'nearest', behavior: 'smooth' })
+ }, [activeIdx])
+
const normalUrl = location.pathname === '/' ? '/' : location.pathname
return (
@@ -234,14 +242,12 @@ export function StageShell() {
>
{/* Top HUD */}
-
-
- 杜康好客
- 全景展控
-
+
+

+
{/* Module Navigation Pills */}
-