/*
* Realm — Design System
* Castlevania-inspired: deep cold tones, orange accent, flat geometry
*
* Architecture: CSS @layer for cascade control
* Colors: oklch with relative color syntax
* SVG: fill-only, no stroke, CSS-controlled
*/
@layer reset, tokens, base, layout, components, utilities;
/* ================================================================
@property — typed custom properties for smooth animation
================================================================ */
@property --fill-current {
syntax: "<color>";
inherits: true;
initial-value: oklch(0.85 0.02 280);
}
@property --fill-accent {
syntax: "<color>";
inherits: true;
initial-value: oklch(0.72 0.18 55);
}
/* ================================================================
KEYFRAMES — skull opening animation
================================================================ */
@keyframes skull-split-up {
0%, 30% { transform: translateY(0); }
100% { transform: translateY(-110%); }
}
@keyframes skull-split-down {
0%, 30% { transform: translateY(0); }
100% { transform: translateY(110%); }
}
@keyframes wall-fade {
0%, 99% { visibility: visible; }
100% { visibility: hidden; }
}
@keyframes lock-shake {
0%, 100% { transform: rotate(0deg); }
15% { transform: rotate(-10deg); }
30% { transform: rotate(10deg); }
45% { transform: rotate(-7deg); }
60% { transform: rotate(7deg); }
75% { transform: rotate(-3deg); }
90% { transform: rotate(3deg); }
}
@keyframes lock-text-appear {
0% { opacity: 0; transform: translateY(0); }
12% { opacity: 1; transform: translateY(-8px); }
65% { opacity: 1; transform: translateY(-8px); }
100% { opacity: 0; transform: translateY(-14px); }
}
@keyframes forge-pulse {
0%, 100% { opacity: 0.25; }
50% { opacity: 0.4; }
}
/* ================================================================
RESET
================================================================ */
@layer reset {
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
}
img,
svg {
display: block;
max-width: 100%;
}
a {
color: inherit;
text-decoration: none;
}
ul,
ol {
list-style: none;
}
}
/* ================================================================
TOKENS
================================================================ */
@layer tokens {
:root {
/* === Background scale (deep cold) === */
--bg-abyss: oklch(0.10 0.02 290);
--bg-deep: oklch(0.15 0.03 285);
--bg-mid: oklch(0.22 0.04 280);
--bg-surface: oklch(0.28 0.04 278);
--bg-elevated: oklch(0.35 0.04 275);
/* === Text scale === */
--text-primary: oklch(0.85 0.02 280);
--text-secondary: oklch(0.60 0.03 280);
--text-dim: oklch(0.42 0.03 285);
/* === Accent: high-saturation orange (sole emphasis) === */
--accent: oklch(0.72 0.18 55);
--accent-bright: oklch(from var(--accent) calc(l + 0.1) c h);
--accent-dim: oklch(from var(--accent) calc(l - 0.15) c h);
--accent-glow: oklch(from var(--accent) l c h / 0.3);
/* === Spacing === */
--space-xs: 0.25rem;
--space-sm: 0.5rem;
--space-md: 1rem;
--space-lg: 2rem;
--space-xl: 4rem;
/* === Typography === */
--font-body: system-ui, -apple-system, "Segoe UI", sans-serif;
--font-mono: ui-monospace, "Cascadia Code", "Fira Code", monospace;
--text-sm: clamp(0.8rem, 0.75rem + 0.25vw, 0.875rem);
--text-base: clamp(0.95rem, 0.9rem + 0.25vw, 1.05rem);
--text-lg: clamp(1.2rem, 1rem + 1vw, 1.75rem);
--text-xl: clamp(1.8rem, 1.4rem + 2vw, 3rem);
/* === Motion === */
--ease-default: cubic-bezier(0.4, 0, 0.2, 1);
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--duration-fast: 150ms;
--duration-normal: 300ms;
--duration-slow: 600ms;
}
@media (prefers-reduced-motion: reduce) {
:root {
--duration-fast: 0ms;
--duration-normal: 0ms;
--duration-slow: 0ms;
}
}
}
/* ================================================================
BASE
================================================================ */
@layer base {
html {
color-scheme: dark;
}
body {
font-family: var(--font-body);
font-size: var(--text-base);
line-height: 1.6;
color: var(--text-primary);
background-color: var(--bg-abyss);
min-height: 100dvh;
overflow-x: hidden;
}
h1,
h2,
h3 {
line-height: 1.2;
color: var(--text-primary);
}
h1 { font-size: var(--text-xl); }
h2 { font-size: var(--text-lg); }
h3 { font-size: var(--text-base); }
p {
color: var(--text-secondary);
}
code {
font-family: var(--font-mono);
font-size: 0.9em;
}
::selection {
background: var(--accent-glow);
color: var(--text-primary);
}
}
/* ================================================================
LAYOUT
================================================================ */
@layer layout {
/* --- Page-level layout (used on sub-pages) --- */
.realm {
display: flex;
flex-direction: column;
align-items: center;
min-height: 100dvh;
padding: var(--space-lg);
}
/* --- Folder layout (index page) --- */
.realm-folder {
width: 100%;
margin-inline: auto;
height: 100dvh;
overflow: hidden;
display: flex;
flex-direction: column;
}
.realm-container {
width: 100%;
max-width: 72rem;
margin-inline: auto;
padding-inline: var(--space-md);
}
/* --- Skull overlay --- */
.skull-overlay {
position: fixed;
inset: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
pointer-events: none;
animation: wall-fade 2.5s var(--ease-out) forwards;
}
.skull-overlay .skull {
width: 100%;
height: 100%;
}
.skull-top {
animation: skull-split-up 2.5s var(--ease-out) forwards;
}
.skull-bottom {
animation: skull-split-down 2.5s var(--ease-out) forwards;
}
@media (prefers-reduced-motion: reduce) {
.skull-overlay {
display: none;
}
}
/* --- Folder content area --- */
.folder-content {
flex: 1;
background: var(--bg-deep);
min-height: 0;
overflow: hidden;
container-type: inline-size;
}
/* --- Scene SVG --- */
.folder-content svg {
width: 100%;
height: 100%;
}
/* --- Scene depth layers (compositing within a scene) --- */
.realm-layer {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.realm-layer--far { z-index: 1; }
.realm-layer--mid { z-index: 2; }
.realm-layer--near { z-index: 3; }
.realm-layer--ui { z-index: 10; }
/* --- Scene container (close-up pages: unified viewport lock) --- */
.realm-scene {
width: 100%;
height: 100dvh;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
background: var(--bg-abyss);
}
.realm-scene__content {
flex: 1;
min-height: 0;
width: 100%;
max-width: var(--scene-max-width, 56rem);
display: flex;
align-items: center;
justify-content: center;
padding-inline: var(--space-lg);
}
.realm-scene__backdrop {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: var(--scene-bg, var(--bg-abyss));
}
.realm-scene__backdrop > svg {
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
}
.realm-scene__nav {
flex-shrink: 0;
padding-block: var(--space-md);
}
}
/* ================================================================
COMPONENTS
================================================================ */
@layer components {
/* --- Navigation --- */
.realm-nav {
display: flex;
flex-direction: column;
gap: var(--space-md);
align-items: center;
text-align: center;
}
.realm-nav a {
display: inline-flex;
align-items: center;
gap: var(--space-sm);
font-size: var(--text-lg);
color: var(--text-secondary);
transition: color var(--duration-normal) var(--ease-default);
}
.realm-nav a:hover,
.realm-nav a:focus-visible {
color: var(--accent);
}
.realm-nav a:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 4px;
}
/* --- Realm Title / Sigil --- */
.realm-sigil {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-lg);
margin-block-end: var(--space-xl);
}
.realm-sigil svg {
width: clamp(6rem, 15vw, 12rem);
height: auto;
}
.realm-sigil__title {
font-size: var(--text-xl);
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--text-primary);
}
.realm-sigil__subtitle {
font-size: var(--text-sm);
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--text-dim);
}
/* --- SVG Components --- */
/* Fill defaults: all SVG paths inherit from CSS, not inline attributes */
.svg-icon {
fill: var(--fill-current);
transition: fill var(--duration-normal) var(--ease-default);
}
.svg-icon--accent {
fill: var(--fill-accent);
}
/* Interactive SVG elements */
.svg-interactive {
cursor: pointer;
transition:
fill var(--duration-normal) var(--ease-default),
opacity var(--duration-fast) var(--ease-default),
transform var(--duration-normal) var(--ease-default);
transform-origin: center;
}
.svg-interactive:hover {
fill: var(--accent);
transform: scale(1.05);
}
/* Decorative divider */
.realm-divider {
width: 100%;
max-width: 20rem;
height: auto;
margin-inline: auto;
opacity: 0.3;
}
.realm-divider path {
fill: var(--text-dim);
}
/* --- Scene Hotspots --- */
.hotspot {
cursor: pointer;
}
.hotspot .hotspot-shape {
fill: var(--bg-surface);
transition: fill var(--duration-normal) var(--ease-default);
}
.hotspot .fill-path {
fill: oklch(0.20 0.03 275);
}
.hotspot:hover .hotspot-shape,
.hotspot:focus-visible .hotspot-shape {
fill: var(--accent);
}
.hotspot .hotspot-icon {
fill: var(--text-dim);
transition: fill var(--duration-normal) var(--ease-default);
}
.hotspot:hover .hotspot-icon,
.hotspot:focus-visible .hotspot-icon {
fill: var(--bg-abyss);
}
.hotspot:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
/* Hotspot tooltip via CSS */
.hotspot .hotspot-label {
fill: var(--text-dim);
font-family: var(--font-body);
font-size: 11px;
opacity: 0;
transition: opacity var(--duration-normal) var(--ease-default);
}
.hotspot:hover .hotspot-label,
.hotspot:focus-visible .hotspot-label {
opacity: 1;
fill: var(--accent-bright);
}
/* Map-board labels: always visible (no tooltip opacity) */
.hotspot .map-label {
fill: var(--text-dim);
font-family: var(--font-body);
font-size: 11px;
transition: fill var(--duration-normal) var(--ease-default);
}
.hotspot:hover .map-label,
.hotspot:focus-visible .map-label {
fill: var(--accent-bright);
}
/* --- Scene fill classes --- */
.fill-sky { fill: oklch(0.12 0.03 290); }
.fill-mountain { fill: oklch(0.16 0.03 285); }
.fill-mountain-far { fill: oklch(0.13 0.025 288); }
.fill-ground { fill: oklch(0.18 0.035 280); }
.fill-wall { fill: oklch(0.25 0.04 278); }
.fill-wall-dark { fill: oklch(0.20 0.035 280); }
.fill-wall-light { fill: oklch(0.30 0.04 275); }
.fill-roof { fill: oklch(0.18 0.04 282); }
.fill-window { fill: oklch(from var(--accent) l c h / 0.5); }
.fill-window-glow { fill: oklch(from var(--accent) l c h / 0.25); }
.fill-door { fill: oklch(0.08 0.02 290); }
.fill-moon { fill: oklch(0.85 0.02 280); }
.fill-star { fill: oklch(0.70 0.02 280); }
.fill-path { fill: oklch(0.20 0.03 275); }
.fill-tree { fill: oklch(0.14 0.04 285); }
.fill-board { fill: var(--bg-elevated); }
.fill-accent-shape { fill: var(--accent); }
.fill-accent-dim { fill: var(--accent-dim); }
/* --- Skull SVG fills --- */
.fill-skull { fill: oklch(0.25 0.04 280); }
.fill-skull-eye { fill: var(--accent); }
/* --- Scene title text --- */
.scene-title {
fill: var(--text-dim);
font-family: var(--font-body);
font-size: 14px;
letter-spacing: 0.1em;
}
/* --- Notice Board --- */
.notice-board {
display: flex;
flex-direction: column;
align-items: center;
max-width: 56rem;
width: 100%;
margin-inline: auto;
margin-block-end: var(--space-lg);
}
.notice-board__frame {
background: var(--bg-elevated);
padding: var(--space-md);
width: 100%;
}
.notice-board__title {
font-size: var(--text-lg);
letter-spacing: 0.1em;
color: var(--accent);
text-align: center;
padding-block-end: var(--space-md);
}
.notice-board__surface {
background: var(--bg-mid);
padding: var(--space-lg);
max-height: 60vh;
overflow-y: auto;
}
.notice-board__surface h2 {
font-size: var(--text-base);
color: var(--accent-dim);
margin-block: var(--space-lg) var(--space-sm);
}
.notice-board__surface h2:first-child {
margin-block-start: 0;
}
.notice-board__surface p {
margin-block-end: var(--space-md);
}
.notice-board__surface ul {
margin-block-end: var(--space-md);
padding-inline-start: var(--space-lg);
list-style: disc;
color: var(--text-secondary);
}
.notice-board__surface li {
margin-block-end: var(--space-xs);
}
.notice-board__post {
width: 4rem;
height: 12rem;
background: var(--bg-elevated);
}
.notice-board__ground {
width: 100%;
margin-block-start: -1rem;
}
.notice-board__ground svg {
display: block;
width: 100%;
height: auto;
}
.notice-board__surface::-webkit-scrollbar {
width: 6px;
}
.notice-board__surface::-webkit-scrollbar-track {
background: var(--bg-deep);
}
.notice-board__surface::-webkit-scrollbar-thumb {
background: var(--bg-surface);
}
/* --- Scroll (卷轴) --- */
.scroll {
display: flex;
flex-direction: column;
align-items: center;
max-width: 56rem;
width: 100%;
margin-inline: auto;
}
.scroll__rod {
width: calc(100% + 2rem);
height: 0.75rem;
background: var(--bg-elevated);
border-radius: 0.375rem;
position: relative;
flex-shrink: 0;
}
.scroll__rod::before,
.scroll__rod::after {
content: '';
position: absolute;
top: 50%;
translate: 0 -50%;
width: 1.25rem;
height: 1.25rem;
background: var(--bg-elevated);
border-radius: 50%;
}
.scroll__rod::before { left: -0.375rem; }
.scroll__rod::after { right: -0.375rem; }
.scroll__body {
padding: var(--space-lg);
max-height: 60vh;
overflow-y: auto;
width: 100%;
background: var(--bg-surface);
}
.scroll__title {
font-size: var(--text-lg);
letter-spacing: 0.1em;
color: var(--accent);
text-align: center;
padding-block-end: var(--space-md);
}
.scroll__body h2 {
font-size: var(--text-base);
color: var(--accent-dim);
margin-block: var(--space-lg) var(--space-sm);
}
.scroll__body h2:first-of-type {
margin-block-start: 0;
}
.scroll__body p {
margin-block-end: var(--space-md);
}
.scroll__body ul {
margin-block-end: var(--space-md);
padding-inline-start: var(--space-lg);
list-style: disc;
color: var(--text-secondary);
}
.scroll__body li {
margin-block-end: var(--space-xs);
}
.scroll__body::-webkit-scrollbar {
width: 6px;
}
.scroll__body::-webkit-scrollbar-track {
background: transparent;
}
.scroll__body::-webkit-scrollbar-thumb {
background: var(--bg-surface);
}
/* --- Card / Panel --- */
.realm-content {
max-width: 40rem;
width: 100%;
}
/* --- Capsule Panel (:target overlay) --- */
.capsule-panel {
position: fixed;
inset: 0;
z-index: 10;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: oklch(0.05 0.02 290 / 0.92);
opacity: 0;
visibility: hidden;
transition:
opacity var(--duration-normal) var(--ease-default),
visibility var(--duration-normal) var(--ease-default);
}
.capsule-panel:target {
opacity: 1;
visibility: visible;
}
.capsule-panel .notice-board {
max-height: 80vh;
overflow-y: auto;
}
.capsule-panel .scroll {
max-width: min(90vw, 56rem);
padding-inline: var(--space-md);
}
.capsule-panel__close {
margin-block-start: var(--space-md);
color: var(--text-secondary);
font-size: var(--text-sm);
text-decoration: none;
transition: color var(--duration-normal) var(--ease-default);
min-width: 44px;
min-height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.capsule-panel__close:hover,
.capsule-panel__close:focus-visible {
color: var(--accent);
}
/* --- Close-up SVG panels (colophon :has(:target)) --- */
/* Keep panel visible when a descendant is :target */
.capsule-panel:has(:target) {
opacity: 1;
visibility: visible;
}
/* Close-up SVG sizing */
.closeup {
width: min(90vw, 56rem);
max-height: 80vh;
display: block;
}
/* Interactive items within close-up */
.closeup-item { cursor: pointer; }
.closeup-item__shape {
fill: var(--bg-surface);
transition: fill var(--duration-normal) var(--ease-default);
}
.closeup-item:hover .closeup-item__shape { fill: var(--accent); }
.closeup-item:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.closeup-item__name {
fill: var(--text-dim);
font-family: var(--font-body);
font-size: 12px;
transition: fill var(--duration-normal) var(--ease-default);
}
.closeup-item:hover .closeup-item__name { fill: var(--accent-bright); }
/* Description card — hidden by default, shown when parent <g> is :target */
.closeup-desc {
opacity: 0;
transition: opacity var(--duration-normal) var(--ease-default);
pointer-events: none;
}
:target > .closeup-desc {
opacity: 1;
pointer-events: auto;
}
.closeup-desc__bg { fill: var(--bg-deep); }
.closeup-desc__label { fill: var(--accent); font-family: var(--font-body); font-size: 14px; }
.closeup-desc__text { fill: var(--text-secondary); font-family: var(--font-body); font-size: 13px; }
/* Panel header text inside SVG */
.closeup-title { fill: var(--accent); font-family: var(--font-body); font-size: 18px; letter-spacing: 0.1em; }
/* Forge: dim non-targeted flames when one is selected */
.closeup--forge .closeup-item {
transition: opacity var(--duration-normal) var(--ease-default);
}
.closeup--forge:has(:target) .closeup-item:not(:target) {
opacity: 0.15;
}
/* Fire depth fills — graduated brightness (front=bright, back=dim) */
/* Layer 1 (front): reuses fill-window-glow / fill-window / fill-accent-shape */
.fill-fire-2-glow { fill: oklch(from var(--accent) calc(l - 0.06) c h / 0.2); }
.fill-fire-2-body { fill: oklch(from var(--accent) calc(l - 0.06) c h / 0.4); }
.fill-fire-2-core { fill: oklch(from var(--accent) calc(l - 0.06) c h); }
.fill-fire-3-glow { fill: oklch(from var(--accent) calc(l - 0.12) c h / 0.15); }
.fill-fire-3-body { fill: oklch(from var(--accent) calc(l - 0.12) c h / 0.3); }
.fill-fire-3-core { fill: oklch(from var(--accent) calc(l - 0.12) c h); }
.fill-fire-4-glow { fill: oklch(from var(--accent) calc(l - 0.2) c h / 0.1); }
.fill-fire-4-body { fill: oklch(from var(--accent) calc(l - 0.2) c h / 0.2); }
.fill-fire-4-core { fill: oklch(from var(--accent) calc(l - 0.2) c h); }
/* --- Capsule Lock (room padlock animation) --- */
.capsule-lock {
transform-box: fill-box;
transform-origin: center bottom;
}
.capsule-lock-text {
fill: var(--accent);
font-family: var(--font-body);
font-size: 11px;
opacity: 0;
}
:target > .capsule-lock {
animation: lock-shake 0.6s var(--ease-default);
}
:target > .capsule-lock-text {
animation: lock-text-appear 3s var(--ease-default) forwards;
}
@media (prefers-reduced-motion: reduce) {
:target > .capsule-lock {
animation: none;
}
:target > .capsule-lock-text {
animation: none;
opacity: 1;
}
}
/* --- Forge Glow (工坊炉火) --- */
.forge-glow {
animation: forge-pulse 3s var(--ease-default) infinite;
}
@media (prefers-reduced-motion: reduce) {
.forge-glow { animation: none; }
}
/* --- Phone Scene (联络处场景容器) --- */
.phone-scene {
background: oklch(0.25 0.04 278);
width: min(95%, 56rem);
min-height: 80dvh;
margin-inline: auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: var(--space-xl) var(--space-lg);
}
/* --- Phone Booth (联络处) --- */
.phone-booth {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-lg);
flex-wrap: wrap;
}
/* Note stack: two stacked notes */
.phone-note-stack {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
align-self: flex-start;
clip-path: inset(0 -2rem -200vh -2rem);
padding-block-end: var(--space-md);
}
/* Dial guide note */
.phone-note {
background: var(--bg-surface);
padding: var(--space-md) var(--space-lg);
width: 18rem;
position: relative;
z-index: 2;
}
.phone-note__title {
color: var(--accent);
font-size: var(--text-base);
letter-spacing: 0.05em;
margin-block-end: var(--space-sm);
}
.phone-note__list {
display: grid;
grid-template-columns: auto 1fr;
gap: var(--space-xs) var(--space-sm);
}
.phone-note__list dt {
color: var(--accent-bright);
font-family: var(--font-mono);
font-size: var(--text-base);
}
.phone-note__list dd {
color: var(--text-secondary);
font-size: var(--text-base);
}
/* Hidden hint note (checkbox hack) */
.phone-hint-toggle {
position: absolute;
opacity: 0;
pointer-events: none;
}
.phone-hint {
background: var(--bg-elevated);
padding: var(--space-xs) var(--space-md);
width: 11rem;
margin-block-start: -14rem;
transform: rotate(5deg);
position: relative;
z-index: 1;
transition: transform var(--duration-slow) var(--ease-out);
}
.phone-hint__tab {
display: block;
cursor: pointer;
color: var(--accent);
font-size: var(--text-sm);
text-align: center;
line-height: 2rem;
user-select: none;
}
.phone-hint__tab:hover {
color: var(--accent-bright);
}
.phone-hint__body {
padding-block-start: var(--space-sm);
color: var(--text-secondary);
font-size: var(--text-sm);
line-height: 1.8;
}
.phone-hint__body ol {
list-style: decimal;
padding-inline-start: var(--space-md);
}
.phone-hint__body li {
margin-block-end: var(--space-xs);
}
.phone-hint-toggle:checked ~ .phone-hint {
transform: translateY(calc(14rem + var(--space-xs))) rotate(5deg);
}
.phone-hint-toggle:focus-visible ~ .phone-hint .phone-hint__tab {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
.phone-hint {
transition: none;
}
}
/* Phone unit */
.phone {
position: relative;
display: flex;
flex-direction: column;
width: clamp(18rem, 50vw, 24rem);
}
/* Hood (canopy) */
.phone-hood {
background: var(--text-dim);
height: 1.25rem;
margin-inline: calc(-1 * var(--space-md));
position: relative;
}
.phone-hood::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 3px;
background: var(--bg-elevated);
}
/* Body */
.phone-body {
position: relative;
background: var(--bg-surface);
padding: var(--space-md);
padding-inline-start: calc(var(--space-md) + 3rem);
}
/* Shelf */
.phone-shelf {
background: var(--text-dim);
height: 0.75rem;
margin-inline: calc(-1 * var(--space-md));
position: relative;
}
.phone-shelf::before {
content: '';
position: absolute;
left: 50%;
translate: -50% 0;
bottom: 100%;
width: 0;
height: 0;
border-left: 0.5rem solid transparent;
border-right: 0.5rem solid transparent;
border-bottom: 0.5rem solid var(--text-dim);
}
/* Coin slot */
.phone-coin {
width: 50%;
margin-inline: auto;
margin-block: var(--space-sm);
height: 0.5rem;
background: var(--bg-deep);
border-top: 2px solid var(--bg-mid);
border-bottom: 2px solid var(--bg-mid);
}
/* Handset */
.phone-handset {
position: absolute;
left: var(--space-sm);
top: calc(var(--space-md) - 1rem);
width: 1.5rem;
height: 3.5rem;
background: var(--text-dim);
border: none;
cursor: pointer;
z-index: 1;
transition: transform var(--duration-normal) var(--ease-out);
}
/* T-shape: earpiece */
.phone-handset::before {
content: '';
position: absolute;
top: -0.25rem;
left: -0.375rem;
width: 2.25rem;
height: 0.75rem;
background: var(--text-dim);
border-radius: 0.25rem 0.25rem 0 0;
}
/* T-shape: mouthpiece */
.phone-handset::after {
content: '';
position: absolute;
bottom: -0.25rem;
left: -0.375rem;
width: 2.25rem;
height: 0.75rem;
background: var(--text-dim);
border-radius: 0 0 0.25rem 0.25rem;
}
.phone-handset:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.phone[data-state="idle"] .phone-handset {
transform: none;
}
.phone:not([data-state="idle"]) .phone-handset {
transform: translateY(-2rem) rotate(-15deg);
}
/* Keypad */
.phone-keypad {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 3px;
transition: opacity var(--duration-normal) var(--ease-default);
}
.phone-key {
background: var(--bg-elevated);
border: none;
color: var(--text-primary);
font-family: var(--font-mono);
font-size: var(--text-base);
padding: var(--space-sm) 0;
cursor: pointer;
transition: background var(--duration-fast) var(--ease-default);
}
.phone-key:hover {
background: var(--accent-dim);
}
.phone-key:active {
background: var(--accent);
}
.phone-key:focus-visible {
outline: 2px solid var(--accent);
outline-offset: -2px;
}
/* Keypad state: disabled when not ready */
.phone[data-state="idle"] .phone-keypad,
.phone[data-state="connecting"] .phone-keypad,
.phone[data-state="connected"] .phone-keypad,
.phone[data-state="error"] .phone-keypad {
pointer-events: none;
opacity: 0.3;
}
.phone[data-state="ready"] .phone-keypad {
pointer-events: auto;
opacity: 1;
}
/* Display */
.phone-display {
background: var(--bg-abyss);
color: var(--accent);
font-family: var(--font-mono);
padding: var(--space-sm) var(--space-md);
display: flex;
flex-direction: column;
gap: 2px;
min-height: 5.5rem;
box-shadow: inset 0 0 0 2px var(--bg-deep);
}
.phone-display__digits {
font-size: var(--text-lg);
letter-spacing: 0.2em;
min-height: 1.4em;
}
.phone-display__status {
font-size: var(--text-sm);
color: var(--text-dim);
}
/* Result (shown inside display) */
.phone-result {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.phone-result__link {
color: var(--accent-bright);
font-family: var(--font-mono);
font-size: var(--text-sm);
}
.phone-result__link:hover,
.phone-result__link:focus-visible {
color: var(--accent);
text-decoration: underline;
}
/* --- Skip Link (keyboard/screen reader shortcut) --- */
.skip-link {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
white-space: nowrap; border: 0;
z-index: 200;
background: var(--bg-abyss);
color: var(--accent);
font-size: var(--text-base);
text-decoration: none;
}
.skip-link:focus {
position: fixed;
top: var(--space-sm); left: var(--space-sm);
width: auto; height: auto;
padding: var(--space-sm) var(--space-md);
clip: auto; overflow: visible;
outline: 2px solid var(--accent);
outline-offset: 2px;
}
/* --- Screen Reader Navigation (always visually hidden) --- */
.scene-nav {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
white-space: nowrap; border: 0;
}
.scene-nav__list {
list-style: none;
padding: 0; margin: 0;
}
.scene-nav__link {
display: block;
padding: var(--space-xs);
color: var(--text-secondary);
}
a.scene-nav__link:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.scene-nav__link--locked {
color: var(--text-dim);
}
/* --- Touch Hitarea Expansion --- */
.hotspot-hitarea {
fill-opacity: 0;
pointer-events: all;
}
/* --- realm-scene context overrides --- */
.realm-scene .phone-scene {
width: 100%;
min-height: 0;
height: 100%;
}
.realm-scene .notice-board {
max-height: 100%;
overflow: hidden;
}
.realm-scene .notice-board__frame {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
.realm-scene .notice-board__surface {
max-height: none;
flex: 1;
min-height: 0;
}
/* --- Village (友邻村落) --- */
.village-viewport {
flex: 1;
min-height: 0;
min-width: 0;
overflow: auto;
background: var(--bg-deep);
}
.village-canvas {
min-width: 100%;
min-height: 100%;
}
.village-canvas svg {
display: block;
width: 100%;
height: 100%;
}
.village-controls {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-sm);
padding: var(--space-sm);
background: var(--bg-abyss);
flex-shrink: 0;
}
.village-zoom-label {
background: var(--bg-surface);
color: var(--text-primary);
font-family: var(--font-mono);
font-size: var(--text-sm);
padding: var(--space-xs) var(--space-sm);
cursor: pointer;
transition: background var(--duration-fast) var(--ease-default);
}
.village-zoom-label:hover {
background: var(--accent-dim);
}
.village-zoom-label:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
/* Active zoom level indicator */
#vz1:checked ~ .village-controls [for="vz1"],
#vz2:checked ~ .village-controls [for="vz2"],
#vz3:checked ~ .village-controls [for="vz3"],
#vz4:checked ~ .village-controls [for="vz4"] {
background: var(--accent-dim);
color: var(--text-primary);
}
/* Zoom levels via canvas sizing — SVG fills canvas, canvas overflows viewport */
#vz2:checked ~ .village-viewport .village-canvas { width: 150%; height: 150%; }
#vz3:checked ~ .village-viewport .village-canvas { width: 200%; height: 200%; }
#vz4:checked ~ .village-viewport .village-canvas { width: 300%; height: 300%; }
/* Village house hover: roof accent, window glow */
.village-house:hover .fill-roof,
.village-house:focus-visible .fill-roof {
fill: var(--accent-dim);
}
.village-house:hover .house-window,
.village-house:focus-visible .house-window {
fill: var(--accent);
}
/* Village labels: smaller font, hidden by default */
.village-label {
font-size: 9px;
}
/* Show labels at zoom ≥ 2× */
#vz3:checked ~ .village-viewport .village-canvas .village-label,
#vz4:checked ~ .village-viewport .village-canvas .village-label {
opacity: 1;
fill: var(--text-secondary);
}
/* --- Responsive scene rules --- */
@container (max-width: 25rem) {
.scene-detail {
display: none;
}
}
}
/* ================================================================
UTILITIES
================================================================ */
@layer utilities {
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.text-accent { color: var(--accent); }
.text-dim { color: var(--text-dim); }
.fill-accent { fill: var(--accent); }
.fill-primary { fill: var(--text-primary); }
}