Files
mtgodot-poc/.planning/sketches/001-mobile-hud/index.html
T

665 lines
90 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>移动端 HUD 草图 · 暗黑 ARPG</title>
<link rel="stylesheet" href="../themes/default.css">
<style>
* { box-sizing: border-box; }
:root { color-scheme: dark; }
html, body { margin: 0; min-height: 100%; background: radial-gradient(circle at 50% 0%, #1c3048 0, #0b1018 42rem); color: var(--color-text); font-family: var(--font-sans); }
body { padding: 34px 20px 60px; }
button, input { font: inherit; }
button { color: inherit; cursor: pointer; }
.page-head { max-width: 1040px; margin: 0 auto 24px; }
.eyebrow { color: var(--color-primary); font-size: var(--text-xs); letter-spacing: .18em; text-transform: uppercase; }
h1 { margin: 5px 0; font-size: clamp(1.45rem, 3vw, 2.2rem); }
.page-head p { margin: 6px 0 0; max-width: 720px; color: var(--color-text-muted); line-height: 1.55; }
.variant { max-width: 1040px; margin: auto; }
.variant-intro { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin: 0 auto 14px; max-width: 920px; }
.variant-intro h2 { margin: 0; font-size: var(--text-lg); }
.variant-intro p { margin: 0; color: var(--color-text-muted); font-size: var(--text-sm); }
.device-wrap { display: flex; justify-content: center; }
.device { position: relative; width: min(844px, calc(100vw - 40px)); aspect-ratio: 844 / 390; overflow: hidden; border: 1px solid rgba(221, 238, 255, .45); border-radius: 30px; background: #0a1018; box-shadow: 0 28px 80px rgba(0,0,0,.55), 0 0 0 7px rgba(255,255,255,.035); transition: width .25s ease, aspect-ratio .25s ease, border-radius .25s ease; }
.preview-tablet .device { width: min(1280px, calc(100vw - 40px)); aspect-ratio: 16 / 9; border-radius: 24px; }
.preview-desktop .device { width: min(1440px, calc(100vw - 40px)); aspect-ratio: 16 / 9; border-radius: 18px; }
.scene { position: absolute; inset: 0; overflow: hidden; background: linear-gradient(145deg, rgba(16,39,54,.85), rgba(8,17,25,.88) 48%, rgba(32,22,42,.94)); }
.scene::before { content: ""; position: absolute; inset: -12%; opacity: .55; background: radial-gradient(ellipse at 25% 37%, rgba(95,181,187,.28), transparent 24%), radial-gradient(ellipse at 78% 23%, rgba(144,97,221,.28), transparent 22%), linear-gradient(145deg, transparent 35%, rgba(255,211,108,.08) 36%, transparent 37% 58%, rgba(116,185,255,.08) 59%, transparent 60%); transform: rotate(-7deg); }
.scene::after { content: ""; position: absolute; inset: 0; background: linear-gradient(transparent 57%, rgba(4,8,13,.42) 82%, rgba(4,8,13,.78)); pointer-events: none; }
.terrain { position: absolute; inset: 32% 4% 16%; opacity: .4; background: repeating-linear-gradient(164deg, transparent 0 19px, rgba(173,214,213,.16) 20px 21px), repeating-linear-gradient(22deg, transparent 0 36px, rgba(46,70,82,.42) 37px 39px); clip-path: polygon(0 25%, 25% 4%, 53% 24%, 75% 0, 100% 21%, 100% 100%, 0 100%); }
.hero { position: absolute; left: 48%; top: 40%; width: 48px; height: 82px; transform: translate(-50%, -50%); border-radius: 45% 45% 28% 28%; background: linear-gradient(90deg, #152d44, #b7d7e8 45%, #315375 58%, #101d2d); box-shadow: 0 0 18px rgba(116,185,255,.45); }
.hero::before { content: ""; position: absolute; left: 9px; top: -26px; width: 30px; height: 32px; border-radius: 50%; background: radial-gradient(circle at 40% 42%, #ffe9c5 0 18%, #6f4c3e 19% 54%, #1a2432 55%); }
.hero::after { content: "✦"; position: absolute; right: -29px; top: 6px; color: var(--color-accent); font-size: 28px; text-shadow: 0 0 14px #ffb638; }
.mob { position: absolute; left: 68%; top: 44%; width: 58px; height: 42px; border-radius: 48% 52% 35% 35%; background: linear-gradient(140deg, #2b4155, #8c6672 45%, #201824); box-shadow: 0 0 18px rgba(255,111,120,.2); }
.mob::before { content: ""; position: absolute; left: 13px; top: -17px; width: 34px; height: 22px; border-radius: 55% 45% 35% 35%; background: #5f3442; }
.mob::after { content: "· ·"; position: absolute; left: 9px; top: -13px; color: #ffb1a9; letter-spacing: 6px; font-size: 13px; }
.top-status { position: absolute; z-index: 2; top: 14px; left: 14px; right: 14px; display: flex; align-items: center; gap: 9px; }
.avatar { width: 44px; height: 44px; flex: 0 0 auto; display: grid; place-items: center; border: 1px solid rgba(255,211,108,.78); border-radius: 50%; background: radial-gradient(circle, #658fb6, #182637 62%, #0b121c); box-shadow: 0 0 0 3px rgba(12,20,30,.58); font-size: 20px; }
.status-copy { min-width: 0; flex: 0 0 148px; width: 148px; }
.status-line { display: flex; align-items: center; justify-content: space-between; gap: 7px; font-size: var(--text-xs); }
.status-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.level { color: var(--color-accent); font-family: var(--font-mono); }
.meter { height: 7px; margin-top: 4px; overflow: hidden; border: 1px solid rgba(255,255,255,.18); border-radius: 8px; background: rgba(3,8,13,.72); }
.meter i { display: block; height: 100%; border-radius: inherit; }
.meter.hp i { width: 78%; background: linear-gradient(90deg, #f05767, #ff9d82); }
.meter.mp i { width: 62%; background: linear-gradient(90deg, #4f91f8, #8dc8ff); }
.meter.resource { height: 5px; margin-top: 3px; }
.meter.resource i { width: 68%; background: linear-gradient(90deg, #39217d, #8c55ed 70%, #caa0ff); }
.mini-map { position: relative; width: 74px; height: 74px; flex: 0 0 auto; overflow: hidden; border: 1px solid rgba(213,235,255,.52); border-radius: 50%; background: radial-gradient(circle at 44% 58%, rgba(101,178,161,.5) 0 12%, transparent 13%), linear-gradient(35deg, #17313a 0 38%, #284b58 39% 44%, #17313a 45% 63%, #4c385d 64%); box-shadow: inset 0 0 0 4px rgba(8,14,22,.55), 0 6px 14px rgba(0,0,0,.3); }
.mini-map::before { content: ""; position: absolute; left: 47%; top: 43%; width: 7px; height: 7px; border-radius: 50%; background: var(--color-accent); box-shadow: 0 0 0 4px rgba(255,211,108,.18), 16px -20px 0 -2px var(--color-danger); }
.target-card { position: absolute; z-index: 2; top: 94px; left: 50%; width: 190px; transform: translateX(-50%); padding: 8px 11px; opacity: .92; border: 1px solid rgba(255,111,120,.36); border-radius: 12px; background: rgba(13,19,29,.62); backdrop-filter: blur(12px); box-shadow: var(--shadow-sm); }
.target-card strong { display: block; font-size: var(--text-sm); }
.target-card small { color: #ffabb0; font-size: var(--text-xs); }
.target-card .meter { height: 5px; margin-top: 6px; }
.target-card .meter i { width: 43%; background: linear-gradient(90deg, #e65060, #ffab87); }
.quest-pill { position: absolute; z-index: 2; top: 102px; right: 14px; display: flex; align-items: center; gap: 6px; border: 1px solid rgba(255,211,108,.36); border-radius: 12px; padding: 8px 10px; background: rgba(13,19,29,.54); color: #ffe9ab; font-size: var(--text-xs); backdrop-filter: blur(12px); }
.chat-bubble { position: absolute; z-index: 2; left: 50%; top: 57%; transform: translateX(-50%); max-width: 168px; padding: 7px 10px; border: 1px solid rgba(255,255,255,.23); border-radius: 12px 12px 12px 3px; background: rgba(10,16,24,.72); color: #e3efff; font-size: var(--text-xs); backdrop-filter: blur(10px); }
.chat-bubble::after { content: ""; position: absolute; left: 11px; bottom: -7px; border: 6px solid transparent; border-top-color: rgba(10,16,24,.72); border-left: 0; }
.joystick { position: absolute; z-index: 4; left: 22px; bottom: 116px; width: 110px; height: 110px; border: 1px solid rgba(213,235,255,.24); border-radius: 50%; background: radial-gradient(circle, rgba(116,185,255,.2) 0 28%, rgba(116,185,255,.07) 29% 62%, rgba(10,16,24,.15) 63%); box-shadow: inset 0 0 0 12px rgba(9,17,26,.22); touch-action: none; }
.joystick::before { content: ""; position: absolute; inset: 18px; border: 1px dashed rgba(213,235,255,.24); border-radius: 50%; }
.stick { position: absolute; left: 50%; top: 50%; width: 54px; height: 54px; transform: translate(-50%,-50%); border: 1px solid rgba(213,235,255,.64); border-radius: 50%; background: radial-gradient(circle at 35% 28%, rgba(255,255,255,.32), rgba(73,134,193,.66) 38%, rgba(19,49,75,.88)); box-shadow: 0 5px 12px rgba(0,0,0,.36); transition: transform .12s ease; }
.joystick.is-active .stick { box-shadow: 0 0 0 6px rgba(116,185,255,.14), 0 5px 12px rgba(0,0,0,.36); }
.combat-cluster { position: absolute; z-index: 4; right: 16px; bottom: 116px; width: 156px; height: 156px; }
.skill { position: absolute; display: grid; place-items: center; width: 52px; height: 52px; border: 1px solid rgba(213,235,255,.54); border-radius: 50%; background: radial-gradient(circle at 36% 30%, rgba(255,255,255,.33), rgba(55,88,129,.8) 45%, rgba(10,19,31,.92)); box-shadow: 0 6px 14px rgba(0,0,0,.36); font-size: 20px; transition: transform .15s ease, filter .15s ease; }
.skill:hover, .skill:focus-visible { transform: scale(1.08); filter: brightness(1.25); }
.skill.main { right: 0; bottom: 14px; width: 78px; height: 78px; border-color: rgba(255,211,108,.74); background: radial-gradient(circle at 35% 25%, #ffdf9a, #ad5b4d 42%, #2b1b2e 78%); font-size: 29px; }
.skill.s1 { left: 12px; top: 5px; }
.skill.s2 { left: 52px; top: 52px; }
.skill.s3 { right: 4px; top: 3px; }
.skill.s4 { left: 0; bottom: 10px; }
.skill em { position: absolute; right: -2px; bottom: -2px; display: none; min-width: 18px; padding: 2px 4px; border-radius: 8px; background: #0a111c; color: #f9fcff; font-size: 10px; font-style: normal; }
.skill.cooling { filter: grayscale(.7) brightness(.58); }
.skill.cooling em { display: block; }
.context-action { position: absolute; z-index: 4; right: 23px; bottom: 279px; display: flex; align-items: center; gap: 6px; border: 1px solid rgba(101,225,178,.45); border-radius: 18px; padding: 7px 10px; background: rgba(14,44,46,.72); color: #adffe1; font-size: var(--text-xs); backdrop-filter: blur(10px); }
.utility-btn { min-width: 41px; height: 38px; display: grid; place-items: center; flex: 1; border: 1px solid rgba(213,235,255,.12); border-radius: 12px; background: rgba(116,185,255,.08); font-size: 17px; transition: all .15s ease; }
.utility-btn:hover, .utility-btn.active { border-color: rgba(116,185,255,.5); background: rgba(116,185,255,.21); transform: translateY(-2px); }
.utility-btn span { display: block; margin-top: 1px; color: var(--color-text-muted); font-size: 9px; }
.floating-cooldown { position: absolute; z-index: 5; left: 50%; top: 69%; transform: translate(-50%, -50%); padding: 9px 13px; border: 1px solid rgba(255,211,108,.42); border-radius: 16px; background: rgba(22,18,19,.74); color: #ffe4a0; font-size: var(--text-xs); opacity: 0; transition: opacity .2s ease; }
.floating-cooldown.show { opacity: 1; }
.modal-layer { position: absolute; z-index: 8; inset: 0; display: none; align-items: flex-end; background: rgba(2,6,10,.48); backdrop-filter: blur(6px); }
.modal-layer.open { display: flex; }
.sheet { width: 100%; max-height: 74%; overflow: auto; padding: 17px; border-top: 1px solid rgba(213,235,255,.32); border-radius: 24px 24px 0 0; background: rgba(12,21,33,.95); box-shadow: 0 -16px 42px rgba(0,0,0,.4); transform: translateY(20px); animation: rise .18s ease forwards; }
@keyframes rise { to { transform: translateY(0); } }
.sheet-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.sheet-head h3 { margin: 0; font-size: var(--text-lg); }
.sheet-title-row { display: flex; align-items: center; gap: 8px; }
.close-btn { width: 32px; height: 32px; border: 1px solid var(--color-border); border-radius: 50%; background: var(--color-surface-soft); }
.back-btn { width: 32px; height: 32px; border: 1px solid var(--color-border); border-radius: 50%; background: var(--color-surface-soft); }
.action-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.action-item { min-height: 58px; border: 1px solid rgba(213,235,255,.13); border-radius: 14px; background: rgba(116,185,255,.07); color: var(--color-text); transition: all .15s ease; }
.action-item:hover { border-color: rgba(116,185,255,.5); background: rgba(116,185,255,.16); transform: translateY(-2px); }
.action-item b { display: block; font-size: 20px; }
.action-item span { color: var(--color-text-muted); font-size: 10px; }
.menu-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.menu-item { min-height: 88px; padding: 10px 7px; border: 1px solid rgba(213,235,255,.16); border-radius: 15px; background: linear-gradient(145deg, rgba(116,185,255,.13), rgba(10,16,24,.35)); color: var(--color-text); text-align: center; transition: transform .15s ease, border-color .15s ease, background .15s ease; }
.menu-item:hover, .menu-item:focus-visible { transform: translateY(-2px); border-color: rgba(255,211,108,.72); background: rgba(116,185,255,.23); outline: none; }
.menu-item b { display: block; margin-bottom: 5px; color: var(--color-accent); font-size: 25px; line-height: 1; }
.menu-item span { display: block; font-size: var(--text-sm); }
.menu-item small { display: block; margin-top: 4px; color: var(--color-text-muted); font-size: 9px; line-height: 1.3; }
.sheet-note { margin: 10px 0 0; color: var(--color-text-muted); font-size: var(--text-xs); line-height: 1.4; }
.chat-list { display: grid; gap: 8px; margin-bottom: 10px; }
.chat-line { padding: 8px 10px; border-radius: 12px; background: rgba(255,255,255,.055); font-size: var(--text-sm); }
.chat-line strong { color: var(--color-primary); }
.chat-compose { display: flex; gap: 8px; }
.chat-compose input { min-width: 0; flex: 1; padding: 10px; border: 1px solid var(--color-border); border-radius: 12px; background: rgba(0,0,0,.25); color: var(--color-text); outline: none; }
.send-btn { padding: 0 13px; border: 1px solid rgba(101,225,178,.4); border-radius: 12px; background: rgba(101,225,178,.14); color: #adffe1; }
.annotation-label { position: fixed; z-index: 50; display: none; padding: 5px 7px; pointer-events: none; border: 1px solid rgba(255,211,108,.45); border-radius: 7px; background: #101a27; color: #ffe4a0; font: 11px var(--font-mono); box-shadow: var(--shadow-sm); }
body.annotate [data-label]:hover { outline: 1px dashed rgba(255,211,108,.7); outline-offset: 3px; }
.toolbar { position: fixed; z-index: 30; right: 16px; bottom: 16px; display: flex; align-items: center; gap: 7px; padding: 8px 10px; border: 1px solid rgba(255,255,255,.16); border-radius: 12px; background: rgba(5,9,14,.78); color: #fff; font-size: 11px; opacity: .44; transition: opacity .2s ease; backdrop-filter: blur(12px); }
.toolbar:hover { opacity: 1; }
.toolbar button, .toolbar select { border: 1px solid rgba(255,255,255,.19); border-radius: 7px; padding: 5px 7px; background: rgba(255,255,255,.08); color: #fff; }
.toolbar button:hover, .toolbar select:hover { background: rgba(116,185,255,.22); }
.toast { position: fixed; z-index: 60; left: 50%; bottom: 72px; transform: translate(-50%, 12px); padding: 9px 14px; opacity: 0; border: 1px solid rgba(213,235,255,.28); border-radius: 12px; background: rgba(9,16,25,.93); color: var(--color-text); box-shadow: var(--shadow-md); transition: all .18s ease; pointer-events: none; font-size: var(--text-sm); }
.toast.show { opacity: 1; transform: translate(-50%, 0); }
.interaction-section { max-width: 1040px; margin: 54px auto 0; }
.interaction-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; margin-bottom: 18px; }
.interaction-head h2 { margin: 5px 0 6px; font-size: clamp(1.35rem, 2.6vw, 2rem); }
.interaction-head p { max-width: 720px; margin: 0; color: #c6b399; line-height: 1.55; }
.interaction-legend { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 6px; max-width: 330px; }
.legend-chip { display: inline-flex; align-items: center; gap: 5px; padding: 6px 9px; border: 1px solid rgba(224, 181, 105, .26); border-radius: 999px; background: rgba(93, 53, 25, .2); color: #d8bd91; font-size: 10px; white-space: nowrap; }
.legend-chip i { width: 7px; height: 7px; border-radius: 50%; background: #e0b15e; box-shadow: 0 0 8px rgba(224, 177, 91, .7); }
.legend-chip.menu i { background: #8f6be8; box-shadow: 0 0 8px rgba(143, 107, 232, .7); }
.legend-chip.full i { background: #68baa0; box-shadow: 0 0 8px rgba(104, 186, 160, .7); }
.legend-chip.modal i { background: #e26c5c; box-shadow: 0 0 8px rgba(226, 108, 92, .7); }
.flow-filter { display: flex; flex-wrap: wrap; gap: 7px; margin: 0 0 14px; }
.flow-filter button { padding: 7px 12px; border: 1px solid rgba(224, 181, 105, .24); border-radius: 999px; background: rgba(44, 24, 14, .62); color: #c8b18e; font-size: 11px; }
.flow-filter button.active, .flow-filter button:hover { border-color: rgba(239, 195, 113, .72); background: rgba(163, 100, 44, .28); color: #f5d998; }
.flow-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.flow-card { min-width: 0; padding: 14px; border: 1px solid rgba(224, 181, 105, .25); border-radius: 16px; background: linear-gradient(145deg, rgba(64, 35, 20, .72), rgba(20, 11, 8, .88)); box-shadow: 0 10px 26px rgba(0,0,0,.18); }
.flow-card.is-hidden { display: none; }
.flow-card-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 12px; }
.flow-card-head h3 { margin: 0; color: #f4e0b5; font-size: 14px; }
.flow-index { color: #d8a758; font: 11px var(--font-mono); letter-spacing: .08em; }
.flow-scope { color: #a99478; font-size: 10px; }
.flow-track { display: flex; align-items: stretch; gap: 6px; overflow-x: auto; padding: 2px 1px 5px; scrollbar-width: thin; }
.flow-node { position: relative; min-width: 104px; min-height: 67px; padding: 9px 8px; border: 1px solid rgba(224, 181, 105, .24); border-radius: 12px; background: rgba(16, 9, 7, .8); color: #f5ead7; text-align: left; transition: transform .15s ease, border-color .15s ease, background .15s ease; }
.flow-node:hover, .flow-node:focus-visible { transform: translateY(-2px); border-color: rgba(239, 195, 113, .85); background: rgba(117, 68, 32, .36); outline: none; }
.flow-node b { display: block; margin-bottom: 5px; font-size: 12px; }
.flow-node small { display: block; color: #bba586; font-size: 10px; line-height: 1.35; }
.flow-node.menu-node { border-color: rgba(143, 107, 232, .4); }
.flow-node.full-node { border-color: rgba(104, 186, 160, .4); }
.flow-node.modal-node { border-color: rgba(226, 108, 92, .42); }
.flow-arrow { align-self: center; flex: 0 0 auto; color: #d8a758; font-size: 16px; opacity: .78; }
.flow-detail { margin-top: 14px; min-height: 132px; padding: 16px 18px; border: 1px solid rgba(239, 195, 113, .34); border-radius: 16px; background: rgba(20, 10, 7, .84); box-shadow: 0 15px 32px rgba(0,0,0,.22); }
.flow-detail.empty { display: grid; place-items: center; color: #bba586; text-align: center; }
.flow-detail h3 { margin: 4px 0 8px; color: #f4e0b5; font-size: 17px; }
.detail-kicker { color: #d8a758; font: 10px var(--font-mono); letter-spacing: .14em; text-transform: uppercase; }
.detail-grid { display: grid; grid-template-columns: 1.1fr 1fr 1fr; gap: 14px; }
.detail-block strong { display: block; margin-bottom: 4px; color: #d8bd91; font-size: 11px; }
.detail-block p { margin: 0; color: #c8b18e; font-size: 11px; line-height: 1.5; }
.detail-block p + p { margin-top: 4px; }
.flow-note { margin: 12px 0 0; padding-top: 10px; border-top: 1px solid rgba(224, 181, 105, .14); color: #9f8b70; font-size: 10px; line-height: 1.5; }
.hidden { display: none !important; }
@media (max-width: 620px) { body { padding-inline: 10px; } .variant-intro { display: block; padding-inline: 4px; } .variant-intro p { margin-top: 4px; } .toolbar { right: 8px; bottom: 8px; } }
/* Diablo-inspired ARPG direction: warm bronze frames, dark glass, circular combat controls. */
body { background: radial-gradient(circle at 50% 0%, #3a2416 0, #170e0b 35rem, #090706 70rem); }
.page-head p { color: #c6b399; }
.device { border-color: rgba(224, 181, 105, .72); border-radius: 22px; box-shadow: 0 28px 80px rgba(0,0,0,.7), 0 0 0 6px rgba(204, 147, 64, .08), inset 0 0 0 1px rgba(255, 229, 175, .14); }
.scene { background: linear-gradient(145deg, #725031, #b98045 40%, #4b2b1d 78%, #1b100c); }
.scene::before { opacity: .7; background: radial-gradient(ellipse at 34% 40%, rgba(255, 224, 152, .28), transparent 24%), radial-gradient(ellipse at 70% 28%, rgba(42, 16, 13, .4), transparent 27%), repeating-linear-gradient(151deg, rgba(255, 220, 155, .06) 0 2px, transparent 2px 38px); }
.scene::after { background: linear-gradient(180deg, rgba(12, 7, 5, .54), transparent 20%, transparent 62%, rgba(8, 5, 4, .7)); }
.terrain { opacity: .62; background: repeating-linear-gradient(164deg, transparent 0 17px, rgba(70, 35, 18, .18) 18px 20px), repeating-linear-gradient(22deg, transparent 0 35px, rgba(255, 218, 152, .16) 36px 38px); }
.hero { background: linear-gradient(90deg, #1a1210, #d2a85d 43%, #6b3d25 59%, #110b09); box-shadow: 0 0 20px rgba(255, 198, 93, .48); }
.hero::before { background: radial-gradient(circle at 40% 42%, #ffe0b2 0 18%, #6d3b2b 19% 54%, #160e0c 55%); }
.hero::after { color: #ffe0a1; text-shadow: 0 0 14px #ef812e; }
.mob { background: linear-gradient(140deg, #493021, #963f32 45%, #1c1110); box-shadow: 0 0 18px rgba(214, 69, 45, .35); }
.mob::before { background: #4f1e1b; }
.mob::after { color: #ffc0a0; }
.top-status { top: 15px; left: 18px; right: auto; }
.player-status { display: flex; width: 148px; flex-direction: column; align-items: center; gap: 3px; }
.player-name { display: flex; width: 148px; align-items: center; justify-content: center; gap: 6px; color: #f4e0b5; font: 600 11px var(--font-mono); text-shadow: 0 2px 4px #000; white-space: nowrap; }
.player-meters { width: 44px; }
.party-rail { position: absolute; z-index: 4; top: 112px; left: 18px; display: grid; width: 148px; gap: 5px; }
.party-member { display: grid; grid-template-columns: 27px minmax(0, 1fr) auto; align-items: center; gap: 6px; min-height: 34px; padding: 4px 6px 4px 4px; border: 1px solid rgba(223, 181, 111, .32); border-radius: 8px; background: rgba(19, 10, 7, .65); color: #f4e0b5; text-align: left; box-shadow: 0 4px 10px rgba(0,0,0,.28); transition: transform .15s ease, border-color .15s ease, background .15s ease; }
.party-member:hover, .party-member:focus-visible { transform: translateX(2px); border-color: rgba(239, 195, 113, .76); background: rgba(87, 46, 22, .78); outline: none; }
.party-avatar { display: grid; width: 25px; height: 25px; place-items: center; border: 1px solid rgba(239, 195, 112, .6); border-radius: 50%; background: radial-gradient(circle, #89684e, #291711 68%); color: #f7d58f; font-size: 12px; }
.party-copy { min-width: 0; }
.party-copy strong { display: block; overflow: hidden; color: #e9d5ae; font-size: 9px; line-height: 1.15; text-overflow: ellipsis; white-space: nowrap; }
.party-hp { height: 4px; margin-top: 3px; overflow: hidden; border-radius: 4px; background: rgba(5, 3, 2, .72); }
.party-hp i { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, #8a2525, #e85a46); }
.party-distance { color: #bfa77f; font: 9px var(--font-mono); }
.top-right-controls { position: absolute; z-index: 4; top: 14px; right: 14px; display: flex; align-items: flex-start; gap: 7px; }
.top-right-controls .menu-button { min-width: 42px; width: 42px; height: 42px; flex: 0 0 auto; background: rgba(17, 9, 7, .78); border-color: rgba(224, 177, 91, .5); box-shadow: 0 4px 10px rgba(0,0,0,.42); }
.avatar { border-color: rgba(239, 195, 112, .92); background: radial-gradient(circle, #99704c, #2b1a13 62%, #0d0907); box-shadow: 0 0 0 3px rgba(13, 8, 6, .7), 0 0 16px rgba(231, 167, 68, .22); }
.level { color: #f4ca7b; }
.meter { height: 9px; border-color: rgba(255, 227, 170, .25); background: rgba(11, 6, 5, .82); box-shadow: inset 0 1px 2px rgba(0,0,0,.45); }
.meter.hp i { background: linear-gradient(90deg, #761b1e, #e2432c 72%, #ff8961); }
.meter.mp i { background: linear-gradient(90deg, #3c247f, #8454f0 72%, #c58aff); }
.mini-map { width: 142px; height: 86px; border-radius: 9px; border-color: rgba(223, 187, 119, .72); background: radial-gradient(circle at 43% 58%, rgba(226, 179, 83, .42) 0 7%, transparent 8%), linear-gradient(32deg, #21150e 0 31%, #6a4323 32% 39%, #271711 40% 63%, #80603b 64%); box-shadow: inset 0 0 0 4px rgba(8, 5, 4, .68), 0 6px 14px rgba(0,0,0,.48); }
.mini-map::before { left: 47%; top: 43%; background: #f8d17e; box-shadow: 0 0 0 4px rgba(255, 205, 106, .2), 16px -20px 0 -2px #d65838; }
.mini-map::after { content: "荒漠边境"; position: absolute; right: 7px; bottom: 5px; color: rgba(245, 213, 157, .7); font: 10px var(--font-mono); letter-spacing: .08em; }
.target-card { top: 13px; width: 260px; padding: 4px 12px 6px; border: 0; border-radius: 0; background: rgba(13, 8, 6, .2); box-shadow: none; text-align: center; }
.target-card strong { color: #f4e0b5; font: 600 15px var(--font-mono); letter-spacing: .1em; text-shadow: 0 2px 4px #000; }
.target-card small { color: #d0b285; }
.target-card .meter { height: 11px; margin-top: 5px; border-color: rgba(10, 5, 4, .9); }
.target-card .meter i { background: linear-gradient(90deg, #6d171a, #d43d2e 75%, #f06d45); }
.quest-pill { top: 109px; right: 168px; border-color: rgba(224, 177, 91, .38); background: rgba(24, 13, 9, .65); color: #f1d49a; box-shadow: 0 5px 13px rgba(0,0,0,.25); }
.chat-bubble { top: 57%; background: rgba(17, 9, 7, .72); border-color: rgba(236, 197, 133, .28); color: #f4e6cb; }
.context-action { right: 235px; bottom: 104px; border-color: rgba(219, 173, 93, .58); background: rgba(25, 12, 8, .74); color: #f6d593; box-shadow: 0 5px 15px rgba(0,0,0,.32); }
.right-rail { position: absolute; z-index: 4; top: 111px; right: 14px; display: grid; gap: 8px; }
.right-rail .utility-btn { min-width: 42px; width: 42px; height: 42px; flex: 0 0 auto; background: rgba(17, 9, 7, .64); border-color: rgba(216, 171, 99, .3); box-shadow: 0 4px 10px rgba(0,0,0,.3); }
.right-rail .utility-btn span { color: #d8bd91; }
.joystick { left: 25px; bottom: 18px; width: 120px; height: 120px; border-color: rgba(223, 181, 111, .32); background: radial-gradient(circle, rgba(185, 119, 58, .26) 0 28%, rgba(74, 39, 22, .42) 29% 62%, rgba(12, 7, 5, .26) 63%); box-shadow: inset 0 0 0 12px rgba(27, 13, 8, .28), 0 7px 18px rgba(0,0,0,.35); }
.joystick::before { border-color: rgba(238, 202, 139, .2); }
.stick { border-color: rgba(236, 194, 117, .58); background: radial-gradient(circle at 35% 28%, rgba(255, 239, 196, .28), rgba(114, 70, 38, .82) 38%, rgba(30, 14, 9, .94)); }
.combat-cluster { right: 20px; bottom: 15px; width: 190px; height: 190px; }
.combat-cluster::before { content: ""; position: absolute; inset: 16px -1px -2px 8px; border: 1px solid rgba(185, 132, 68, .3); border-radius: 50%; background: radial-gradient(circle, rgba(21, 10, 8, .56), rgba(12, 6, 5, .12) 68%, transparent 69%); box-shadow: 0 0 0 11px rgba(12, 6, 5, .18); }
.combat-cluster::after { content: ""; position: absolute; inset: 5px -4px 12px 5px; border: 2px solid rgba(219, 169, 83, .28); border-left-color: transparent; border-bottom-color: transparent; border-radius: 50%; transform: rotate(34deg); pointer-events: none; }
.skill { z-index: 1; width: 58px; height: 58px; border-color: rgba(232, 191, 115, .58); background: radial-gradient(circle at 35% 26%, rgba(247, 225, 177, .24), rgba(62, 28, 22, .94) 46%, rgba(12, 6, 6, .98)); box-shadow: 0 6px 14px rgba(0,0,0,.6), inset 0 0 0 3px rgba(6, 3, 3, .36); }
.skill:hover, .skill:focus-visible { border-color: #f4d28d; filter: brightness(1.3); box-shadow: 0 0 0 3px rgba(238, 187, 90, .2), 0 7px 16px rgba(0,0,0,.6); }
.skill.main { width: 88px; height: 88px; border-color: rgba(234, 196, 123, .86); background: radial-gradient(circle at 35% 25%, #7e5cf1, #321d75 48%, #0e0820 78%); box-shadow: 0 0 0 4px rgba(15, 7, 28, .75), 0 0 18px rgba(107, 65, 220, .6), 0 7px 17px rgba(0,0,0,.65); font-size: 31px; }
.skill.main:hover, .skill.main:focus-visible { box-shadow: 0 0 0 5px rgba(232, 190, 100, .27), 0 0 24px rgba(138, 92, 255, .78); }
.skill.s1 { left: 10px; top: 14px; }
.skill.s2 { left: 62px; top: 70px; }
.skill.s3 { right: 2px; top: 15px; }
.skill.s4 { left: 13px; bottom: 2px; }
#mobile-hud .skill.s4 { left: auto; right: 16px; top: -52px; bottom: auto; border-color: rgba(223, 66, 44, .86); background: radial-gradient(circle at 35% 25%, #f06a40, #7d1f25 45%, #1a080b 78%); }
.skill em { background: #140907; color: #f2d491; }
.utility-btn { border-color: rgba(215, 171, 98, .2); border-radius: 7px; background: rgba(124, 76, 40, .16); }
.utility-btn:hover, .utility-btn.active { border-color: rgba(240, 200, 123, .72); background: rgba(173, 107, 46, .28); }
.utility-btn span { color: #cdb58d; }
.modal-layer { background: rgba(7, 3, 2, .6); }
.sheet { border-top-color: rgba(226, 181, 99, .5); background: rgba(25, 14, 10, .97); }
.action-item { border-color: rgba(220, 176, 100, .24); background: rgba(139, 82, 40, .12); }
.action-item:hover { border-color: rgba(239, 195, 113, .7); background: rgba(163, 100, 44, .24); }
.action-item span, .sheet-note { color: #c8b18e; }
.menu-item { border-color: rgba(220, 176, 100, .3); background: linear-gradient(145deg, rgba(128, 73, 34, .38), rgba(30, 15, 9, .72)); box-shadow: inset 0 1px 0 rgba(255, 224, 163, .08), 0 6px 14px rgba(0,0,0,.2); }
.menu-item:hover, .menu-item:focus-visible { border-color: rgba(239, 195, 113, .82); background: linear-gradient(145deg, rgba(164, 95, 40, .52), rgba(44, 20, 10, .82)); }
.menu-item b { color: #f0c979; }
.menu-item small { color: #bda887; }
.send-btn { border-color: rgba(117, 186, 123, .5); background: rgba(75, 122, 73, .2); color: #c3e8b9; }
.toolbar { background: rgba(10, 6, 4, .88); border-color: rgba(228, 183, 103, .32); }
.toolbar button:hover, .toolbar select:hover { background: rgba(171, 104, 43, .28); }
.toast { background: rgba(20, 10, 7, .96); border-color: rgba(224, 178, 98, .42); }
@media (max-height: 460px) and (orientation: landscape) {
body { padding-top: 66px; }
.top-status { top: 10px; }
.player-status, .player-name { width: 122px; }
.player-meters { width: 44px; }
.player-name { font-size: 9px; }
.party-rail { top: 91px; left: 10px; width: 122px; gap: 4px; }
.party-member { min-height: 30px; padding-block: 3px; }
.party-avatar { width: 22px; height: 22px; font-size: 10px; }
.top-right-controls { top: 9px; right: 9px; }
.mini-map { width: 108px; height: 64px; }
.right-rail { top: 82px; }
.status-copy { flex-basis: 122px; width: 122px; }
.quest-pill { top: 84px; right: 128px; }
.target-card { top: 8px; }
.joystick { width: 96px; height: 96px; left: 18px; bottom: 13px; }
.combat-cluster { right: 14px; bottom: 10px; transform: scale(.82); transform-origin: bottom right; }
.context-action { bottom: 88px; }
}
@media (max-width: 820px) {
.interaction-head { display: block; }
.interaction-legend { justify-content: flex-start; margin-top: 12px; }
.flow-grid { grid-template-columns: 1fr; }
.detail-grid { grid-template-columns: 1fr; gap: 9px; }
}
</style>
</head>
<body>
<header class="page-head">
<div class="eyebrow">Mobile ARPG Control HUD / Sketch 001</div>
<h1>暗黑 ARPG 风格 · 横屏 HUD 草图</h1>
<p>按 844×390 横屏手机设计,保持世界画面优先,战斗操作触手可达;底部中央保持留白,低频功能统一收纳到右上角菜单。点击首页控件或下方交互图节点,查看完整的移动端 UI 流程。</p>
</header>
<main id="sketch-canvas" class="preview-phone">
<section id="mobile-hud" class="variant">
<div class="variant-intro"><h2>暗黑 ARPG 横屏 HUD</h2><p>左上按“名称→头像→HP/MP/资源短条”排列;右上按“背包→小地图→菜单”排列,地图通过点击小地图进入,底部中央保持留白。</p></div>
<div class="device-wrap"><div class="device" data-label="844 × 390 landscape · 暗黑 ARPG">
<div class="scene">
<div class="terrain"></div><div class="hero" data-label="玩家角色"></div><div class="mob"></div>
<div class="top-status" data-label="玩家状态"><div class="player-status"><div class="player-name"><span>Azure · 暮光骑士</span><span class="level">Lv.42</span></div><button class="avatar demo-btn" data-panel="character" data-label="打开角色状态"></button><div class="player-meters"><div class="meter hp" aria-label="生命值"><i></i></div><div class="meter mp" aria-label="法力值"><i></i></div><div class="meter resource" aria-label="职业资源:奥能 68%"><i></i></div></div></div></div>
<div class="party-rail" data-label="队伍信息:仅组队后显示"><button class="party-member demo-btn" data-panel="party" data-label="队员 Mira · 点击查看或定位"><span class="party-avatar">M</span><span class="party-copy"><strong>Mira · 圣骑士</strong><span class="party-hp"><i style="width: 83%"></i></span></span><span class="party-distance">12m</span></button><button class="party-member demo-btn" data-panel="party" data-label="队员 Ken · 点击查看或定位"><span class="party-avatar">K</span><span class="party-copy"><strong>Ken · 游侠</strong><span class="party-hp"><i style="width: 61%"></i></span></span><span class="party-distance">34m</span></button></div>
<div class="top-right-controls" data-label="顶部背包、地图与菜单"><button class="utility-btn top-bag demo-btn" data-panel="inventory" data-label="打开背包"><span>背包</span></button><button class="mini-map demo-btn" data-panel="map" aria-label="打开地图" data-label="小地图 / 地图"></button><button class="utility-btn menu-button demo-btn" data-panel="more" data-label="打开菜单"><span>菜单</span></button></div>
<button class="target-card demo-btn" data-panel="target" data-label="目标板"><strong>血狼 · Lv.40</strong><small>敌对目标</small><div class="meter"><i></i></div></button>
<button class="quest-pill demo-btn" data-panel="quest" data-label="任务追踪">◆ 采集狼牙 <b>2/5</b></button>
<div class="chat-bubble" data-label="头顶聊天气泡">附近:小心精英怪!</div>
<button class="context-action demo-btn" data-toast="靠近目标后:拾取 / 对话 / 钓鱼 / 进入副本">✦ 交互</button>
<div class="joystick" data-joystick data-label="虚拟摇杆"><div class="stick"></div></div>
<div class="combat-cluster" data-label="战斗技能区">
<button class="skill s1 demo-btn" data-skill="冰刃" data-toast="释放技能:冰刃"><em>8</em></button>
<button class="skill s2 demo-btn" data-skill="冲锋" data-toast="释放技能:冲锋"><em>5</em></button>
<button class="skill s3 demo-btn" data-skill="治疗" data-toast="释放技能:治疗"><em>12</em></button>
<button class="skill s4 demo-btn" data-skill="药水" data-toast="使用物品:红色药水"><em>3</em></button>
<button class="skill main demo-btn" data-skill="普攻" data-toast="普通攻击"></button>
</div>
<div class="floating-cooldown" data-cooldown>技能冷却中</div>
<div class="modal-layer" data-modal><div class="sheet"><div class="sheet-head"><div class="sheet-title-row"><button class="back-btn hidden" data-back aria-label="返回上一级"></button><h3 data-sheet-title>功能</h3></div><button class="close-btn" data-close>×</button></div><div data-sheet-body></div></div></div>
</div>
</div></div>
</section>
</main>
<section class="interaction-section" aria-labelledby="interaction-title">
<div class="interaction-head">
<div>
<div class="eyebrow">Interaction map / all screens</div>
<h2 id="interaction-title">全量 UI 移动端交互图</h2>
<p>每张图表示一条完整操作路径。点击任意节点,查看它的入口位置、触控方式、页面形态和返回路径;任务始终从任务栏进入,组队信息固定显示在左上玩家头像下方。</p>
</div>
<div class="interaction-legend" aria-label="页面形态图例">
<span class="legend-chip"><i></i>常驻 HUD</span>
<span class="legend-chip menu"><i></i>右上角菜单</span>
<span class="legend-chip full"><i></i>全屏面板</span>
<span class="legend-chip modal"><i></i>场景弹窗</span>
</div>
</div>
<div class="flow-filter" role="tablist" aria-label="交互图筛选">
<button class="active" data-flow-filter="all" role="tab" aria-selected="true">全部流程</button>
<button data-flow-filter="entry" role="tab" aria-selected="false">进入游戏</button>
<button data-flow-filter="combat" role="tab" aria-selected="false">战斗与任务</button>
<button data-flow-filter="character" role="tab" aria-selected="false">角色与物品</button>
<button data-flow-filter="social" role="tab" aria-selected="false">社交与经济</button>
<button data-flow-filter="system" role="tab" aria-selected="false">系统状态</button>
</div>
<div class="flow-grid" id="flow-grid">
<article class="flow-card" data-flow-group="entry">
<div class="flow-card-head"><h3>进入游戏与角色入口</h3><span class="flow-index">01 · ENTRY</span></div>
<div class="flow-track">
<button class="flow-node full-node" data-detail="login"><b>登录</b><small>账号密码<br>失败提示</small></button><span class="flow-arrow"></span>
<button class="flow-node full-node" data-detail="server"><b>服务器</b><small>频道选择<br>连接确认</small></button><span class="flow-arrow"></span>
<button class="flow-node full-node" data-detail="character_select"><b>角色列表</b><small>选择 / 创建<br>删除 / 改名</small></button><span class="flow-arrow"></span>
<button class="flow-node modal-node" data-detail="loading"><b>加载</b><small>资源加载<br>进入场景</small></button><span class="flow-arrow"></span>
<button class="flow-node" data-detail="hud"><b>首页 HUD</b><small>开始操作</small></button>
</div>
</article>
<article class="flow-card" data-flow-group="combat">
<div class="flow-card-head"><h3>首页战斗与掉落</h3><span class="flow-index">02 · COMBAT</span></div>
<div class="flow-track">
<button class="flow-node" data-detail="hud"><b>首页 HUD</b><small>左上状态<br>右上入口</small></button><span class="flow-arrow"></span>
<button class="flow-node" data-detail="movement"><b>移动摇杆</b><small>拖动移动<br>松手复位</small></button><span class="flow-arrow"></span>
<button class="flow-node" data-detail="target"><b>锁定目标</b><small>点击目标<br>显示血条</small></button><span class="flow-arrow"></span>
<button class="flow-node" data-detail="combat_action"><b>普攻 / 技能</b><small>点击释放<br>冷却反馈</small></button><span class="flow-arrow"></span>
<button class="flow-node modal-node" data-detail="loot"><b>掉落 / 交互</b><small>拾取、对话<br>进入副本</small></button><span class="flow-arrow"></span>
<button class="flow-node" data-detail="death"><b>死亡 / 复活</b><small>战斗失败<br>回城或复活</small></button>
</div>
</article>
<article class="flow-card" data-flow-group="combat">
<div class="flow-card-head"><h3>任务、地图与副本</h3><span class="flow-index">03 · QUEST</span></div>
<div class="flow-track">
<button class="flow-node" data-detail="quest"><b>任务栏</b><small>首页常驻<br>直接点击</small></button><span class="flow-arrow"></span>
<button class="flow-node full-node" data-detail="quest_log"><b>任务日志</b><small>列表 / 奖励<br>任务详情</small></button><span class="flow-arrow"></span>
<button class="flow-node full-node" data-detail="map"><b>小地图 / 大地图</b><small>点击小地图<br>地图信标</small></button><span class="flow-arrow"></span>
<button class="flow-node" data-detail="navigation"><b>自动寻路</b><small>导航至目标<br>到达提示</small></button><span class="flow-arrow"></span>
<button class="flow-node modal-node" data-detail="npc_quest"><b>NPC 对话</b><small>接取 / 完成<br>任务奖励</small></button><span class="flow-arrow"></span>
<button class="flow-node modal-node" data-detail="dungeon_result"><b>副本结算</b><small>奖励 / 再次挑战<br>退出副本</small></button>
</div>
</article>
<article class="flow-card" data-flow-group="character">
<div class="flow-card-head"><h3>角色、装备与技能</h3><span class="flow-index">04 · CHARACTER</span></div>
<div class="flow-track">
<button class="flow-node" data-detail="character_entry"><b>玩家头像</b><small>左上角<br>唯一入口</small></button><span class="flow-arrow"></span>
<button class="flow-node full-node" data-detail="character_status"><b>角色属性</b><small>等级、属性<br>成长信息</small></button><span class="flow-arrow"></span>
<button class="flow-node full-node" data-detail="equipment"><b>装备</b><small>穿戴、替换<br>查看对比</small></button><span class="flow-arrow"></span>
<button class="flow-node full-node" data-detail="skills"><b>技能</b><small>主动、被动<br>坐骑、表情</small></button><span class="flow-arrow"></span>
<button class="flow-node modal-node" data-detail="skill_upgrade"><b>技能加点</b><small>查看消耗<br>确认升级</small></button><span class="flow-arrow"></span>
<button class="flow-node modal-node" data-detail="inspect"><b>查看玩家</b><small>点击角色<br>查看装备</small></button>
</div>
</article>
<article class="flow-card" data-flow-group="character">
<div class="flow-card-head"><h3>背包、物品与仓库</h3><span class="flow-index">05 · ITEMS</span></div>
<div class="flow-track">
<button class="flow-node menu-node" data-detail="inventory"><b>背包</b><small>右上快捷入口<br>菜单也可进入</small></button><span class="flow-arrow"></span>
<button class="flow-node modal-node" data-detail="item_tooltip"><b>物品详情</b><small>点击选中<br>长按查看</small></button><span class="flow-arrow"></span>
<button class="flow-node modal-node" data-detail="item_action"><b>使用 / 装备 / 出售</b><small>按钮操作<br>危险操作确认</small></button><span class="flow-arrow"></span>
<button class="flow-node full-node" data-detail="safebox"><b>仓库</b><small>背包与仓库<br>左右切换</small></button><span class="flow-arrow"></span>
<button class="flow-node modal-node" data-detail="select_item"><b>任务物品选择</b><small>选择数量<br>提交确认</small></button>
</div>
</article>
<article class="flow-card" data-flow-group="social">
<div class="flow-card-head"><h3>队伍状态、聊天、好友与公会</h3><span class="flow-index">06 · SOCIAL</span></div>
<div class="flow-track">
<button class="flow-node" data-detail="party"><b>左侧队伍条</b><small>头像下方<br>成员状态</small></button><span class="flow-arrow"></span>
<button class="flow-node modal-node" data-detail="party"><b>队员信息</b><small>查看 / 定位<br>离队设置</small></button><span class="flow-arrow"></span>
<button class="flow-node menu-node" data-detail="social_entry"><b>右上菜单</b><small>社交入口<br>不含队伍</small></button><span class="flow-arrow"></span>
<button class="flow-node full-node" data-detail="chat"><b>聊天</b><small>综合、私聊<br>队伍、公会</small></button><span class="flow-arrow"></span>
<button class="flow-node full-node" data-detail="friend"><b>好友</b><small>申请、私聊<br>查看资料</small></button><span class="flow-arrow"></span>
<button class="flow-node full-node" data-detail="love"><b>情侣 / 爱意</b><small>关系信息<br>互动操作</small></button><span class="flow-arrow"></span>
<button class="flow-node full-node" data-detail="guild"><b>公会</b><small>成员、技能<br>公告、公会战</small></button>
</div>
</article>
<article class="flow-card" data-flow-group="social">
<div class="flow-card-head"><h3>商店、交易与制作</h3><span class="flow-index">07 · ECONOMY</span></div>
<div class="flow-track">
<button class="flow-node modal-node" data-detail="commerce_entry"><b>NPC / 玩家</b><small>靠近后点击<br>出现交互</small></button><span class="flow-arrow"></span>
<button class="flow-node full-node" data-detail="shop"><b>商店 / 商城</b><small>购买、出售<br>商城物品</small></button><span class="flow-arrow"></span>
<button class="flow-node full-node" data-detail="trade"><b>玩家交易</b><small>拖入物品<br>双方确认</small></button><span class="flow-arrow"></span>
<button class="flow-node full-node" data-detail="private_shop"><b>私人商店</b><small>浏览、购买<br>摆摊管理</small></button><span class="flow-arrow"></span>
<button class="flow-node full-node" data-detail="cube"><b>制作</b><small>放入材料<br>制作确认</small></button><span class="flow-arrow"></span>
<button class="flow-node modal-node" data-detail="refine"><b>精炼 / 龙魂</b><small>强化结果<br>失败确认</small></button>
</div>
</article>
<article class="flow-card" data-flow-group="system">
<div class="flow-card-head"><h3>系统设置与异常状态</h3><span class="flow-index">08 · SYSTEM</span></div>
<div class="flow-track">
<button class="flow-node menu-node" data-detail="system_entry"><b>右上菜单</b><small>系统入口<br>随时可打开</small></button><span class="flow-arrow"></span>
<button class="flow-node full-node" data-detail="options"><b>系统 / 游戏设置</b><small>音量、画面<br>操作、屏蔽</small></button><span class="flow-arrow"></span>
<button class="flow-node full-node" data-detail="help"><b>帮助</b><small>操作说明<br>功能说明</small></button><span class="flow-arrow"></span>
<button class="flow-node modal-node" data-detail="character_switch"><b>选择角色</b><small>离开当前角色<br>二次确认</small></button><span class="flow-arrow"></span>
<button class="flow-node modal-node" data-detail="logout"><b>登出 / 退出</b><small>保存状态<br>确认离开</small></button><span class="flow-arrow"></span>
<button class="flow-node modal-node" data-detail="reconnect"><b>断线重连</b><small>连接恢复<br>重新进入场景</small></button>
</div>
</article>
</div>
<aside class="flow-detail empty" id="flow-detail" aria-live="polite">
<div><div class="detail-kicker">Tap a node</div><p>点击上方任意节点,查看该界面的移动端入口、位置、交互方式和返回路径。</p></div>
</aside>
</section>
<div class="toolbar" id="sketch-tools" aria-label="草图工具">
<span>横屏预览</span><button data-viewport="phone">手机</button><button data-viewport="tablet">平板</button><button data-viewport="desktop">桌面</button>
<select id="theme-switcher" aria-label="主题"><option value="default">暗黑铜金</option></select>
<button id="annotation-toggle">标注</button>
</div>
<div class="toast" id="toast" role="status"></div><div class="annotation-label" id="annotation-label"></div>
<script>
const body = document.body;
const canvas = document.getElementById('sketch-canvas');
const toast = document.getElementById('toast');
let toastTimer;
const featureCatalog = [
['♟', '社交', 'social', '聊天 · 好友 · 公会'],
['✧', '成长', 'growth', '强化 · 制作 · 龙魂'],
['▣', '活动', 'adventure', '副本 · 钓鱼'],
['◈', '商业', 'market', '商店 · 交易 · 仓库'],
['⚙', '系统', 'system', '设置 · 帮助 · 退出']
];
const featureCopy = {
inventory: '<p class="sheet-note">装备 / 腰带 / 物品拖放 · 右侧滑出面板,回到战斗时自动收起。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="拖动红药到快捷栏">♥<span>红药 ×12</span></button><button class="action-item demo-btn" data-toast="查看装备">⚔<span>装备</span></button><button class="action-item demo-btn" data-toast="打开腰带">▤<span>腰带</span></button><button class="action-item demo-btn" data-toast="整理背包">✦<span>整理</span></button></div>',
character: '<p class="sheet-note">头像入口承载 HP / MP、状态、装备、技能、坐骑和表情动作。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="打开状态页">♥<span>状态</span></button><button class="action-item demo-btn" data-toast="查看装备">⚔<span>装备</span></button><button class="action-item demo-btn" data-panel="skills"><b>✦</b><span>技能</span></button><button class="action-item demo-btn" data-panel="mount"><b>♞</b><span>坐骑</span></button><button class="action-item demo-btn" data-panel="emote"><b>☺</b><span>表情</span></button><button class="action-item demo-btn" data-toast="打开称号页">◆<span>称号</span></button></div>',
quest: '<p class="sheet-note">任务追踪使用半透明卡片;点击任务行进入日志,地图信标从卡片右上角进入。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="追踪:采集狼牙">◆<span>追踪</span></button><button class="action-item demo-btn" data-toast="打开任务日志">▤<span>日志</span></button><button class="action-item demo-btn" data-toast="显示地图信标">⌖<span>导航</span></button><button class="action-item demo-btn" data-toast="收起任务条">×<span>隐藏</span></button></div>',
map: '<p class="sheet-note">小地图点击后升起全屏地图,保留副本目的地罗盘和任务信标。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="地图缩放 +"><span>放大</span></button><button class="action-item demo-btn" data-toast="地图缩放 -"><span>缩小</span></button><button class="action-item demo-btn" data-toast="查看世界地图">⌖<span>世界地图</span></button><button class="action-item demo-btn" data-toast="回到玩家位置">●<span>定位</span></button></div>',
target: '<p class="sheet-note">目标板支持切换目标、查看血量和情境动作;按住目标卡可进入更多交互。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="切换目标">⇄<span>切换</span></button><button class="action-item demo-btn" data-toast="开始自动攻击">⚔<span>攻击</span></button><button class="action-item demo-btn" data-toast="打开目标信息">? <span>信息</span></button><button class="action-item demo-btn" data-toast="取消目标">×<span>清除</span></button></div>',
chat: '<div class="chat-list"><div class="chat-line"><strong>[队伍]</strong> Mira:副本入口在北门。</div><div class="chat-line"><strong>[附近]</strong> 你:收到,马上到。</div><div class="chat-line"><strong>[公会]</strong> Ken:今晚 20:00 公会战。</div></div><div class="chat-compose"><input placeholder="输入消息…"><button class="send-btn" data-send>发送</button></div>',
party: '<p class="sheet-note">队伍信息从左上玩家头像下方的队员条进入;未组队时该区域自动隐藏。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="定位队员">⌖<span>定位</span></button><button class="action-item demo-btn" data-toast="查看队员资料">♟<span>资料</span></button><button class="action-item demo-btn" data-toast="打开队伍设置">⚙<span>设置</span></button><button class="action-item demo-btn" data-toast="离开队伍确认">×<span>离队</span></button></div>',
more: '<p class="sheet-note">菜单只保留五个一级入口;角色相关功能点击左上头像进入,背包、小地图和任务仍从首页直接进入。</p><div class="menu-grid">' + featureCatalog.map(([icon, name, key, note]) => `<button class="menu-item demo-btn" data-panel="${key}" data-toast="打开${name}"><b>${icon}</b><span>${name}</span><small>${note}</small></button>`).join('') + '</div>',
guild: '<p class="sheet-note">公会信息、成员、会徽、公会战和公告从左侧社交轨进入。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="打开公会成员">♟<span>成员</span></button><button class="action-item demo-btn" data-toast="查看公会技能">✧<span>技能</span></button><button class="action-item demo-btn" data-toast="查看公会会徽">✦<span>会徽</span></button><button class="action-item demo-btn" data-toast="查看公会战">⚔<span>公会战</span></button></div>'
};
Object.assign(featureCopy, {
social: '<p class="sheet-note">社交菜单只保留聊天、好友、公会、情侣和观战;队伍状态固定在左上玩家头像下方。</p><div class="action-grid"><button class="action-item demo-btn" data-panel="chat"><b>☵</b><span>聊天</span></button><button class="action-item demo-btn" data-panel="friend"><b>♧</b><span>好友</span></button><button class="action-item demo-btn" data-panel="guild"><b>♜</b><span>公会</span></button><button class="action-item demo-btn" data-panel="love"><b>♥</b><span>情侣</span></button><button class="action-item demo-btn" data-panel="observer"><b>◉</b><span>观战</span></button></div>',
growth: '<p class="sheet-note">成长功能统一处理角色长期养成;技能、坐骑和表情仍归入角色页面,避免重复入口。</p><div class="action-grid"><button class="action-item demo-btn" data-panel="refine"><b>⚒</b><span>精炼</span></button><button class="action-item demo-btn" data-panel="dragon"><b>✧</b><span>龙魂</span></button><button class="action-item demo-btn" data-panel="cube"><b>▣</b><span>制作</span></button></div>',
adventure: '<p class="sheet-note">活动通过任务追踪、场景交互或这里进入;地图仍只通过右上小地图打开。</p><div class="action-grid"><button class="action-item demo-btn" data-panel="dungeon"><b>▣</b><span>副本</span></button><button class="action-item demo-btn" data-panel="fishing"><b>♨</b><span>钓鱼</span></button></div>',
market: '<p class="sheet-note">涉及货币与物品流转的功能统一归入商业;背包仍在首页右上角单独打开。</p><div class="action-grid"><button class="action-item demo-btn" data-panel="shop"><b>▤</b><span>商店</span></button><button class="action-item demo-btn" data-panel="mall"><b>◈</b><span>商城</span></button><button class="action-item demo-btn" data-panel="trade"><b>⇄</b><span>交易</span></button><button class="action-item demo-btn" data-panel="private_shop"><b>▥</b><span>私人商店</span></button><button class="action-item demo-btn" data-panel="safebox"><b>▤</b><span>仓库</span></button><button class="action-item demo-btn" data-panel="exchange"><b>↔</b><span>兑换</span></button></div>',
friend: '<p class="sheet-note">好友与申请使用统一列表;选择好友后可私聊、查看资料或邀请组队。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="查看好友申请"><span>申请</span></button><button class="action-item demo-btn" data-toast="打开好友资料">♧<span>资料</span></button><button class="action-item demo-btn" data-toast="发起私聊">☵<span>私聊</span></button><button class="action-item demo-btn" data-toast="邀请组队">♟<span>组队</span></button></div>',
love: '<p class="sheet-note">情侣与爱意信息放在社交分组内,避免成为首页固定入口。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="查看关系信息">♥<span>关系</span></button><button class="action-item demo-btn" data-toast="查看爱意等级">✦<span>等级</span></button><button class="action-item demo-btn" data-toast="发送互动">☺<span>互动</span></button><button class="action-item demo-btn" data-toast="打开情侣设置">⚙<span>设置</span></button></div>',
skills: '<p class="sheet-note">技能分为主动、被动、坐骑和表情动作;战斗中的技能按钮由此处配置。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="打开主动技能">❄<span>主动</span></button><button class="action-item demo-btn" data-toast="打开被动技能">✧<span>被动</span></button><button class="action-item demo-btn" data-toast="打开坐骑技能">♞<span>坐骑</span></button><button class="action-item demo-btn" data-toast="打开表情动作">☺<span>表情</span></button></div>',
shop: '<p class="sheet-note">NPC 商店支持购买和出售;商城使用同一套商品详情与确认交互。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="打开购买列表"><span>购买</span></button><button class="action-item demo-btn" data-toast="打开出售列表"><span>出售</span></button><button class="action-item demo-btn" data-toast="查看商品详情">? <span>详情</span></button><button class="action-item demo-btn" data-toast="确认交易金额">✓<span>确认</span></button></div>',
mall: '<p class="sheet-note">商城从菜单进入,购买前查看价格、限购和获得方式,支付动作使用二次确认。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="浏览商城分类">▤<span>分类</span></button><button class="action-item demo-btn" data-toast="查看商品详情">? <span>详情</span></button><button class="action-item demo-btn" data-toast="加入购物车"><span>加入</span></button><button class="action-item demo-btn" data-toast="确认购买">✓<span>购买</span></button></div>',
safebox: '<p class="sheet-note">仓库采用背包与仓库左右切换;长按物品看详情,拖动完成存入或取出。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="存入仓库">↓<span>存入</span></button><button class="action-item demo-btn" data-toast="取出物品">↑<span>取出</span></button><button class="action-item demo-btn" data-toast="整理仓库">✦<span>整理</span></button><button class="action-item demo-btn" data-toast="切换商城仓库">▤<span>商城仓库</span></button></div>',
trade: '<p class="sheet-note">玩家交易通过点击玩家或交互按钮进入;双方放入物品后分别确认,避免误操作。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="拖入交易物品">⇄<span>放入</span></button><button class="action-item demo-btn" data-toast="查看对方物品">? <span>查看</span></button><button class="action-item demo-btn" data-toast="锁定交易内容">▣<span>锁定</span></button><button class="action-item demo-btn" data-toast="等待对方确认">✓<span>确认</span></button></div>',
private_shop: '<p class="sheet-note">私人商店通过点击摊位或玩家进入;浏览和摆摊管理共用一个全屏面板。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="浏览私人商店">▤<span>浏览</span></button><button class="action-item demo-btn" data-toast="购买摊位物品"><span>购买</span></button><button class="action-item demo-btn" data-toast="打开摆摊管理">⚒<span>管理</span></button><button class="action-item demo-btn" data-toast="关闭私人商店">×<span>关闭</span></button></div>',
exchange: '<p class="sheet-note">兑换界面展示兑换材料、产出和数量;滑动数量后点击兑换并确认。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="选择兑换材料">▥<span>材料</span></button><button class="action-item demo-btn" data-toast="调整兑换数量">↔<span>数量</span></button><button class="action-item demo-btn" data-toast="查看兑换产出">✦<span>产出</span></button><button class="action-item demo-btn" data-toast="确认兑换">✓<span>兑换</span></button></div>',
cube: '<p class="sheet-note">Cube 制作将材料槽放在中央,底部只保留一个主要制作按钮,制作完成后弹出结果。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="选择制作配方">▤<span>配方</span></button><button class="action-item demo-btn" data-toast="放入制作材料"><span>材料</span></button><button class="action-item demo-btn" data-toast="查看制作结果">? <span>预览</span></button><button class="action-item demo-btn" data-toast="确认制作">⚒<span>制作</span></button></div>',
refine: '<p class="sheet-note">精炼和龙魂使用同一套强化确认流程:选择装备、查看成功率和消耗,再确认结果。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="选择强化装备">⚔<span>装备</span></button><button class="action-item demo-btn" data-toast="查看强化材料">▥<span>材料</span></button><button class="action-item demo-btn" data-toast="查看成功率"><span>成功率</span></button><button class="action-item demo-btn" data-toast="确认强化">✧<span>强化</span></button></div>',
dragon: '<p class="sheet-note">龙魂属于制作与强化分支,保留独立页面入口但不放在首页常驻 HUD。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="查看龙魂槽位">✧<span>槽位</span></button><button class="action-item demo-btn" data-toast="选择龙魂">◇<span>选择</span></button><button class="action-item demo-btn" data-toast="查看龙魂属性">? <span>属性</span></button><button class="action-item demo-btn" data-toast="确认镶嵌">✓<span>镶嵌</span></button></div>',
fishing: '<p class="sheet-note">钓鱼是场景情境交互:靠近钓点后出现交互按钮,完成小游戏后返回场景。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="开始钓鱼">♨<span>开始</span></button><button class="action-item demo-btn" data-toast="收杆">↗<span>收杆</span></button><button class="action-item demo-btn" data-toast="查看鱼获">▥<span>鱼获</span></button><button class="action-item demo-btn" data-toast="退出钓鱼">×<span>退出</span></button></div>',
dungeon: '<p class="sheet-note">副本入口通过任务导航或场景交互按钮进入;副本内显示目标罗盘,完成后进入结算。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="查看副本目标">⌖<span>目标</span></button><button class="action-item demo-btn" data-toast="查看队伍状态">♟<span>队伍</span></button><button class="action-item demo-btn" data-toast="退出副本">×<span>退出</span></button><button class="action-item demo-btn" data-toast="开始副本">▣<span>进入</span></button></div>',
mount: '<p class="sheet-note">坐骑从技能页面配置,也可在场景中通过情境按钮召唤和收起。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="召唤坐骑">♞<span>召唤</span></button><button class="action-item demo-btn" data-toast="打开坐骑技能">✧<span>技能</span></button><button class="action-item demo-btn" data-toast="更换坐骑">⇄<span>更换</span></button><button class="action-item demo-btn" data-toast="收起坐骑">×<span>收起</span></button></div>',
observer: '<p class="sheet-note">观战从社交菜单或队员资料进入,观战时隐藏自身战斗操作,保留退出观战按钮。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="选择观战对象">◉<span>对象</span></button><button class="action-item demo-btn" data-toast="切换观战视角">↔<span>视角</span></button><button class="action-item demo-btn" data-toast="查看战斗信息">? <span>信息</span></button><button class="action-item demo-btn" data-toast="退出观战">×<span>退出</span></button></div>',
emote: '<p class="sheet-note">表情动作从技能菜单配置,战斗中不占常驻按钮;选择后在角色头顶显示反馈。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="播放表情:挥手">☺<span>挥手</span></button><button class="action-item demo-btn" data-toast="播放表情:欢呼">✦<span>欢呼</span></button><button class="action-item demo-btn" data-toast="播放动作:坐下">♟<span>动作</span></button><button class="action-item demo-btn" data-toast="设置快捷表情"><span>配置</span></button></div>',
system: '<p class="sheet-note">系统菜单从右上角进入,采用右侧抽屉;设置和帮助进入全屏面板,离开游戏使用确认弹窗。</p><div class="action-grid"><button class="action-item demo-btn" data-panel="options"><b>⚙</b><span>设置</span></button><button class="action-item demo-btn" data-panel="help"><b>?</b><span>帮助</span></button><button class="action-item demo-btn" data-toast="打开角色选择">♜<span>角色</span></button><button class="action-item demo-btn" data-toast="打开退出确认">×<span>退出</span></button></div>',
options: '<p class="sheet-note">设置分为音频、画面、操作和社交屏蔽;滑动条与开关都在全屏面板中完成。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="调整音乐音量">♫<span>音乐</span></button><button class="action-item demo-btn" data-toast="调整音效音量">♪<span>音效</span></button><button class="action-item demo-btn" data-toast="调整镜头距离">◎<span>镜头</span></button><button class="action-item demo-btn" data-toast="保存设置">✓<span>保存</span></button></div>',
help: '<p class="sheet-note">帮助按移动、战斗、任务、社交和系统分类,使用短卡片说明,支持上下滚动。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="查看移动说明">↕<span>移动</span></button><button class="action-item demo-btn" data-toast="查看战斗说明">⚔<span>战斗</span></button><button class="action-item demo-btn" data-toast="查看系统说明">⚙<span>系统</span></button><button class="action-item demo-btn" data-toast="查看快捷操作">✦<span>快捷操作</span></button></div>',
reconnect: '<p class="sheet-note">断线时覆盖场景显示连接状态;恢复成功后自动回到原位置,失败时提供重试或返回角色列表。</p><div class="action-grid"><button class="action-item demo-btn" data-toast="重新连接中">↻<span>重试</span></button><button class="action-item demo-btn" data-toast="查看网络状态">⌁<span>网络</span></button><button class="action-item demo-btn" data-toast="返回角色列表">♜<span>角色列表</span></button><button class="action-item demo-btn" data-toast="关闭提示">×<span>关闭</span></button></div>'
});
const titles = { inventory: '背包', character: '角色', social: '社交', growth: '成长', adventure: '活动', market: '商业', skills: '技能', quest: '任务', map: '地图', party: '社交 / 队伍', friend: '好友', love: '情侣 / 爱意', guild: '公会', target: '目标', chat: '聊天', shop: '商店', mall: '商城', safebox: '仓库', trade: '玩家交易', private_shop: '私人商店', exchange: '兑换', cube: 'Cube 制作', refine: '精炼 / 强化', dragon: '龙魂', fishing: '钓鱼', dungeon: '副本', mount: '坐骑', observer: '观战', emote: '表情动作', system: '系统', help: '帮助', options: '设置', more: '功能菜单' };
const flowDetails = {
login: { kind: '全屏入口', title: '登录界面', entry: '启动游戏后全屏显示;账号密码输入区保持在横屏安全区中央。', touch: '点击输入框唤起键盘,点击登录提交;错误信息在表单下方即时提示。', back: '登录成功进入服务器选择;失败留在当前页并保留已输入账号。', note: '不与战斗 HUD 共存。' },
server: { kind: '全屏入口', title: '服务器 / 频道选择', entry: '登录成功后进入;服务器列表居中,推荐频道置顶。', touch: '点击频道选中,再点击进入;网络状态和人数使用标签显示。', back: '返回登录界面,或进入角色列表。', note: '连接失败直接复用断线重连提示。' },
character_select: { kind: '全屏入口', title: '角色列表与角色管理', entry: '服务器选择后进入;角色卡横向排列。', touch: '点击角色选中,点击创建、删除、改名;删除和改名必须二次确认。', back: '返回服务器选择或进入加载界面。', note: '阵营、职业和等级放在角色卡内,不进入战斗 HUD。' },
loading: { kind: '状态弹窗', title: '加载界面', entry: '角色进入场景、切换地图或副本时覆盖全屏。', touch: '无需操作;加载失败显示重试按钮。', back: '加载完成进入首页 HUD,异常时转断线重连。', note: '避免用户在资源未完成时点击世界。' },
hud: { kind: '常驻 HUD', title: '首页 HUD', entry: '进入场景后的默认页面;世界画面优先。', touch: '左上头像看角色,右上背包/小地图/菜单,任务栏直接进入任务,底部两侧完成移动和战斗。', back: '所有功能面板关闭后回到这里。', note: '名称在头像顶部,HP/MP 条在头像下方且与头像同宽。' },
movement: { kind: '常驻 HUD', title: '移动摇杆', entry: '首页左下角,仅作为战斗操作控件。', touch: '按住并拖动控制方向,松手复位;支持边缘拖动防止拇指滑出。', back: '没有独立页面,松手即返回待机状态。', note: '底部中央保持空白,不放功能菜单。' },
target: { kind: '常驻 HUD', title: '目标选择与目标板', entry: '点击敌人、NPC 或目标板触发;目标板位于顶部中央。', touch: '点击切换目标,长按查看信息;普通攻击和技能自动作用于当前目标。', back: '点击空白清除目标,或选择新目标。', note: '目标血条不与玩家血条混淆。' },
combat_action: { kind: '常驻 HUD', title: '普通攻击与技能', entry: '首页右下角环形战斗盘。', touch: '点击普攻;点击技能释放;长按可预览需要瞄准的技能;冷却用灰度和数字反馈。', back: '释放完成回到战斗状态,技能配置从菜单进入。', note: '药水可作为战斗盘上的物品按钮,不另设底部功能栏。' },
loot: { kind: '场景弹窗', title: '掉落物与情境交互', entry: '靠近掉落物、NPC、钓点或副本入口时,在角色附近出现交互按钮。', touch: '点击拾取、对话、钓鱼或进入;掉落物名称显示在世界中并支持点击。', back: '动作完成回到场景;物品自动进入背包并给出短暂提示。', note: '不在首页固定显示拾取菜单。' },
death: { kind: '场景弹窗', title: '死亡与复活', entry: 'HP 归零后覆盖场景中央。', touch: '点击原地复活、回城或等待;危险选项二次确认。', back: '复活后回到首页 HUD,回城后进入加载流程。', note: '死亡时暂时禁用摇杆和技能按钮。' },
quest: { kind: '常驻 HUD', title: '任务追踪栏', entry: '首页上方常驻显示,不放入右上菜单。', touch: '点击任务行进入日志;点击目标或导航标记进入地图/自动寻路。', back: '关闭任务日志后回到首页,任务进度仍保留。', note: '任务栏是任务系统的唯一首页快捷入口。' },
quest_log: { kind: '全屏面板', title: '任务日志', entry: '点击任务追踪栏打开;采用列表 + 详情布局。', touch: '点击任务切换详情,滚动查看奖励;点击追踪更新首页任务栏。', back: '关闭后返回首页 HUD,不改变当前追踪任务。', note: '任务地图标记从详情页进入。' },
map: { kind: '全屏面板', title: '小地图与大地图', entry: '点击右上小地图进入;没有独立地图按钮。', touch: '双指缩放、拖动地图、点击信标导航,点击定位回到玩家位置。', back: '点击关闭或系统返回回到首页 HUD。', note: '背包已替代原地图快捷按钮。' },
navigation: { kind: '常驻 + 场景', title: '自动寻路', entry: '从任务详情、地图信标或 NPC 目标触发。', touch: '点击导航开始;到达后显示交互按钮;移动摇杆可随时接管。', back: '手动移动、点击停止或到达目的地后结束。', note: '不额外占用常驻 HUD 按钮。' },
npc_quest: { kind: '场景弹窗', title: 'NPC 对话与任务', entry: '靠近 NPC 后点击角色或情境交互按钮。', touch: '点击对话选项;接取/完成任务使用明确的主按钮。', back: '关闭对话回到场景;完成后刷新任务栏和奖励提示。', note: '商店、仓库和任务可以在同一 NPC 对话入口分流。' },
dungeon_result: { kind: '场景弹窗', title: '副本结算', entry: '副本完成或失败后自动弹出。', touch: '滚动查看奖励,点击再次挑战或退出;奖励领取使用主按钮。', back: '退出回到场景,或再次挑战进入加载界面。', note: '副本内保留目标罗盘和队伍状态。' },
character_entry: { kind: '头像入口', title: '角色功能入口', entry: '只点击左上角玩家头像进入。', touch: '点击头像进入角色页,在其中切换属性、装备、技能、坐骑和外观。', back: '关闭角色面板回到首页 HUD。', note: '角色不出现在右上菜单。' },
character_status: { kind: '全屏面板', title: '角色属性', entry: '左上头像 → 角色属性。', touch: '左右切换属性分类,滚动查看等级、属性、经验和资源。', back: '返回角色入口,再返回首页。', note: '战斗中只展示精简状态条。' },
equipment: { kind: '全屏面板', title: '装备界面', entry: '左上头像 → 角色 → 装备。', touch: '点击装备槽查看,长按物品详情,拖动穿戴或替换。', back: '关闭装备页返回角色或首页。', note: '物品对比使用上下对照卡片。' },
skills: { kind: '全屏面板', title: '技能与快捷栏配置', entry: '左上头像 → 角色 → 技能。', touch: '点击技能查看说明,长按查看数值,拖动到首页技能槽。', back: '保存配置后回到首页,技能按钮即时更新。', note: '主动、被动、坐骑、表情分标签显示。' },
skill_upgrade: { kind: '场景弹窗', title: '技能升级 / 加点', entry: '技能详情页点击升级。', touch: '点击加点预览消耗,再点击确认;资源不足显示原因。', back: '确认后刷新技能等级,取消回到技能详情。', note: '升级不可误触,必须二次确认。' },
inspect: { kind: '场景弹窗', title: '查看其他玩家装备', entry: '点击场景玩家后选择查看,或从队伍/好友资料进入。', touch: '点击玩家资料页签查看装备、等级和公会;不可修改对方数据。', back: '关闭资料卡回到场景或社交列表。', note: '交易、私聊、组队使用同一上下文操作栏。' },
inventory: { kind: '全屏面板', title: '背包', entry: '右上背包快捷按钮或菜单 → 背包。', touch: '点击选中,长按详情,拖动整理/装备/放入快捷栏;使用和出售用明确按钮。', back: '关闭返回首页,物品操作结果用 toast 提示。', note: '腰带、装备栏、任务物品作为背包内页。' },
item_tooltip: { kind: '场景弹窗', title: '物品详情提示', entry: '背包、仓库、商店或掉落物点击/长按。', touch: '点击物品查看详情;长按保持详情卡,点击操作按钮执行动作。', back: '点空白或返回关闭详情卡。', note: '详情卡不遮挡关键操作区域。' },
item_action: { kind: '场景弹窗', title: '使用、装备、出售', entry: '物品详情卡底部操作区。', touch: '点击主操作,数量类动作弹出数量选择;出售和稀有物品使用二次确认。', back: '完成后刷新背包,取消回到详情卡。', note: '拖放和点击操作同时支持。' },
safebox: { kind: '全屏面板', title: '仓库与扩展物品栏', entry: '菜单 → 背包 → 仓库,或靠近仓库 NPC 后进入。', touch: '左右切换背包/仓库/商城仓库;拖动物品存入或取出。', back: '关闭面板返回 NPC 对话或首页。', note: '私人商店背包沿用同一物品组件。' },
select_item: { kind: '场景弹窗', title: '任务物品选择', entry: '任务奖励、提交材料或任务详情中的选择按钮。', touch: '点击物品,滑动或加减调整数量,提交前显示所需数量。', back: '确认后回到任务流程,取消不消耗物品。', note: '数量选择不使用难操作的小型输入框。' },
social_entry: { kind: '右上角菜单', title: '社交功能入口', entry: '右上菜单 → 社交;队伍不出现在社交菜单。', touch: '点击聊天、好友、情侣、公会或观战进入对应页面。', back: '关闭社交页面回到首页 HUD。', note: '组队信息固定显示在左上玩家头像下方。' },
chat: { kind: '全屏面板', title: '聊天与战斗日志', entry: '菜单 → 社交 → 聊天;新消息以短暂提示或气泡显示。', touch: '点击频道切换,点击输入框唤起键盘,长按消息进行复制/私聊。', back: '关闭回到首页,未读数显示在菜单角标。', note: '综合、私聊、系统、战斗、队伍、公会统一在频道中。' },
party: { kind: '左侧 HUD', title: '队伍信息', entry: '组队后显示在左上玩家头像下方;未组队时自动隐藏。', touch: '点击队员查看资料或定位,长按打开队伍设置;邀请通过好友或玩家交互触发。', back: '关闭队员卡回到首页,队伍血量和距离继续常驻显示。', note: '队伍不出现在社交菜单,也不设置独立首页按钮。' },
friend: { kind: '全屏面板', title: '好友与好友申请', entry: '菜单 → 社交 → 好友,或点击玩家资料。', touch: '点击申请、同意、私聊、查看资料;列表上下滚动。', back: '返回社交菜单或首页。', note: '好友申请通过临时弹窗提醒。' },
love: { kind: '全屏面板', title: '情侣 / 爱意', entry: '菜单 → 社交 → 情侣 / 爱意。', touch: '点击关系资料和互动动作;关键关系操作使用确认。', back: '关闭返回社交或首页。', note: '低频关系信息不占用首页 HUD。' },
guild: { kind: '全屏面板', title: '公会', entry: '菜单 → 社交 → 公会。', touch: '顶部标签切换成员、技能、公告、公会战和会徽;列表支持滚动。', back: '返回社交菜单或首页。', note: '公会邀请通过临时确认弹窗进入。' },
commerce_entry: { kind: '场景弹窗', title: 'NPC / 玩家情境入口', entry: '靠近 NPC、玩家、摊位或制作设施后显示交互按钮。', touch: '点击交互按钮,从上下文菜单选择对话、商店、交易、仓库或制作。', back: '关闭上下文菜单返回场景。', note: '只在附近有可交互对象时出现。' },
shop: { kind: '全屏面板', title: 'NPC 商店与商城', entry: 'NPC 交互 → 商店,或菜单 → 商店 / 商城。', touch: '点击商品查看详情,数量选择后购买/出售;支付前二次确认。', back: '返回 NPC 对话或首页。', note: '商城使用同一套物品详情和确认组件。' },
trade: { kind: '全屏面板', title: '玩家交易', entry: '点击玩家 → 交易,或场景交互菜单进入。', touch: '拖动物品到交易栏,双方锁定后分别确认;内容变化时重新确认。', back: '取消交易返回场景,已锁定物品自动退回。', note: '交易金额和物品变化必须明显反馈。' },
private_shop: { kind: '全屏面板', title: '私人商店', entry: '点击玩家摊位进入浏览;自己的摊位从菜单管理。', touch: '点击商品购买,管理页拖动物品上架;上架/下架有确认提示。', back: '关闭返回场景,摊位状态保留。', note: '使用背包的物品组件,减少学习成本。' },
cube: { kind: '全屏面板', title: 'Cube 制作', entry: '菜单 → 交易与制作 → Cube,或靠近制作设施。', touch: '选择配方,拖入材料,查看产出预览,点击制作。', back: '结果弹窗关闭后回到制作页或首页。', note: '制作按钮放在面板底部,不占用首页底部。' },
refine: { kind: '全屏 + 弹窗', title: '精炼 / 强化 / 龙魂', entry: '菜单 → 交易与制作 → 精炼,龙魂作为同组页签。', touch: '选择装备、查看材料和成功率,确认强化;结果用中央弹窗反馈。', back: '返回制作菜单或首页,失败也明确显示消耗。', note: '龙魂当前代码入口需单独开启后再接入正式菜单。' },
system_entry: { kind: '右上角菜单', title: '系统入口', entry: '右上角菜单最外侧按钮。', touch: '点击菜单打开右侧抽屉,点击遮罩或关闭按钮收起。', back: '回到首页 HUD,世界输入恢复。', note: '菜单打开时暂停战斗点击,防止误操作。' },
options: { kind: '全屏面板', title: '系统设置与游戏设置', entry: '菜单 → 系统 → 设置。', touch: '滑动音量、镜头距离等,点击开关控制 PK、名称、伤害数字和屏蔽项。', back: '即时保存后返回菜单或首页。', note: '设置项按音频、画面、操作、社交分组。' },
help: { kind: '全屏面板', title: '帮助界面', entry: '菜单 → 系统 → 帮助。', touch: '点击分类卡片,上下滚动查看说明;可从快捷入口直接跳转。', back: '返回系统菜单或首页。', note: '帮助文案与实际移动端手势保持一致。' },
character_switch: { kind: '全屏 + 弹窗', title: '选择角色', entry: '菜单 → 系统 → 选择角色。', touch: '点击后先保存并弹出确认,再回到角色列表。', back: '取消确认继续当前角色。', note: '切换角色触发加载流程。' },
logout: { kind: '场景弹窗', title: '登出 / 退出游戏', entry: '菜单 → 系统 → 登出或退出。', touch: '点击后弹出确认,显示未保存状态;确认后返回登录或关闭游戏。', back: '取消后回到系统菜单。', note: '属于高风险操作,必须二次确认。' },
reconnect: { kind: '状态弹窗', title: '断线重连', entry: '网络中断时覆盖当前页面,不清除当前 HUD 状态。', touch: '自动重试;用户可点击重试、查看网络或返回角色列表。', back: '连接成功恢复原场景,失败则进入角色列表。', note: '恢复后保留任务追踪、目标和队伍状态。' }
};
function showToast(message) {
toast.textContent = message;
toast.classList.add('show');
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toast.classList.remove('show'), 1400);
}
function currentVariantRoot(el) { return el.closest('.variant'); }
function renderPanel(root, key, history = []) {
const modal = root.querySelector('[data-modal]');
const title = root.querySelector('[data-sheet-title]');
const bodyNode = root.querySelector('[data-sheet-body]');
const back = root.querySelector('[data-back]');
title.textContent = titles[key] || key;
bodyNode.innerHTML = featureCopy[key] || `<p class="sheet-note">${titles[key] || key} 面板草图。</p>`;
modal.dataset.current = key;
modal.dataset.history = JSON.stringify(history);
back.classList.toggle('hidden', history.length === 0);
bindDynamic(bodyNode);
}
function openPanel(trigger, key) {
const root = currentVariantRoot(trigger);
const modal = root.querySelector('[data-modal]');
const history = modal.classList.contains('open') && modal.dataset.current ? JSON.parse(modal.dataset.history || '[]') : [];
if (modal.classList.contains('open') && modal.dataset.current && modal.dataset.current !== key) history.push(modal.dataset.current);
renderPanel(root, key, history);
modal.classList.add('open');
}
function closePanel(trigger) {
const modal = trigger.closest('[data-modal]');
modal.classList.remove('open');
modal.dataset.current = '';
modal.dataset.history = '[]';
}
function backPanel(trigger) {
const modal = trigger.closest('[data-modal]');
const history = JSON.parse(modal.dataset.history || '[]');
const key = history.pop();
if (!key) return;
renderPanel(trigger.closest('.variant'), key, history);
}
function bindDynamic(scope = document) {
scope.querySelectorAll('[data-toast]').forEach(btn => {
if (btn.dataset.bound) return;
btn.dataset.bound = '1';
btn.addEventListener('click', () => showToast(btn.dataset.toast));
});
const send = scope.querySelector('[data-send]');
if (send && !send.dataset.bound) {
send.dataset.bound = '1';
send.addEventListener('click', () => {
const input = scope.querySelector('input');
if (!input.value.trim()) { showToast('请输入聊天内容'); return; }
showToast('消息已发送'); input.value = '';
});
}
}
const flowDetail = document.getElementById('flow-detail');
function renderFlowDetail(key) {
const detail = flowDetails[key];
if (!detail) return;
flowDetail.classList.remove('empty');
flowDetail.innerHTML = `<div class="detail-kicker">${detail.kind}</div><h3>${detail.title}</h3><div class="detail-grid"><div class="detail-block"><strong>入口 / 所在位置</strong><p>${detail.entry}</p></div><div class="detail-block"><strong>移动端触控</strong><p>${detail.touch}</p></div><div class="detail-block"><strong>返回路径</strong><p>${detail.back}</p></div></div><p class="flow-note">设计备注:${detail.note}</p>`;
flowDetail.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
document.querySelectorAll('[data-detail]').forEach(node => node.addEventListener('click', () => renderFlowDetail(node.dataset.detail)));
document.querySelectorAll('[data-flow-filter]').forEach(filter => filter.addEventListener('click', () => {
const value = filter.dataset.flowFilter;
document.querySelectorAll('[data-flow-filter]').forEach(item => {
const active = item === filter;
item.classList.toggle('active', active);
item.setAttribute('aria-selected', active ? 'true' : 'false');
});
document.querySelectorAll('.flow-card').forEach(card => card.classList.toggle('is-hidden', value !== 'all' && card.dataset.flowGroup !== value));
showToast(value === 'all' ? '显示全部交互流程' : '已筛选交互流程');
}));
document.addEventListener('click', event => {
const btn = event.target.closest('.demo-btn');
if (btn) {
if (btn.dataset.panel) openPanel(btn, btn.dataset.panel);
if (btn.dataset.skill) {
showToast(btn.dataset.toast || ('触发:' + btn.dataset.skill));
if (btn.dataset.skill !== '普攻') {
btn.classList.add('cooling');
const badge = btn.querySelector('em');
if (badge) badge.textContent = '3';
setTimeout(() => { btn.classList.remove('cooling'); if (badge) badge.textContent = ''; }, 900);
}
}
}
const close = event.target.closest('[data-close]');
if (close) closePanel(close);
const back = event.target.closest('[data-back]');
if (back) backPanel(back);
if (event.target.matches('[data-modal]')) closePanel(event.target.querySelector('[data-close]'));
});
document.querySelectorAll('[data-viewport]').forEach(btn => btn.addEventListener('click', () => {
canvas.className = 'preview-' + btn.dataset.viewport;
showToast('预览尺寸:' + btn.textContent);
}));
document.getElementById('annotation-toggle').addEventListener('click', event => {
body.classList.toggle('annotate');
event.currentTarget.textContent = body.classList.contains('annotate') ? '关闭标注' : '标注';
showToast(body.classList.contains('annotate') ? '标注模式已开启' : '标注模式已关闭');
});
const annotation = document.getElementById('annotation-label');
document.addEventListener('mousemove', event => {
if (!body.classList.contains('annotate')) return;
const el = event.target.closest('[data-label]');
if (!el) { annotation.style.display = 'none'; return; }
annotation.textContent = el.dataset.label;
annotation.style.left = Math.min(window.innerWidth - 180, event.clientX + 12) + 'px';
annotation.style.top = Math.min(window.innerHeight - 40, event.clientY + 12) + 'px';
annotation.style.display = 'block';
});
document.querySelectorAll('[data-joystick]').forEach(joystick => {
let active = false;
const stick = joystick.querySelector('.stick');
const move = event => {
if (!active) return;
const rect = joystick.getBoundingClientRect();
const x = (event.clientX ?? event.touches?.[0]?.clientX) - (rect.left + rect.width / 2);
const y = (event.clientY ?? event.touches?.[0]?.clientY) - (rect.top + rect.height / 2);
const limit = 26, length = Math.max(1, Math.hypot(x, y)), scale = Math.min(1, limit / length);
stick.style.transform = `translate(calc(-50% + ${x * scale}px), calc(-50% + ${y * scale}px))`;
};
const release = () => { active = false; joystick.classList.remove('is-active'); stick.style.transform = 'translate(-50%, -50%)'; showToast('移动摇杆已复位'); };
joystick.addEventListener('pointerdown', event => { active = true; joystick.classList.add('is-active'); joystick.setPointerCapture(event.pointerId); move(event); });
joystick.addEventListener('pointermove', move); joystick.addEventListener('pointerup', release); joystick.addEventListener('pointercancel', release);
});
bindDynamic();
</script>
</body>
</html>