Files
pokopiawiki.tootaio.com/DesignGuidelines.html
xiaomai b39e37ca28 feat(ui): overhaul frontend design system and layout
Introduce reusable UI components (AppShell, EntityCard, PageHeader)
Implement Pokemon-themed CSS variables and responsive grids
Refactor all views to adopt the new component structure
2026-04-30 13:52:44 +08:00

4143 lines
141 KiB
HTML
Raw Permalink 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>Pokémon UI Library Design Guidelines v3</title>
<meta name="description" content="面向已获授权 Pokémon 项目的完整 UI 库设计规范,覆盖品牌元素、视觉令牌、控件、导航、反馈、数据展示、专属产品组件与页面模板。" />
<style>
:root {
color-scheme: light;
--pokemon-yellow: #ffcb05;
--pokemon-yellow-2: #ffe46b;
--pokemon-blue: #2a75bb;
--pokemon-blue-deep: #003a70;
--pokemon-red: #ee1515;
--pokemon-red-deep: #cc0000;
--pokeball-black: #202124;
--pokeball-white: #f7f8fb;
--type-normal: #a8a77a;
--type-fire: #ee8130;
--type-water: #6390f0;
--type-electric: #f7d02c;
--type-grass: #7ac74c;
--type-ice: #96d9d6;
--type-fighting: #c22e28;
--type-poison: #a33ea1;
--type-ground: #e2bf65;
--type-flying: #a98ff3;
--type-psychic: #f95587;
--type-bug: #a6b91a;
--type-rock: #b6a136;
--type-ghost: #735797;
--type-dragon: #6f35fc;
--type-dark: #705746;
--type-steel: #b7b7ce;
--type-fairy: #d685ad;
--type-stellar: #35c4f0;
--bg: #f2f5fa;
--bg-alt: #eaf1fb;
--surface: #ffffff;
--surface-raised: #ffffff;
--surface-soft: #f8fafd;
--ink: #151923;
--ink-soft: #354052;
--muted: #687487;
--line: #d8deea;
--line-strong: #1f2a3b;
--focus: #0b63ce;
--success: #2eb872;
--warning: #ffb800;
--danger: #df2f2f;
--radius-card: 8px;
--radius-control: 8px;
--radius-small: 6px;
--shadow-raised: 0 14px 32px rgba(23, 35, 54, .13);
--shadow-control: 0 3px 0 var(--line-strong);
--shadow-soft: 0 8px 22px rgba(23, 35, 54, .09);
--container: 1240px;
--font-sans: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
--font-display: "Arial Rounded MT Bold", "Nunito", "Avenir Next Rounded", var(--font-sans);
--font-mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
}
[data-theme="night"] {
color-scheme: dark;
--bg: #101722;
--bg-alt: #151f2e;
--surface: #182233;
--surface-raised: #1d2a3f;
--surface-soft: #121c2c;
--ink: #f5f8ff;
--ink-soft: #dce6f5;
--muted: #a8b5c7;
--line: #334158;
--line-strong: #f5f8ff;
--shadow-raised: 0 16px 36px rgba(0, 0, 0, .34);
--shadow-soft: 0 8px 24px rgba(0, 0, 0, .28);
}
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
min-width: 320px;
color: var(--ink);
font-family: var(--font-sans);
line-height: 1.6;
background:
linear-gradient(90deg, rgba(42, 117, 187, .08) 1px, transparent 1px) 0 0 / 32px 32px,
linear-gradient(rgba(42, 117, 187, .08) 1px, transparent 1px) 0 0 / 32px 32px,
linear-gradient(180deg, var(--bg) 0%, var(--bg-alt) 100%);
overflow-x: hidden;
}
body.lock-scroll {
overflow: hidden;
}
a {
color: inherit;
text-decoration: none;
}
button,
input,
select,
textarea {
font: inherit;
}
button {
border: 0;
}
code {
padding: 2px 6px;
border: 1px solid var(--line);
border-radius: 6px;
background: var(--surface-soft);
color: var(--pokemon-blue-deep);
font-family: var(--font-mono);
font-size: .88em;
}
[data-theme="night"] code {
color: var(--pokemon-yellow);
}
.sr-only {
position: absolute !important;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
img,
svg {
display: block;
max-width: 100%;
}
:focus-visible {
outline: 3px solid var(--focus);
outline-offset: 3px;
}
.container {
width: min(100%, var(--container));
margin: 0 auto;
padding: 0 24px;
}
.site-header {
position: sticky;
top: 0;
z-index: 50;
border-bottom: 1px solid rgba(31, 42, 59, .12);
background: color-mix(in srgb, var(--surface) 88%, transparent);
backdrop-filter: blur(18px);
}
.top-nav {
min-height: 70px;
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 22px;
}
.brand-lockup {
display: inline-flex;
align-items: center;
gap: 12px;
min-width: 220px;
}
.pokemon-word {
display: inline-block;
color: var(--pokemon-yellow);
font-family: var(--font-display);
font-size: 1.75rem;
font-weight: 900;
line-height: .9;
-webkit-text-stroke: 2px var(--pokemon-blue-deep);
text-shadow: 2px 3px 0 var(--pokemon-blue);
}
.brand-subtitle {
display: block;
margin-top: 2px;
color: var(--muted);
font-size: .74rem;
font-weight: 800;
text-transform: uppercase;
}
.nav-links {
display: flex;
justify-content: center;
gap: 4px;
flex-wrap: wrap;
}
.nav-links a {
min-height: 38px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 8px 10px;
border-radius: var(--radius-control);
color: var(--ink-soft);
font-size: .9rem;
font-weight: 800;
}
.nav-links a:hover {
background: rgba(255, 203, 5, .22);
color: var(--ink);
}
.nav-actions {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 8px;
}
.pokeball {
position: relative;
width: var(--ball-size, 44px);
height: var(--ball-size, 44px);
flex: 0 0 auto;
border: calc(var(--ball-size, 44px) * .07) solid var(--pokeball-black);
border-radius: 50%;
background:
linear-gradient(to bottom, var(--pokemon-red) 0 45%, var(--pokeball-black) 45% 55%, var(--pokeball-white) 55% 100%);
box-shadow: inset 0 4px 0 rgba(255,255,255,.45), 0 3px 0 rgba(0,0,0,.18);
}
.pokeball::after {
content: "";
position: absolute;
inset: 50% auto auto 50%;
width: calc(var(--ball-size, 44px) * .34);
height: calc(var(--ball-size, 44px) * .34);
transform: translate(-50%, -50%);
border: calc(var(--ball-size, 44px) * .055) solid var(--pokeball-black);
border-radius: 50%;
background: var(--pokeball-white);
box-shadow: inset 0 0 0 calc(var(--ball-size, 44px) * .055) #dfe5ef;
}
.section {
padding: 58px 0;
}
.section.band {
background: color-mix(in srgb, var(--surface) 72%, transparent);
border-top: 1px solid rgba(31, 42, 59, .08);
border-bottom: 1px solid rgba(31, 42, 59, .08);
}
.section-header {
display: grid;
gap: 10px;
max-width: 860px;
margin-bottom: 24px;
}
.section-kicker {
display: inline-flex;
align-items: center;
gap: 8px;
width: fit-content;
color: var(--pokemon-blue);
font-size: .82rem;
font-weight: 900;
text-transform: uppercase;
}
.section-kicker::before {
content: "";
width: 18px;
height: 18px;
border: 3px solid var(--line-strong);
border-radius: 50%;
background:
linear-gradient(to bottom, var(--pokemon-red) 0 44%, var(--line-strong) 44% 56%, var(--surface) 56% 100%);
}
h1,
h2,
h3,
h4 {
margin: 0;
font-family: var(--font-display);
line-height: 1.08;
}
h1 {
max-width: 820px;
font-size: 4.9rem;
font-weight: 950;
}
h2 {
font-size: 2.85rem;
font-weight: 950;
}
h3 {
font-size: 1.35rem;
font-weight: 950;
}
h4 {
font-size: 1rem;
font-weight: 950;
}
p {
margin: 0;
}
.lead {
max-width: 760px;
color: var(--ink-soft);
font-size: 1.08rem;
}
.muted {
color: var(--muted);
}
.hero {
min-height: calc(100vh - 70px);
display: grid;
grid-template-columns: minmax(0, 1fr) 460px;
align-items: center;
gap: 44px;
padding-top: 34px;
padding-bottom: 38px;
}
.hero-copy {
margin-top: 20px;
color: var(--ink-soft);
font-size: 1.18rem;
}
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 26px;
}
.quick-index {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 10px;
margin-top: 30px;
max-width: 720px;
}
.quick-index a {
min-height: 74px;
display: grid;
align-content: center;
gap: 2px;
padding: 12px;
border: 1px solid var(--line);
border-radius: var(--radius-card);
background: var(--surface);
box-shadow: var(--shadow-soft);
transition: transform .16s ease, border-color .16s ease;
}
.quick-index a:hover {
transform: translateY(-2px);
border-color: var(--pokemon-blue);
}
.quick-index strong {
font-family: var(--font-display);
line-height: 1.1;
}
.quick-index span {
color: var(--muted);
font-size: .82rem;
}
.pokedex-shell {
position: relative;
border: 4px solid #7b0f16;
border-radius: var(--radius-card);
background:
linear-gradient(90deg, rgba(255,255,255,.15) 0 18%, transparent 18% 100%),
linear-gradient(180deg, #f43c3c 0%, #d81724 100%);
box-shadow: 0 9px 0 #7b0f16, var(--shadow-raised);
overflow: hidden;
}
.pokedex-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
padding: 16px 18px;
border-bottom: 4px solid #7b0f16;
}
.pokedex-lights {
display: flex;
align-items: center;
gap: 8px;
}
.lens {
width: 44px;
height: 44px;
border: 4px solid #ffffff;
border-radius: 50%;
background:
radial-gradient(circle at 32% 28%, #ffffff 0 12%, transparent 14%),
#45b8ff;
box-shadow: 0 0 0 3px #172036, inset 0 -8px 0 rgba(0,0,0,.18);
}
.signal {
width: 15px;
height: 15px;
border: 2px solid #172036;
border-radius: 50%;
box-shadow: inset 0 2px 0 rgba(255,255,255,.42);
}
.signal.red {
background: #ff3f4a;
}
.signal.yellow {
background: var(--pokemon-yellow);
}
.signal.green {
background: #38d678;
}
.pokedex-code {
padding: 7px 10px;
border: 2px solid #172036;
border-radius: var(--radius-small);
background: #172036;
color: #ffffff;
font-family: var(--font-mono);
font-size: .82rem;
font-weight: 800;
}
.pokedex-body {
padding: 18px;
}
.pokedex-screen {
min-height: 456px;
padding: 16px;
border: 4px solid #172036;
border-radius: var(--radius-card);
background:
linear-gradient(90deg, rgba(42,117,187,.08) 1px, transparent 1px) 0 0 / 18px 18px,
linear-gradient(rgba(42,117,187,.08) 1px, transparent 1px) 0 0 / 18px 18px,
#eef9ff;
color: #172036;
}
.screen-topline {
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
margin-bottom: 14px;
}
.dex-number {
display: inline-flex;
align-items: center;
min-height: 30px;
padding: 4px 9px;
border: 2px solid #172036;
border-radius: var(--radius-small);
background: #172036;
color: #ffffff;
font-family: var(--font-mono);
font-size: .78rem;
font-weight: 900;
}
.sprite-stage {
min-height: 184px;
display: grid;
place-items: center;
margin-bottom: 12px;
border: 2px solid rgba(23,32,54,.18);
border-radius: var(--radius-card);
background:
linear-gradient(135deg, rgba(255,203,5,.24), rgba(42,117,187,.12)),
#ffffff;
}
.pikachu-face {
position: relative;
width: 138px;
height: 122px;
border: 4px solid #4a3216;
border-radius: 50% 50% 44% 44%;
background:
radial-gradient(circle at 32% 48%, #281b12 0 7px, transparent 8px),
radial-gradient(circle at 68% 48%, #281b12 0 7px, transparent 8px),
radial-gradient(circle at 21% 66%, #ee1515 0 13px, transparent 14px),
radial-gradient(circle at 79% 66%, #ee1515 0 13px, transparent 14px),
radial-gradient(ellipse at 50% 66%, #4a3216 0 4px, transparent 5px),
var(--pokemon-yellow);
box-shadow: inset 0 8px 0 rgba(255,255,255,.28), 0 8px 0 rgba(0,0,0,.12);
}
.pikachu-face::before,
.pikachu-face::after {
content: "";
position: absolute;
top: -66px;
width: 38px;
height: 92px;
border: 4px solid #4a3216;
border-bottom: 0;
border-radius: 70% 70% 18% 18%;
background:
linear-gradient(to bottom, #4a3216 0 31%, var(--pokemon-yellow) 32% 100%);
transform-origin: bottom center;
z-index: -1;
}
.pikachu-face::before {
left: 16px;
transform: rotate(-24deg);
}
.pikachu-face::after {
right: 16px;
transform: rotate(24deg);
}
.dex-title-row {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
}
.dex-title-row h3 {
color: #172036;
font-size: 1.55rem;
}
.stat-list {
display: grid;
gap: 9px;
margin-top: 14px;
}
.stat-row {
display: grid;
grid-template-columns: 44px 1fr 36px;
align-items: center;
gap: 8px;
font-size: .78rem;
font-weight: 900;
text-transform: uppercase;
}
.stat-track {
height: 12px;
overflow: hidden;
border: 2px solid #172036;
border-radius: 999px;
background: #ffffff;
}
.stat-fill {
display: block;
height: 100%;
border-radius: inherit;
background: var(--pokemon-blue);
}
.pokedex-controls {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: 12px;
margin-top: 16px;
}
.dpad {
width: 74px;
height: 74px;
position: relative;
}
.dpad::before,
.dpad::after {
content: "";
position: absolute;
inset: 50% auto auto 50%;
transform: translate(-50%, -50%);
border-radius: 7px;
background: #172036;
}
.dpad::before {
width: 24px;
height: 74px;
}
.dpad::after {
width: 74px;
height: 24px;
}
.screen-buttons {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.btn {
--btn-bg: var(--surface);
--btn-fg: var(--ink);
--btn-border: var(--line-strong);
min-height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 10px 14px;
border: 2px solid var(--btn-border);
border-radius: var(--radius-control);
background: var(--btn-bg);
color: var(--btn-fg);
box-shadow: var(--shadow-control);
font-weight: 900;
line-height: 1.1;
cursor: pointer;
transition: transform .14s ease, box-shadow .14s ease, background .14s ease, border-color .14s ease;
white-space: nowrap;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 0 var(--line-strong);
}
.btn:active,
.btn.is-active {
transform: translateY(2px);
box-shadow: 0 1px 0 var(--line-strong);
}
.btn.primary {
--btn-bg: var(--pokemon-yellow);
--btn-fg: #172036;
}
.btn.blue {
--btn-bg: var(--pokemon-blue);
--btn-fg: #ffffff;
}
.btn.red {
--btn-bg: var(--pokemon-red);
--btn-fg: #ffffff;
}
.btn.ghost {
--btn-bg: transparent;
--btn-border: var(--line);
box-shadow: none;
}
.btn.small {
min-height: 36px;
padding: 7px 10px;
font-size: .86rem;
box-shadow: 0 2px 0 var(--line-strong);
}
.btn.large {
min-height: 54px;
padding: 13px 18px;
font-size: 1.05rem;
}
.btn:disabled,
.btn.disabled {
opacity: .48;
transform: none;
box-shadow: 0 2px 0 var(--line);
cursor: not-allowed;
}
.icon {
width: 1.05em;
height: 1.05em;
display: inline-grid;
place-items: center;
flex: 0 0 auto;
font-weight: 950;
line-height: 1;
}
.icon-btn {
width: 44px;
min-width: 44px;
padding: 0;
}
.grid {
display: grid;
gap: 16px;
}
.grid.two {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.grid.three {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.grid.four {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.grid.six {
grid-template-columns: repeat(6, minmax(0, 1fr));
}
.card {
border: 1px solid var(--line);
border-radius: var(--radius-card);
background: var(--surface);
box-shadow: var(--shadow-soft);
overflow: hidden;
}
.card.padded {
padding: 18px;
}
.card.strong {
border: 2px solid var(--line-strong);
box-shadow: var(--shadow-control);
}
.card-title {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
}
.card-title h3,
.card-title h4 {
min-width: 0;
}
.rule-list {
margin: 0;
padding: 0;
list-style: none;
display: grid;
gap: 10px;
}
.rule-list li {
position: relative;
padding-left: 28px;
color: var(--ink-soft);
}
.rule-list li::before {
content: "";
position: absolute;
left: 0;
top: .32em;
width: 16px;
height: 16px;
border: 2px solid var(--line-strong);
border-radius: 50%;
background:
linear-gradient(to bottom, var(--pokemon-red) 0 45%, var(--line-strong) 45% 55%, var(--surface) 55% 100%);
}
.token-swatch {
min-height: 148px;
display: grid;
align-content: space-between;
gap: 16px;
padding: 14px;
border: 1px solid rgba(31, 42, 59, .22);
border-radius: var(--radius-card);
color: #ffffff;
background: var(--swatch);
box-shadow: var(--shadow-soft);
}
.token-swatch.light-text {
color: #172036;
}
.token-swatch strong {
font-family: var(--font-display);
font-size: 1.15rem;
line-height: 1.1;
}
.token-swatch span {
opacity: .92;
font-size: .86rem;
}
.token-swatch code {
width: fit-content;
border-color: rgba(31,42,59,.2);
background: rgba(255,255,255,.86);
color: #172036;
}
.type-grid {
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 10px;
}
.type-chip {
--type-bg: var(--type-normal);
min-height: 32px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 7px;
padding: 6px 10px;
border: 1px solid rgba(0,0,0,.18);
border-radius: 999px;
background: var(--type-bg);
color: #ffffff;
font-size: .78rem;
font-weight: 950;
line-height: 1;
text-shadow: 0 1px 0 rgba(0,0,0,.22);
text-transform: uppercase;
white-space: nowrap;
}
.type-chip::before {
content: "";
width: 9px;
height: 9px;
border: 2px solid rgba(255,255,255,.85);
border-radius: 50%;
background: rgba(255,255,255,.24);
flex: 0 0 auto;
}
.type-chip.type-image-chip {
--type-badge-height: 20px;
width: auto;
max-width: 100%;
height: var(--type-badge-height);
min-height: 0;
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
color: inherit;
line-height: 0;
text-shadow: none;
justify-self: start;
}
.type-chip.type-image-chip::before {
content: none;
display: none;
}
.type-chip.type-image-chip img {
width: auto;
max-width: 100%;
height: 100%;
object-fit: contain;
filter: drop-shadow(0 1px 0 rgba(31, 42, 59, .18));
}
.type-grid .type-image-chip {
--type-badge-height: 25px;
justify-self: center;
}
.data-table .type-image-chip,
.dex-mini-card .type-image-chip {
--type-badge-height: 28px;
}
.screen-topline .type-image-chip,
.pokemon-card .type-image-chip,
.suggestion-item .type-image-chip,
.type-toggle .type-image-chip,
.evolution-node .type-image-chip,
.move-card .type-image-chip,
.weakness-cell .type-image-chip,
.team-slot .type-image-chip,
.event-card .type-image-chip {
--type-badge-height: 30px;
}
.type-toggle .type-image-chip,
.weakness-cell .type-image-chip {
justify-self: center;
}
.type-toggle .type-image-chip,
.suggestion-item .type-image-chip,
.weakness-cell .type-image-chip,
.team-slot .type-image-chip,
.event-card .type-image-chip,
.move-card .type-image-chip {
width: var(--type-badge-height);
}
.type-toggle .type-image-chip img,
.suggestion-item .type-image-chip img,
.weakness-cell .type-image-chip img,
.team-slot .type-image-chip img,
.event-card .type-image-chip img,
.move-card .type-image-chip img {
width: 100%;
height: 100%;
}
.small-type-grid {
display: grid;
grid-template-columns: repeat(9, minmax(0, 1fr));
gap: 10px;
margin-top: 14px;
}
.small-type-grid .type-image-chip {
--type-badge-height: 34px;
justify-self: center;
}
.type-normal { --type-bg: var(--type-normal); }
.type-fire { --type-bg: var(--type-fire); }
.type-water { --type-bg: var(--type-water); }
.type-electric { --type-bg: var(--type-electric); color: #172036; text-shadow: none; }
.type-grass { --type-bg: var(--type-grass); }
.type-ice { --type-bg: var(--type-ice); color: #172036; text-shadow: none; }
.type-fighting { --type-bg: var(--type-fighting); }
.type-poison { --type-bg: var(--type-poison); }
.type-ground { --type-bg: var(--type-ground); color: #172036; text-shadow: none; }
.type-flying { --type-bg: var(--type-flying); }
.type-psychic { --type-bg: var(--type-psychic); }
.type-bug { --type-bg: var(--type-bug); color: #172036; text-shadow: none; }
.type-rock { --type-bg: var(--type-rock); }
.type-ghost { --type-bg: var(--type-ghost); }
.type-dragon { --type-bg: var(--type-dragon); }
.type-dark { --type-bg: var(--type-dark); }
.type-steel { --type-bg: var(--type-steel); color: #172036; text-shadow: none; }
.type-fairy { --type-bg: var(--type-fairy); color: #172036; text-shadow: none; }
.type-stellar { --type-bg: var(--type-stellar); color: #172036; text-shadow: none; }
.foundation-card {
min-height: 196px;
display: grid;
gap: 12px;
align-content: start;
}
.number-mark {
width: 42px;
height: 42px;
display: inline-grid;
place-items: center;
border: 2px solid var(--line-strong);
border-radius: var(--radius-control);
background: var(--pokemon-yellow);
box-shadow: 0 3px 0 var(--line-strong);
color: #172036;
font-family: var(--font-display);
font-weight: 950;
}
.asset-showcase {
display: grid;
grid-template-columns: 1.1fr .9fr;
gap: 18px;
align-items: stretch;
}
.brand-panel {
min-height: 320px;
display: grid;
align-content: center;
justify-items: center;
gap: 18px;
padding: 28px;
border: 2px solid var(--line-strong);
border-radius: var(--radius-card);
background:
linear-gradient(90deg, rgba(31,42,59,.08) 1px, transparent 1px) 0 0 / 28px 28px,
linear-gradient(rgba(31,42,59,.08) 1px, transparent 1px) 0 0 / 28px 28px,
var(--surface);
box-shadow: var(--shadow-control);
}
.brand-panel .pokemon-word {
font-size: 4rem;
-webkit-text-stroke-width: 3px;
text-shadow: 3px 5px 0 var(--pokemon-blue);
}
.asset-mini-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.asset-tile {
min-height: 154px;
display: grid;
place-items: center;
gap: 10px;
padding: 16px;
border: 1px solid var(--line);
border-radius: var(--radius-card);
background: var(--surface);
text-align: center;
}
.asset-tile strong {
font-family: var(--font-display);
}
.gym-badge {
width: 68px;
height: 68px;
display: grid;
place-items: center;
border: 3px solid var(--line-strong);
background: var(--pokemon-blue);
color: #ffffff;
box-shadow: 0 4px 0 var(--line-strong);
clip-path: polygon(50% 0, 64% 23%, 91% 19%, 80% 45%, 100% 63%, 71% 67%, 69% 96%, 50% 75%, 31% 96%, 29% 67%, 0 63%, 20% 45%, 9% 19%, 36% 23%);
font-family: var(--font-display);
font-size: 1.3rem;
font-weight: 950;
}
.trainer-pass {
display: grid;
grid-template-columns: 82px 1fr;
gap: 14px;
align-items: center;
width: min(100%, 360px);
padding: 14px;
border: 2px solid var(--line-strong);
border-radius: var(--radius-card);
background:
linear-gradient(90deg, var(--pokemon-blue) 0 26%, transparent 26% 100%),
var(--pokemon-yellow);
box-shadow: 0 4px 0 var(--line-strong);
color: #172036;
text-align: left;
}
.trainer-avatar {
width: 64px;
height: 64px;
display: grid;
place-items: center;
border: 2px solid var(--line-strong);
border-radius: 50%;
background: var(--surface);
color: var(--pokemon-blue);
font-family: var(--font-display);
font-size: 1.65rem;
font-weight: 950;
}
.trainer-pass span {
display: block;
font-size: .82rem;
font-weight: 800;
}
.trainer-pass strong {
display: block;
font-family: var(--font-display);
font-size: 1.25rem;
}
.demo-surface {
display: grid;
gap: 14px;
padding: 16px;
border: 1px dashed var(--line);
border-radius: var(--radius-card);
background: var(--surface-soft);
}
.control-row {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.control-stack {
display: grid;
gap: 12px;
}
.field {
display: grid;
gap: 6px;
}
.field label,
.field-label {
color: var(--ink-soft);
font-size: .86rem;
font-weight: 850;
}
.input,
.select,
.textarea {
width: 100%;
min-height: 44px;
border: 2px solid var(--line);
border-radius: var(--radius-control);
background: var(--surface);
color: var(--ink);
padding: 10px 12px;
transition: border-color .14s ease, box-shadow .14s ease;
}
.textarea {
min-height: 98px;
resize: vertical;
}
.input:focus,
.select:focus,
.textarea:focus {
border-color: var(--pokemon-blue);
box-shadow: 0 0 0 4px rgba(42, 117, 187, .16);
outline: none;
}
.input.is-valid {
border-color: var(--success);
}
.input.is-error {
border-color: var(--danger);
}
.field-note {
color: var(--muted);
font-size: .8rem;
}
.field-note.error {
color: var(--danger);
font-weight: 800;
}
.search-field {
position: relative;
}
.search-field .input {
padding-left: 42px;
}
.search-field::before {
content: "⌕";
position: absolute;
left: 14px;
top: 34px;
color: var(--muted);
font-size: 1.25rem;
line-height: 1;
}
.input-group {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: stretch;
}
.input-addon {
min-height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 12px;
border: 2px solid var(--line);
background: var(--surface-soft);
color: var(--muted);
font-weight: 850;
}
.input-addon:first-child {
border-radius: var(--radius-control) 0 0 var(--radius-control);
border-right: 0;
}
.input-addon:last-child {
border-radius: 0 var(--radius-control) var(--radius-control) 0;
border-left: 0;
}
.input-group .input {
border-radius: 0;
}
.check-control,
.radio-control,
.switch-control {
display: inline-flex;
align-items: center;
gap: 9px;
min-height: 36px;
color: var(--ink-soft);
font-weight: 800;
cursor: pointer;
user-select: none;
}
.check-control input,
.radio-control input {
width: 20px;
height: 20px;
accent-color: var(--pokemon-blue);
}
.switch-control input {
position: absolute;
inline-size: 1px;
block-size: 1px;
opacity: 0;
}
.switch-track {
width: 48px;
height: 28px;
position: relative;
border: 2px solid var(--line-strong);
border-radius: 999px;
background: var(--line);
transition: background .16s ease;
}
.switch-track::after {
content: "";
position: absolute;
top: 2px;
left: 2px;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--surface);
box-shadow: 0 2px 0 rgba(0,0,0,.2);
transition: transform .16s ease;
}
.switch-control input:checked + .switch-track {
background: var(--pokemon-blue);
}
.switch-control input:checked + .switch-track::after {
transform: translateX(20px);
}
.range-wrap {
display: grid;
grid-template-columns: 1fr 44px;
align-items: center;
gap: 12px;
}
input[type="range"] {
width: 100%;
accent-color: var(--pokemon-blue);
}
.range-value {
min-height: 32px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 2px solid var(--line-strong);
border-radius: var(--radius-control);
background: var(--pokemon-yellow);
color: #172036;
font-weight: 950;
}
.stepper {
display: inline-grid;
grid-template-columns: 38px 54px 38px;
align-items: center;
border: 2px solid var(--line-strong);
border-radius: var(--radius-control);
overflow: hidden;
background: var(--surface);
}
.stepper button {
min-height: 38px;
background: var(--pokemon-yellow);
color: #172036;
font-weight: 950;
cursor: pointer;
}
.stepper output {
display: grid;
place-items: center;
min-height: 38px;
border-left: 2px solid var(--line-strong);
border-right: 2px solid var(--line-strong);
font-weight: 900;
}
.segmented {
display: inline-flex;
flex-wrap: wrap;
gap: 4px;
padding: 4px;
border: 2px solid var(--line);
border-radius: var(--radius-control);
background: var(--surface-soft);
}
.segmented button {
min-height: 34px;
padding: 6px 11px;
border-radius: 6px;
background: transparent;
color: var(--ink-soft);
font-weight: 850;
cursor: pointer;
}
.segmented button[aria-pressed="true"] {
background: var(--pokemon-blue);
color: #ffffff;
}
.tabs {
display: grid;
gap: 14px;
}
.tab-list {
display: flex;
gap: 6px;
flex-wrap: wrap;
border-bottom: 2px solid var(--line);
}
.tab-button {
min-height: 42px;
padding: 9px 13px;
border-radius: var(--radius-control) var(--radius-control) 0 0;
background: transparent;
color: var(--ink-soft);
font-weight: 900;
cursor: pointer;
border-bottom: 3px solid transparent;
}
.tab-button[aria-selected="true"] {
background: var(--surface);
color: var(--pokemon-blue-deep);
border-color: var(--pokemon-yellow);
}
[data-theme="night"] .tab-button[aria-selected="true"] {
color: var(--pokemon-yellow);
}
.tab-panel {
display: none;
}
.tab-panel.is-active {
display: block;
}
.pokemon-card {
display: grid;
grid-template-rows: 188px auto;
border: 2px solid var(--line-strong);
border-radius: var(--radius-card);
overflow: hidden;
background: var(--surface);
box-shadow: var(--shadow-control);
}
.pokemon-card-media {
display: grid;
place-items: center;
padding: 18px;
background:
linear-gradient(90deg, rgba(31,42,59,.07) 1px, transparent 1px) 0 0 / 18px 18px,
linear-gradient(rgba(31,42,59,.07) 1px, transparent 1px) 0 0 / 18px 18px,
linear-gradient(135deg, rgba(255,203,5,.34), rgba(99,144,240,.18)),
var(--surface-soft);
}
.pokemon-card-body {
display: grid;
gap: 10px;
padding: 14px;
border-top: 2px solid var(--line-strong);
}
.pokemon-card-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
}
.pokemon-name {
display: grid;
gap: 4px;
}
.pokemon-name strong {
font-family: var(--font-display);
font-size: 1.3rem;
line-height: 1.1;
}
.hp-pill {
min-height: 28px;
display: inline-flex;
align-items: center;
gap: 5px;
padding: 4px 8px;
border: 2px solid var(--line-strong);
border-radius: 999px;
background: var(--pokemon-yellow);
color: #172036;
font-family: var(--font-mono);
font-size: .78rem;
font-weight: 950;
white-space: nowrap;
}
.avatar-symbol {
width: 108px;
height: 108px;
display: grid;
place-items: center;
border: 3px solid var(--line-strong);
border-radius: 50%;
background: var(--pokemon-yellow);
box-shadow: 0 6px 0 rgba(31,42,59,.22);
color: #172036;
font-family: var(--font-display);
font-size: 2.7rem;
font-weight: 950;
}
.progress {
display: grid;
gap: 6px;
}
.progress-label {
display: flex;
justify-content: space-between;
gap: 8px;
color: var(--muted);
font-size: .82rem;
font-weight: 850;
}
.progress-track {
height: 12px;
overflow: hidden;
border: 1px solid var(--line);
border-radius: 999px;
background: var(--surface-soft);
}
.progress-fill {
display: block;
height: 100%;
border-radius: inherit;
background: var(--pokemon-blue);
}
.list-row {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 12px;
align-items: center;
padding: 12px;
border: 1px solid var(--line);
border-radius: var(--radius-card);
background: var(--surface);
}
.mini-ball {
--ball-size: 30px;
}
.data-table-wrap {
overflow-x: auto;
border: 1px solid var(--line);
border-radius: var(--radius-card);
background: var(--surface);
}
.data-table {
width: 100%;
min-width: 720px;
border-collapse: collapse;
}
.data-table th,
.data-table td {
padding: 12px 14px;
border-bottom: 1px solid var(--line);
text-align: left;
vertical-align: middle;
}
.data-table th {
background: var(--pokemon-blue-deep);
color: #ffffff;
font-size: .82rem;
font-weight: 950;
text-transform: uppercase;
}
.data-table tr:last-child td {
border-bottom: 0;
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 6px;
min-height: 28px;
padding: 4px 8px;
border-radius: 999px;
background: var(--surface-soft);
color: var(--ink-soft);
font-size: .78rem;
font-weight: 900;
white-space: nowrap;
}
.status-badge::before {
content: "";
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--status, var(--muted));
}
.status-badge.success { --status: var(--success); }
.status-badge.warning { --status: var(--warning); }
.status-badge.danger { --status: var(--danger); }
.status-badge.info { --status: var(--pokemon-blue); }
.alert {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 12px;
align-items: start;
padding: 14px;
border: 1px solid var(--alert-line, var(--line));
border-left: 6px solid var(--alert-accent, var(--pokemon-blue));
border-radius: var(--radius-card);
background: var(--alert-bg, var(--surface));
}
.alert-icon {
width: 28px;
height: 28px;
display: grid;
place-items: center;
border-radius: 50%;
background: var(--alert-accent, var(--pokemon-blue));
color: #ffffff;
font-weight: 950;
}
.alert.success {
--alert-accent: var(--success);
--alert-line: color-mix(in srgb, var(--success) 38%, var(--line));
--alert-bg: color-mix(in srgb, var(--success) 10%, var(--surface));
}
.alert.warning {
--alert-accent: var(--warning);
--alert-line: color-mix(in srgb, var(--warning) 42%, var(--line));
--alert-bg: color-mix(in srgb, var(--warning) 12%, var(--surface));
}
.alert.danger {
--alert-accent: var(--danger);
--alert-line: color-mix(in srgb, var(--danger) 38%, var(--line));
--alert-bg: color-mix(in srgb, var(--danger) 10%, var(--surface));
}
.toast-stack {
position: fixed;
right: 18px;
bottom: 18px;
z-index: 70;
display: grid;
gap: 10px;
pointer-events: none;
}
.toast {
width: min(360px, calc(100vw - 36px));
display: grid;
grid-template-columns: auto 1fr;
gap: 10px;
align-items: center;
padding: 12px;
border: 2px solid var(--line-strong);
border-radius: var(--radius-card);
background: var(--surface);
box-shadow: var(--shadow-raised);
transform: translateY(14px);
opacity: 0;
transition: transform .2s ease, opacity .2s ease;
}
.toast.show {
transform: translateY(0);
opacity: 1;
}
.modal-backdrop {
position: fixed;
inset: 0;
z-index: 65;
display: none;
place-items: center;
padding: 22px;
background: rgba(8, 13, 22, .56);
}
.modal-backdrop.is-open {
display: grid;
}
.modal {
width: min(560px, 100%);
border: 2px solid var(--line-strong);
border-radius: var(--radius-card);
background: var(--surface);
box-shadow: var(--shadow-raised);
overflow: hidden;
}
.modal-header,
.modal-footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 14px 16px;
background: var(--surface-soft);
}
.modal-header {
border-bottom: 1px solid var(--line);
}
.modal-footer {
border-top: 1px solid var(--line);
justify-content: flex-end;
}
.modal-body {
padding: 16px;
display: grid;
gap: 12px;
}
.tooltip-demo {
position: relative;
display: inline-flex;
width: fit-content;
}
.tooltip-bubble {
position: absolute;
left: 50%;
bottom: calc(100% + 9px);
transform: translateX(-50%) translateY(4px);
min-width: 190px;
padding: 8px 10px;
border-radius: var(--radius-small);
background: #172036;
color: #ffffff;
font-size: .82rem;
opacity: 0;
pointer-events: none;
transition: opacity .14s ease, transform .14s ease;
}
.tooltip-demo:hover .tooltip-bubble,
.tooltip-demo:focus-within .tooltip-bubble {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
.chip-tooltip {
position: fixed;
z-index: 120;
max-width: min(240px, calc(100vw - 24px));
padding: 7px 9px;
border-radius: var(--radius-small);
background: #172036;
color: #ffffff;
box-shadow: 0 8px 18px rgba(0, 0, 0, .22);
font-size: .78rem;
font-weight: 850;
line-height: 1.25;
text-align: center;
white-space: nowrap;
pointer-events: none;
opacity: 0;
transform: translate(-50%, -6px);
transition: opacity .12s ease, transform .12s ease;
}
.chip-tooltip.is-visible {
opacity: 1;
transform: translate(-50%, 0);
}
.breadcrumb {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
color: var(--muted);
font-size: .9rem;
font-weight: 800;
}
.breadcrumb a {
color: var(--pokemon-blue);
}
.breadcrumb span:not(:last-child)::after {
content: "/";
margin-left: 8px;
color: var(--line-strong);
}
.side-nav-demo {
display: grid;
grid-template-columns: 210px 1fr;
min-height: 350px;
border: 1px solid var(--line);
border-radius: var(--radius-card);
overflow: hidden;
background: var(--surface);
}
.side-rail {
display: grid;
align-content: start;
gap: 4px;
padding: 12px;
border-right: 1px solid var(--line);
background: var(--surface-soft);
}
.side-rail a {
min-height: 38px;
display: flex;
align-items: center;
gap: 9px;
padding: 8px 10px;
border-radius: var(--radius-control);
color: var(--ink-soft);
font-weight: 850;
}
.side-rail a.is-active {
background: var(--pokemon-blue);
color: #ffffff;
}
.nav-preview {
display: grid;
align-content: start;
gap: 12px;
padding: 16px;
}
.pagination {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
}
.page-link {
min-width: 38px;
min-height: 38px;
display: inline-grid;
place-items: center;
padding: 6px 10px;
border: 1px solid var(--line);
border-radius: var(--radius-control);
background: var(--surface);
color: var(--ink-soft);
font-weight: 850;
}
.page-link.is-active {
border-color: var(--line-strong);
background: var(--pokemon-yellow);
color: #172036;
}
.bottom-nav {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 4px;
width: min(100%, 420px);
padding: 6px;
border: 1px solid var(--line);
border-radius: var(--radius-card);
background: var(--surface);
box-shadow: var(--shadow-soft);
}
.bottom-nav a {
min-height: 54px;
display: grid;
place-items: center;
gap: 3px;
border-radius: var(--radius-control);
color: var(--muted);
font-size: .76rem;
font-weight: 850;
}
.bottom-nav a.is-active {
background: rgba(255, 203, 5, .25);
color: var(--pokemon-blue-deep);
}
.template-grid {
display: grid;
grid-template-columns: 1.1fr .9fr;
gap: 16px;
}
.template-panel {
border: 2px solid var(--line-strong);
border-radius: var(--radius-card);
background: var(--surface);
box-shadow: var(--shadow-control);
overflow: hidden;
}
.template-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 12px;
border-bottom: 2px solid var(--line-strong);
background: var(--pokemon-blue-deep);
color: #ffffff;
}
.template-body {
padding: 14px;
display: grid;
gap: 12px;
}
.dex-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
.dex-mini-card {
min-height: 112px;
display: grid;
justify-items: center;
align-content: center;
gap: 8px;
padding: 10px;
border: 1px solid var(--line);
border-radius: var(--radius-card);
background: var(--surface-soft);
text-align: center;
}
.detail-layout {
display: grid;
grid-template-columns: 172px 1fr;
gap: 14px;
align-items: start;
}
.detail-art {
min-height: 188px;
display: grid;
place-items: center;
border: 1px solid var(--line);
border-radius: var(--radius-card);
background: linear-gradient(135deg, rgba(255,203,5,.28), rgba(42,117,187,.12));
}
.battle-hud {
display: grid;
gap: 10px;
padding: 12px;
border: 2px solid var(--line-strong);
border-radius: var(--radius-card);
background: #172036;
color: #ffffff;
}
.hud-row {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: 10px;
}
.hp-track {
height: 14px;
overflow: hidden;
border: 2px solid #ffffff;
border-radius: 999px;
background: rgba(255,255,255,.16);
}
.hp-fill {
display: block;
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, #2eb872, #a6e44d);
}
.inventory-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
.item-slot {
min-height: 96px;
display: grid;
place-items: center;
gap: 5px;
padding: 8px;
border: 1px solid var(--line);
border-radius: var(--radius-card);
background: var(--surface-soft);
text-align: center;
font-size: .8rem;
font-weight: 850;
}
.empty-state {
min-height: 220px;
display: grid;
place-items: center;
gap: 12px;
padding: 26px;
border: 1px dashed var(--line);
border-radius: var(--radius-card);
background: var(--surface-soft);
text-align: center;
}
.skeleton {
display: grid;
gap: 10px;
}
.skeleton-line {
height: 14px;
border-radius: 999px;
background: linear-gradient(90deg, var(--line), var(--surface), var(--line));
background-size: 200% 100%;
animation: shimmer 1.4s linear infinite;
}
.skeleton-box {
height: 128px;
border-radius: var(--radius-card);
background: linear-gradient(90deg, var(--line), var(--surface), var(--line));
background-size: 200% 100%;
animation: shimmer 1.4s linear infinite;
}
@keyframes shimmer {
to { background-position: -200% 0; }
}
.autocomplete {
position: relative;
}
.suggestion-list {
position: absolute;
z-index: 18;
top: calc(100% + 6px);
left: 0;
right: 0;
display: none;
gap: 6px;
max-height: 300px;
margin: 0;
padding: 8px;
border: 2px solid var(--line-strong);
border-radius: var(--radius-card);
background: var(--surface);
box-shadow: var(--shadow-raised);
list-style: none;
overflow: auto;
}
.suggestion-list.is-open {
display: grid;
}
.autocomplete.demo-open .suggestion-list {
position: static;
margin-top: 6px;
}
.suggestion-item {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 10px;
min-height: 54px;
padding: 8px;
border-radius: var(--radius-control);
cursor: pointer;
}
.suggestion-item:hover,
.suggestion-item.is-active {
background: rgba(255, 203, 5, .2);
}
.suggestion-item strong {
display: block;
line-height: 1.1;
}
.filter-summary {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: 12px;
padding: 12px;
border: 1px solid var(--line);
border-radius: var(--radius-card);
background: var(--surface);
}
.type-filter-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 8px;
}
.type-toggle {
display: grid;
place-items: center;
min-height: 42px;
padding: 7px;
border: 2px solid var(--line);
border-radius: var(--radius-control);
background: var(--surface);
cursor: pointer;
}
.type-toggle[aria-pressed="true"] {
border-color: var(--line-strong);
background: rgba(255, 203, 5, .24);
box-shadow: 0 2px 0 var(--line-strong);
}
.evolution-chain {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
align-items: center;
gap: 14px;
}
.evolution-node {
display: grid;
justify-items: center;
gap: 8px;
min-height: 190px;
padding: 14px;
border: 1px solid var(--line);
border-radius: var(--radius-card);
background: var(--surface);
text-align: center;
}
.evolution-node:not(:last-child) {
position: relative;
}
.evolution-node:not(:last-child)::after {
content: "→";
position: absolute;
right: -22px;
top: 50%;
transform: translateY(-50%);
color: var(--pokemon-blue);
font-size: 1.55rem;
font-weight: 950;
}
.evolution-sprite {
width: 82px;
height: 82px;
display: grid;
place-items: center;
border: 2px solid var(--line-strong);
border-radius: 50%;
background: linear-gradient(135deg, rgba(255,203,5,.35), rgba(42,117,187,.16)), var(--surface);
color: #172036;
font-family: var(--font-display);
font-size: 1.55rem;
font-weight: 950;
}
.move-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.move-card {
display: grid;
gap: 10px;
padding: 12px;
border: 1px solid var(--line);
border-radius: var(--radius-card);
background: var(--surface);
}
.move-head,
.move-meta {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
flex-wrap: wrap;
}
.move-category {
display: inline-flex;
align-items: center;
min-height: 26px;
padding: 4px 8px;
border-radius: 999px;
background: var(--surface-soft);
color: var(--ink-soft);
font-size: .75rem;
font-weight: 900;
text-transform: uppercase;
}
.weakness-matrix {
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 8px;
}
.weakness-cell {
display: grid;
justify-items: center;
gap: 6px;
min-height: 78px;
padding: 9px;
min-width: 0;
border: 1px solid var(--line);
border-radius: var(--radius-card);
background: var(--surface);
}
.multiplier {
min-width: 42px;
min-height: 26px;
display: inline-grid;
place-items: center;
padding: 3px 8px;
border-radius: 999px;
background: var(--surface-soft);
color: var(--ink-soft);
font-family: var(--font-mono);
font-size: .76rem;
font-weight: 950;
}
.multiplier.high {
background: rgba(223, 47, 47, .14);
color: var(--danger);
}
.multiplier.low {
background: rgba(46, 184, 114, .15);
color: var(--success);
}
.team-slots {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
}
.team-slot {
display: grid;
justify-items: center;
align-content: center;
gap: 8px;
min-height: 148px;
padding: 12px;
border: 2px dashed var(--line);
border-radius: var(--radius-card);
background: var(--surface-soft);
color: var(--ink);
text-align: center;
cursor: pointer;
}
.team-slot.is-filled {
border-style: solid;
border-color: var(--line-strong);
background: var(--surface);
box-shadow: var(--shadow-control);
}
.team-slot.is-selected {
background: rgba(255, 203, 5, .22);
}
.compare-table {
display: grid;
border: 1px solid var(--line);
border-radius: var(--radius-card);
overflow: hidden;
background: var(--surface);
}
.compare-row {
display: grid;
grid-template-columns: 112px repeat(3, minmax(0, 1fr));
min-height: 46px;
border-bottom: 1px solid var(--line);
}
.compare-row:last-child {
border-bottom: 0;
}
.compare-row > * {
display: grid;
align-items: center;
padding: 9px 10px;
border-right: 1px solid var(--line);
}
.compare-row > *:last-child {
border-right: 0;
}
.compare-row strong:first-child {
background: var(--surface-soft);
color: var(--ink-soft);
}
.radar-wrap {
display: grid;
place-items: center;
gap: 10px;
min-height: 260px;
}
.radar-chart {
width: min(100%, 250px);
height: auto;
}
.radar-grid-line {
fill: none;
stroke: var(--line);
stroke-width: 1.3;
}
.radar-axis {
stroke: var(--line);
stroke-width: 1;
}
.radar-shape {
fill: rgba(42, 117, 187, .32);
stroke: var(--pokemon-blue);
stroke-width: 3;
}
.radar-label {
fill: var(--ink-soft);
font-size: 11px;
font-weight: 900;
text-anchor: middle;
}
.battle-action-menu {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
padding: 12px;
border: 2px solid var(--line-strong);
border-radius: var(--radius-card);
background: #172036;
}
.battle-action-menu .btn {
width: 100%;
}
.bag-card,
.event-card {
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
gap: 12px;
padding: 12px;
border: 1px solid var(--line);
border-radius: var(--radius-card);
background: var(--surface);
}
.item-icon {
width: 58px;
height: 58px;
display: grid;
place-items: center;
border: 2px solid var(--line-strong);
border-radius: var(--radius-card);
background: var(--pokemon-yellow);
color: #172036;
font-family: var(--font-display);
font-size: 1.5rem;
font-weight: 950;
}
.event-card {
grid-template-columns: 1fr auto;
align-items: start;
border-color: var(--line-strong);
background:
linear-gradient(135deg, rgba(255,203,5,.22), rgba(42,117,187,.12)),
var(--surface);
}
.countdown {
min-width: 82px;
display: grid;
place-items: center;
padding: 8px;
border: 2px solid var(--line-strong);
border-radius: var(--radius-card);
background: var(--pokemon-red);
color: #ffffff;
font-family: var(--font-mono);
font-weight: 950;
}
.reward-shelf {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(64px, 1fr));
gap: 8px;
}
.reward {
display: grid;
justify-items: center;
gap: 8px;
min-width: 0;
min-height: 104px;
padding: 8px;
border: 1px solid var(--line);
border-radius: var(--radius-card);
background: var(--surface);
text-align: center;
font-size: .82rem;
font-weight: 850;
overflow: hidden;
}
.reward .gym-badge,
.reward .item-icon {
width: 48px;
height: 48px;
font-size: 1rem;
}
.reward > span:last-child {
max-width: 100%;
overflow-wrap: anywhere;
line-height: 1.2;
}
.reward.is-locked {
opacity: .48;
filter: grayscale(.45);
}
.code-block {
overflow: auto;
border: 1px solid var(--line);
border-radius: var(--radius-card);
background: #111827;
color: #f9fafb;
}
.code-block pre {
margin: 0;
padding: 16px;
font-family: var(--font-mono);
font-size: .86rem;
line-height: 1.7;
}
.code-block code {
padding: 0;
border: 0;
background: transparent;
color: inherit;
}
.checklist {
display: grid;
gap: 12px;
margin: 0;
padding: 0;
list-style: none;
}
.checklist li {
display: grid;
grid-template-columns: auto 1fr;
gap: 10px;
align-items: start;
padding: 12px;
border: 1px solid var(--line);
border-radius: var(--radius-card);
background: var(--surface);
color: var(--ink-soft);
}
.checklist li::before {
content: "✓";
width: 24px;
height: 24px;
display: grid;
place-items: center;
border-radius: 50%;
background: var(--success);
color: #ffffff;
font-weight: 950;
line-height: 1;
}
.footer {
padding: 34px 0 48px;
border-top: 1px solid var(--line);
color: var(--muted);
font-size: .9rem;
}
.footer strong {
color: var(--ink);
}
@media (max-width: 1080px) {
.top-nav {
grid-template-columns: 1fr auto;
}
.nav-links {
grid-column: 1 / -1;
justify-content: flex-start;
padding-bottom: 10px;
}
.hero,
.asset-showcase,
.template-grid {
grid-template-columns: 1fr;
}
.pokedex-shell {
max-width: 560px;
}
.grid.four,
.grid.six,
.weakness-matrix,
.reward-shelf {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (max-width: 820px) {
.container {
padding: 0 18px;
}
.section {
padding: 42px 0;
}
h1 {
font-size: 3.05rem;
}
h2 {
font-size: 2.15rem;
}
.hero {
min-height: auto;
}
.quick-index,
.grid.two,
.grid.three,
.grid.four,
.grid.six,
.type-grid,
.asset-mini-grid,
.type-filter-grid,
.team-slots,
.weakness-matrix,
.reward-shelf {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.side-nav-demo,
.detail-layout,
.evolution-chain {
grid-template-columns: 1fr;
}
.evolution-node:not(:last-child)::after {
right: auto;
top: auto;
bottom: -25px;
left: 50%;
transform: translateX(-50%) rotate(90deg);
}
.side-rail {
border-right: 0;
border-bottom: 1px solid var(--line);
}
.inventory-grid,
.dex-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 560px) {
.top-nav {
min-height: 64px;
gap: 12px;
}
.brand-lockup {
min-width: 0;
}
.pokemon-word {
font-size: 1.4rem;
-webkit-text-stroke-width: 1.5px;
text-shadow: 1px 2px 0 var(--pokemon-blue);
}
.brand-subtitle {
display: none;
}
h1 {
font-size: 2.35rem;
}
h2 {
font-size: 1.78rem;
}
.hero-copy,
.lead {
font-size: 1rem;
}
.quick-index,
.grid.two,
.grid.three,
.grid.four,
.grid.six,
.type-grid,
.asset-mini-grid,
.type-filter-grid,
.move-grid,
.team-slots,
.weakness-matrix,
.reward-shelf,
.battle-action-menu {
grid-template-columns: 1fr;
}
.compare-row {
grid-template-columns: 1fr;
}
.compare-row > * {
border-right: 0;
border-bottom: 1px solid var(--line);
}
.compare-row > *:last-child {
border-bottom: 0;
}
.bag-card,
.event-card,
.filter-summary {
grid-template-columns: 1fr;
}
.pokedex-body {
padding: 12px;
}
.pokedex-screen {
min-height: 420px;
padding: 12px;
}
.pokedex-controls {
grid-template-columns: 1fr;
}
.screen-buttons,
.hero-actions,
.control-row {
align-items: stretch;
}
.btn {
width: 100%;
}
.icon-btn {
width: 44px;
}
.modal-footer {
flex-direction: column-reverse;
align-items: stretch;
}
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
*,
*::before,
*::after {
animation-duration: .001ms !important;
animation-iteration-count: 1 !important;
scroll-behavior: auto !important;
transition-duration: .001ms !important;
}
}
@media print {
.site-header,
.hero-actions,
.nav-actions,
.toast-stack,
.modal-backdrop {
display: none !important;
}
body {
background: #ffffff;
}
.card,
.pokedex-shell,
.template-panel,
.pokemon-card {
box-shadow: none;
}
.section {
break-inside: avoid;
}
}
</style>
</head>
<body>
<header class="site-header">
<div class="container top-nav">
<a class="brand-lockup" href="#top" aria-label="返回顶部">
<span class="pokeball" aria-hidden="true"></span>
<span>
<span class="pokemon-word">Pokémon</span>
<span class="brand-subtitle">UI Library Guidelines v3.0</span>
</span>
</a>
<nav class="nav-links" aria-label="页面导航">
<a href="#foundation">方向</a>
<a href="#assets">官方元素</a>
<a href="#tokens">Tokens</a>
<a href="#controls">控件</a>
<a href="#display">数据展示</a>
<a href="#pokemon-components">专属组件</a>
<a href="#feedback">反馈</a>
<a href="#navigation">导航</a>
<a href="#templates">模板</a>
<a href="#handoff">交付</a>
</nav>
<div class="nav-actions">
<button class="btn icon-btn ghost" type="button" id="themeToggle" aria-label="切换明暗主题" title="切换明暗主题">
<span class="icon" aria-hidden="true"></span>
</button>
<button class="btn small primary" type="button" data-open-modal>
<span class="icon" aria-hidden="true">?</span>
用法
</button>
</div>
</div>
</header>
<main id="top">
<section class="container hero" aria-labelledby="page-title">
<div>
<span class="section-kicker">Authorized Pokémon Product System</span>
<h1 id="page-title">Pokémon UI Library Design Guidelines v3</h1>
<p class="hero-copy">
针对已获得授权的 Pokémon 网页、活动页、会员中心、图鉴工具和电商体验,建立一套可落地的 UI 库。v3 在基础控件之上补齐 Team Builder、Evolution Chain、Move Data、Weakness Matrix、Battle Menu、Bag、Event 和 Reward 等产品专属组件。
</p>
<div class="hero-actions" aria-label="快速操作">
<a class="btn primary large" href="#controls"><span class="icon" aria-hidden="true"></span>查看控件库</a>
<a class="btn blue large" href="#pokemon-components"><span class="icon" aria-hidden="true"></span>查看专属组件</a>
<a class="btn blue large" href="#templates"><span class="icon" aria-hidden="true"></span>查看页面模板</a>
</div>
<div class="quick-index" aria-label="规范索引">
<a href="#assets"><strong>Official Elements</strong><span>Logo / Poké Ball / Type</span></a>
<a href="#tokens"><strong>Design Tokens</strong><span>Color / Type / Radius</span></a>
<a href="#controls"><strong>Controls</strong><span>Form / Button / Tabs</span></a>
<a href="#pokemon-components"><strong>Product Components</strong><span>Team / Battle / Dex</span></a>
</div>
</div>
<aside class="pokedex-shell" aria-label="Pokédex UI 示例">
<div class="pokedex-head">
<div class="pokedex-lights" aria-hidden="true">
<span class="lens"></span>
<span class="signal red"></span>
<span class="signal yellow"></span>
<span class="signal green"></span>
</div>
<span class="pokedex-code">KANTO-025</span>
</div>
<div class="pokedex-body">
<div class="pokedex-screen">
<div class="screen-topline">
<span class="dex-number">NATIONAL DEX #025</span>
<span class="type-chip type-image-chip type-electric"><img src="frontend/public/types/13.png" alt="" loading="lazy" /><span class="sr-only">Electric</span></span>
</div>
<div class="sprite-stage">
<div class="pikachu-face" role="img" aria-label="Pikachu 风格面部示意图"></div>
</div>
<div class="dex-title-row">
<div>
<h3>Pikachu</h3>
<p class="muted">Mouse Pokémon · Height 0.4 m · Weight 6.0 kg</p>
</div>
<span class="hp-pill">HP 35</span>
</div>
<p>
作为授权项目的核心示例图鉴界面可以使用官方名称、类型、编号、HP 与 Pokédex 信息结构。视觉需要保证清晰可读,不能让装饰压过任务。
</p>
<div class="stat-list" aria-label="Pikachu 数值示意">
<div class="stat-row"><span>ATK</span><div class="stat-track"><span class="stat-fill" style="width: 55%;"></span></div><span>55</span></div>
<div class="stat-row"><span>SPD</span><div class="stat-track"><span class="stat-fill" style="width: 90%;"></span></div><span>90</span></div>
<div class="stat-row"><span>SP</span><div class="stat-track"><span class="stat-fill" style="width: 50%;"></span></div><span>50</span></div>
</div>
<div class="pokedex-controls">
<div class="screen-buttons">
<button class="btn small primary" type="button"><span class="icon" aria-hidden="true"></span>Thunderbolt</button>
<button class="btn small blue" type="button"><span class="icon" aria-hidden="true"></span>Add Team</button>
</div>
<div class="dpad" aria-hidden="true"></div>
</div>
</div>
</div>
</aside>
</section>
<section class="section band" id="foundation" aria-labelledby="foundation-title">
<div class="container">
<div class="section-header">
<span class="section-kicker">01 / Creative Direction</span>
<h2 id="foundation-title">设计方向</h2>
<p class="lead">旧版的问题是过度抽象成泛怪兽风。新版以授权 Pokémon 产品为前提,明确使用官方视觉元素,但把它们工程化为可复用的 UI 语言。</p>
</div>
<div class="grid four">
<article class="card padded foundation-card">
<span class="number-mark">01</span>
<h3>Official First</h3>
<p class="muted">可使用 Pokémon、Poké Ball、Pokédex、官方角色名、类型体系、HP/EXP、Gym Badge 等授权资产语言。</p>
</article>
<article class="card padded foundation-card">
<span class="number-mark">02</span>
<h3>Game UI, Web Quality</h3>
<p class="muted">借用游戏中的状态、收集、队伍、战斗和背包结构,但网页控件必须保持可访问、可扫描、可响应。</p>
</article>
<article class="card padded foundation-card">
<span class="number-mark">03</span>
<h3>Bright, Not Noisy</h3>
<p class="muted">黄色、蓝色、红色用于关键动作和品牌识别;大面积阅读区域仍使用白色、浅蓝灰和明确分割线。</p>
</article>
<article class="card padded foundation-card">
<span class="number-mark">04</span>
<h3>System Completeness</h3>
<p class="muted">从按钮、表单、筛选、Tab、弹窗、Toast 到图鉴卡、数据表、页面模板,全部使用同一套 tokens。</p>
</article>
</div>
</div>
</section>
<section class="section" id="assets" aria-labelledby="assets-title">
<div class="container">
<div class="section-header">
<span class="section-kicker">02 / Official Elements</span>
<h2 id="assets-title">官方元素使用规范</h2>
<p class="lead">授权场景下可以使用官方元素但需要把“品牌资产”和“UI 控件”分层管理。Logo 和角色图优先作为内容资产Poké Ball、类型色、徽章和图鉴框架可以进入组件体系。</p>
</div>
<div class="asset-showcase">
<div class="brand-panel" aria-label="Pokémon wordmark treatment">
<span class="pokemon-word">Pokémon</span>
<div class="trainer-pass">
<div class="trainer-avatar">T</div>
<div>
<span>TRAINER PASS</span>
<strong>Ash Ketchum</strong>
<span>Pallet Town · Badge 08</span>
</div>
</div>
</div>
<div class="asset-mini-grid">
<div class="asset-tile">
<span class="pokeball" style="--ball-size: 72px;" aria-hidden="true"></span>
<strong>Poké Ball</strong>
<span class="muted">用于图标、空状态、加载、徽章背景</span>
</div>
<div class="asset-tile">
<span class="gym-badge" aria-hidden="true">8</span>
<strong>Gym Badge</strong>
<span class="muted">用于等级、成就、会员权益</span>
</div>
<div class="asset-tile">
<span class="type-chip type-image-chip type-fire"><img src="frontend/public/types/10.png" alt="" loading="lazy" /><span class="sr-only">Fire</span></span>
<strong>Type System</strong>
<span class="muted">筛选、标签、属性、状态分类</span>
</div>
<div class="asset-tile">
<span class="dex-number">DEX #150</span>
<strong>Pokédex ID</strong>
<span class="muted">列表编号、详情标识、收藏序号</span>
</div>
</div>
</div>
<div class="grid two" style="margin-top: 18px;">
<article class="card padded">
<div class="card-title"><h3>可进入 UI 库的元素</h3></div>
<ul class="rule-list">
<li>Poké Ball 作为系统图标、加载器、选择状态、空状态主符号。</li>
<li>使用 <code>frontend/public/types/</code> 中的 Type 图片资产作为筛选、标签、数值、卡片边缘和图表标识CSS 类型色保留为 token 和兜底。</li>
<li>Pokédex 屏幕、训练家卡、Gym Badge、HP/EXP Bar 作为组件构型。</li>
<li>官方角色名和编号可用于样例数据,如 Pikachu #025、Charizard #006、Eevee #133。</li>
</ul>
</article>
<article class="card padded">
<div class="card-title"><h3>仍需控制的元素</h3></div>
<ul class="rule-list">
<li>Logo 不作为普通按钮图标反复出现,只用于品牌区域、授权说明和首屏识别。</li>
<li>角色官方插画不应被裁切到无法识别,也不应被当作装饰纹理平铺。</li>
<li>高饱和类型色必须配合文字对比策略Electric、Ice、Ground、Steel 等浅色标签用深色字。</li>
<li>战斗 UI 可以借鉴状态结构,不建议直接把主机游戏画面一比一搬到网页任务流。</li>
</ul>
</article>
</div>
</div>
</section>
<section class="section band" id="tokens" aria-labelledby="tokens-title">
<div class="container">
<div class="section-header">
<span class="section-kicker">03 / Design Tokens</span>
<h2 id="tokens-title">视觉令牌</h2>
<p class="lead">Tokens 要覆盖品牌色、类型色、基础表面、描边、阴影、圆角和组件状态。这里采用 8px 卡片圆角,强调专业网页质感,而不是过度玩具化。</p>
</div>
<div class="grid six" aria-label="品牌色">
<div class="token-swatch light-text" style="--swatch: #ffcb05;"><strong>Pokémon Yellow</strong><span>Primary CTA / Logo fill</span><code>#FFCB05</code></div>
<div class="token-swatch" style="--swatch: #2a75bb;"><strong>Pokémon Blue</strong><span>Navigation / Links</span><code>#2A75BB</code></div>
<div class="token-swatch" style="--swatch: #003a70;"><strong>Deep Blue</strong><span>Header / Text contrast</span><code>#003A70</code></div>
<div class="token-swatch" style="--swatch: #ee1515;"><strong>Poké Red</strong><span>Urgent / Ball top</span><code>#EE1515</code></div>
<div class="token-swatch" style="--swatch: #202124;"><strong>Ball Black</strong><span>Icon stroke / Strong line</span><code>#202124</code></div>
<div class="token-swatch light-text" style="--swatch: #f2f5fa;"><strong>Map Mist</strong><span>Page background</span><code>#F2F5FA</code></div>
</div>
<div class="card padded" style="margin-top: 18px;">
<div class="card-title">
<h3>Type Badge Assets</h3>
<span class="status-badge info">large badges + small icons</span>
</div>
<div class="type-grid">
<span class="type-chip type-image-chip type-normal"><img src="frontend/public/types/1.png" alt="" loading="lazy" /><span class="sr-only">Normal</span></span>
<span class="type-chip type-image-chip type-fighting"><img src="frontend/public/types/2.png" alt="" loading="lazy" /><span class="sr-only">Fighting</span></span>
<span class="type-chip type-image-chip type-flying"><img src="frontend/public/types/3.png" alt="" loading="lazy" /><span class="sr-only">Flying</span></span>
<span class="type-chip type-image-chip type-poison"><img src="frontend/public/types/4.png" alt="" loading="lazy" /><span class="sr-only">Poison</span></span>
<span class="type-chip type-image-chip type-ground"><img src="frontend/public/types/5.png" alt="" loading="lazy" /><span class="sr-only">Ground</span></span>
<span class="type-chip type-image-chip type-rock"><img src="frontend/public/types/6.png" alt="" loading="lazy" /><span class="sr-only">Rock</span></span>
<span class="type-chip type-image-chip type-bug"><img src="frontend/public/types/7.png" alt="" loading="lazy" /><span class="sr-only">Bug</span></span>
<span class="type-chip type-image-chip type-ghost"><img src="frontend/public/types/8.png" alt="" loading="lazy" /><span class="sr-only">Ghost</span></span>
<span class="type-chip type-image-chip type-steel"><img src="frontend/public/types/9.png" alt="" loading="lazy" /><span class="sr-only">Steel</span></span>
<span class="type-chip type-image-chip type-fire"><img src="frontend/public/types/10.png" alt="" loading="lazy" /><span class="sr-only">Fire</span></span>
<span class="type-chip type-image-chip type-water"><img src="frontend/public/types/11.png" alt="" loading="lazy" /><span class="sr-only">Water</span></span>
<span class="type-chip type-image-chip type-grass"><img src="frontend/public/types/12.png" alt="" loading="lazy" /><span class="sr-only">Grass</span></span>
<span class="type-chip type-image-chip type-electric"><img src="frontend/public/types/13.png" alt="" loading="lazy" /><span class="sr-only">Electric</span></span>
<span class="type-chip type-image-chip type-psychic"><img src="frontend/public/types/14.png" alt="" loading="lazy" /><span class="sr-only">Psychic</span></span>
<span class="type-chip type-image-chip type-ice"><img src="frontend/public/types/15.png" alt="" loading="lazy" /><span class="sr-only">Ice</span></span>
<span class="type-chip type-image-chip type-dragon"><img src="frontend/public/types/16.png" alt="" loading="lazy" /><span class="sr-only">Dragon</span></span>
<span class="type-chip type-image-chip type-dark"><img src="frontend/public/types/17.png" alt="" loading="lazy" /><span class="sr-only">Dark</span></span>
<span class="type-chip type-image-chip type-fairy"><img src="frontend/public/types/18.png" alt="" loading="lazy" /><span class="sr-only">Fairy</span></span>
<span class="type-chip type-image-chip type-stellar"><img src="frontend/public/types/19.png" alt="" loading="lazy" /><span class="sr-only">Stellar</span></span>
</div>
<p class="field-note" style="margin-top: 12px;">
横向徽章使用 <code>frontend/public/types/1.png</code><code>frontend/public/types/19.png</code>;紧凑网格、筛选器、矩阵和卡片内图标使用 <code>frontend/public/types/small/1.png</code><code>frontend/public/types/small/18.png</code>。文件映射1 Normal, 2 Fighting, 3 Flying, 4 Poison, 5 Ground, 6 Rock, 7 Bug, 8 Ghost, 9 Steel, 10 Fire, 11 Water, 12 Grass, 13 Electric, 14 Psychic, 15 Ice, 16 Dragon, 17 Dark, 18 Fairy, 19 Stellar.
</p>
</div>
<div class="grid three" style="margin-top: 18px;">
<article class="card padded">
<div class="card-title"><h3>Typography</h3></div>
<p class="muted">标题使用圆润粗体正文使用系统无衬线。Pokémon wordmark 只用于品牌展示,不替代正文或按钮文字。</p>
<div class="demo-surface" style="margin-top: 12px;">
<h3>Catch, Train, Explore</h3>
<p>正文保持 16px 以上,行高 1.55 至 1.7,保证儿童和成人用户都能快速阅读。</p>
<span class="dex-number">CAPTION / DEX ENTRY</span>
</div>
</article>
<article class="card padded">
<div class="card-title"><h3>Shape</h3></div>
<p class="muted">卡片半径 8px控件半径 8pxPill 只用于 type chips、HP、status badges。Poké Ball 和头像保持圆形。</p>
<div class="demo-surface" style="margin-top: 12px;">
<button class="btn primary" type="button">8px Button</button>
<span class="type-chip type-image-chip type-water"><img src="frontend/public/types/11.png" alt="" loading="lazy" /><span class="sr-only">Water</span></span>
<span class="pokeball mini-ball" aria-hidden="true"></span>
</div>
</article>
<article class="card padded">
<div class="card-title"><h3>Elevation</h3></div>
<p class="muted">主 CTA 和游戏化面板可使用硬阴影,普通信息卡使用柔和阴影。阴影不可替代清晰边框。</p>
<div class="demo-surface" style="margin-top: 12px;">
<button class="btn blue" type="button">Raised Action</button>
<div class="list-row"><span class="pokeball mini-ball"></span><strong>Soft row</strong><span class="status-badge success">Ready</span></div>
</div>
</article>
</div>
</div>
</section>
<section class="section" id="controls" aria-labelledby="controls-title">
<div class="container">
<div class="section-header">
<span class="section-kicker">04 / Controls</span>
<h2 id="controls-title">控件库</h2>
<p class="lead">控件覆盖按钮、图标按钮、输入、选择、开关、范围、步进器、分段控制、Tabs 和筛选标签。所有控件最小点击高度不低于 44px。</p>
</div>
<div class="grid two">
<article class="card padded">
<div class="card-title"><h3>Buttons</h3><span class="status-badge info">Default / Hover / Active / Disabled</span></div>
<div class="demo-surface">
<div class="control-row">
<button class="btn primary" type="button"><span class="icon" aria-hidden="true"></span>Start Journey</button>
<button class="btn blue" type="button"><span class="icon" aria-hidden="true"></span>Add Team</button>
<button class="btn red" type="button"><span class="icon" aria-hidden="true">!</span>Battle</button>
<button class="btn ghost" type="button">Ghost</button>
<button class="btn" type="button" disabled>Disabled</button>
</div>
<div class="control-row">
<button class="btn small primary" type="button">Small</button>
<button class="btn primary" type="button">Medium</button>
<button class="btn large primary" type="button">Large</button>
<button class="btn icon-btn blue" type="button" title="搜索" aria-label="搜索"><span class="icon" aria-hidden="true"></span></button>
<button class="btn icon-btn ghost" type="button" title="筛选" aria-label="筛选"><span class="icon" aria-hidden="true"></span></button>
</div>
</div>
</article>
<article class="card padded">
<div class="card-title"><h3>Inputs</h3><span class="status-badge success">A11y ready</span></div>
<div class="control-stack">
<div class="field search-field">
<label for="searchPokemon">Search Pokémon</label>
<input class="input" id="searchPokemon" type="search" placeholder="Pikachu, Eevee, Charizard" />
</div>
<div class="grid two">
<div class="field">
<label for="trainerName">Trainer name</label>
<input class="input is-valid" id="trainerName" type="text" value="Misty" />
<span class="field-note">可用名称</span>
</div>
<div class="field">
<label for="trainerRegion">Region</label>
<select class="select" id="trainerRegion">
<option>Kanto</option>
<option>Johto</option>
<option>Hoenn</option>
<option>Sinnoh</option>
<option>Paldea</option>
</select>
</div>
</div>
<div class="field">
<label for="friendCode">Friend code</label>
<div class="input-group">
<span class="input-addon">ID</span>
<input class="input is-error" id="friendCode" type="text" value="025-133" />
<span class="input-addon">GEN 1</span>
</div>
<span class="field-note error">需要 12 位数字代码。</span>
</div>
<div class="field">
<label for="dexNote">Dex note</label>
<textarea class="textarea" id="dexNote">Pikachu stores electricity in the electric sacs on its cheeks.</textarea>
</div>
</div>
</article>
<article class="card padded">
<div class="card-title"><h3>Selection Controls</h3></div>
<div class="demo-surface">
<div class="control-row">
<label class="check-control"><input type="checkbox" checked /> Show shiny form</label>
<label class="check-control"><input type="checkbox" /> Include regional forms</label>
</div>
<div class="control-row">
<label class="radio-control"><input name="starter" type="radio" checked /> Bulbasaur</label>
<label class="radio-control"><input name="starter" type="radio" /> Charmander</label>
<label class="radio-control"><input name="starter" type="radio" /> Squirtle</label>
</div>
<div class="control-row">
<label class="switch-control">
<input type="checkbox" id="soundToggle" checked />
<span class="switch-track" aria-hidden="true"></span>
Battle sound
</label>
<label class="switch-control">
<input type="checkbox" />
<span class="switch-track" aria-hidden="true"></span>
Compact mode
</label>
</div>
<div class="field">
<span class="field-label">Capture chance</span>
<div class="range-wrap">
<input id="captureRange" type="range" min="0" max="100" value="64" />
<output class="range-value" id="captureValue" for="captureRange">64</output>
</div>
</div>
<div class="control-row">
<span class="field-label">Potion count</span>
<div class="stepper" data-stepper>
<button type="button" data-step="-1" aria-label="减少"></button>
<output>3</output>
<button type="button" data-step="1" aria-label="增加"></button>
</div>
</div>
</div>
</article>
<article class="card padded">
<div class="card-title"><h3>Segmented / Tabs / Chips</h3></div>
<div class="demo-surface">
<div class="segmented" aria-label="Dex view mode">
<button type="button" aria-pressed="true">Grid</button>
<button type="button" aria-pressed="false">List</button>
<button type="button" aria-pressed="false">Map</button>
</div>
<div class="tabs" data-tabs>
<div class="tab-list" role="tablist" aria-label="Pokémon profile tabs">
<button class="tab-button" role="tab" aria-selected="true" aria-controls="tab-base" id="tab-base-btn" type="button">Base</button>
<button class="tab-button" role="tab" aria-selected="false" aria-controls="tab-moves" id="tab-moves-btn" type="button">Moves</button>
<button class="tab-button" role="tab" aria-selected="false" aria-controls="tab-evo" id="tab-evo-btn" type="button">Evolution</button>
</div>
<div class="tab-panel is-active" role="tabpanel" id="tab-base" aria-labelledby="tab-base-btn">
<p>Base tab 显示编号、分类、身高、体重、能力和基础数值。</p>
</div>
<div class="tab-panel" role="tabpanel" id="tab-moves" aria-labelledby="tab-moves-btn">
<p>Moves tab 显示 Thunderbolt、Quick Attack、Iron Tail 等技能列表。</p>
</div>
<div class="tab-panel" role="tabpanel" id="tab-evo" aria-labelledby="tab-evo-btn">
<p>Evolution tab 显示 Pichu → Pikachu → Raichu 的进化路径。</p>
</div>
</div>
<div class="control-row" aria-label="Type filters">
<span class="type-chip type-image-chip type-electric"><img src="frontend/public/types/13.png" alt="" loading="lazy" /><span class="sr-only">Electric</span></span>
<span class="type-chip type-image-chip type-fire"><img src="frontend/public/types/10.png" alt="" loading="lazy" /><span class="sr-only">Fire</span></span>
<span class="type-chip type-image-chip type-water"><img src="frontend/public/types/11.png" alt="" loading="lazy" /><span class="sr-only">Water</span></span>
<span class="type-chip type-image-chip type-grass"><img src="frontend/public/types/12.png" alt="" loading="lazy" /><span class="sr-only">Grass</span></span>
</div>
</div>
</article>
</div>
</div>
</section>
<section class="section band" id="display" aria-labelledby="display-title">
<div class="container">
<div class="section-header">
<span class="section-kicker">05 / Data Display</span>
<h2 id="display-title">数据展示组件</h2>
<p class="lead">Pokémon 产品通常需要承载大量收集、列表、数值和状态。展示组件应优先保证对比和扫描效率,再加入品牌趣味。</p>
</div>
<div class="grid three">
<article class="pokemon-card">
<div class="pokemon-card-media">
<div class="avatar-symbol" aria-hidden="true">25</div>
</div>
<div class="pokemon-card-body">
<div class="pokemon-card-head">
<div class="pokemon-name">
<span class="dex-number">#025</span>
<strong>Pikachu</strong>
</div>
<span class="hp-pill">HP 35</span>
</div>
<div class="control-row">
<span class="type-chip type-image-chip type-electric"><img src="frontend/public/types/13.png" alt="" loading="lazy" /><span class="sr-only">Electric</span></span>
</div>
<div class="progress">
<div class="progress-label"><span>EXP</span><span>68%</span></div>
<div class="progress-track"><span class="progress-fill" style="width: 68%;"></span></div>
</div>
</div>
</article>
<article class="card padded">
<div class="card-title"><h3>List Rows</h3></div>
<div class="control-stack">
<div class="list-row">
<span class="pokeball mini-ball" aria-hidden="true"></span>
<div><strong>Charizard</strong><p class="muted">Fire / Flying · #006</p></div>
<span class="status-badge warning">Rare</span>
</div>
<div class="list-row">
<span class="pokeball mini-ball" aria-hidden="true"></span>
<div><strong>Squirtle</strong><p class="muted">Water · #007</p></div>
<span class="status-badge success">Caught</span>
</div>
<div class="list-row">
<span class="pokeball mini-ball" aria-hidden="true"></span>
<div><strong>Mewtwo</strong><p class="muted">Psychic · #150</p></div>
<span class="status-badge danger">Locked</span>
</div>
</div>
</article>
<article class="card padded">
<div class="card-title"><h3>Stats / Progress</h3></div>
<div class="control-stack">
<div class="progress">
<div class="progress-label"><span>HP</span><span>82 / 100</span></div>
<div class="progress-track"><span class="progress-fill" style="width: 82%; background: var(--success);"></span></div>
</div>
<div class="progress">
<div class="progress-label"><span>EXP</span><span>44%</span></div>
<div class="progress-track"><span class="progress-fill" style="width: 44%; background: var(--pokemon-yellow);"></span></div>
</div>
<div class="progress">
<div class="progress-label"><span>Catch rate</span><span>18%</span></div>
<div class="progress-track"><span class="progress-fill" style="width: 18%; background: var(--danger);"></span></div>
</div>
<div class="battle-hud">
<div class="hud-row"><strong>Snorlax Lv. 42</strong><span>HP</span></div>
<div class="hp-track"><span class="hp-fill" style="width: 72%;"></span></div>
</div>
</div>
</article>
</div>
<div class="card padded" style="margin-top: 18px;">
<div class="card-title"><h3>Data Table</h3><span class="status-badge info">Sortable / Filterable</span></div>
<div class="data-table-wrap">
<table class="data-table">
<thead>
<tr>
<th>Dex</th>
<th>Name</th>
<th>Type</th>
<th>Region</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>#001</code></td>
<td>Bulbasaur</td>
<td><span class="type-chip type-image-chip type-grass"><img src="frontend/public/types/12.png" alt="" loading="lazy" /><span class="sr-only">Grass</span></span></td>
<td>Kanto</td>
<td><span class="status-badge success">Caught</span></td>
<td><button class="btn small ghost" type="button">View</button></td>
</tr>
<tr>
<td><code>#004</code></td>
<td>Charmander</td>
<td><span class="type-chip type-image-chip type-fire"><img src="frontend/public/types/10.png" alt="" loading="lazy" /><span class="sr-only">Fire</span></span></td>
<td>Kanto</td>
<td><span class="status-badge warning">Seen</span></td>
<td><button class="btn small ghost" type="button">View</button></td>
</tr>
<tr>
<td><code>#007</code></td>
<td>Squirtle</td>
<td><span class="type-chip type-image-chip type-water"><img src="frontend/public/types/11.png" alt="" loading="lazy" /><span class="sr-only">Water</span></span></td>
<td>Kanto</td>
<td><span class="status-badge success">Caught</span></td>
<td><button class="btn small ghost" type="button">View</button></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
<section class="section" id="pokemon-components" aria-labelledby="pokemon-components-title">
<div class="container">
<div class="section-header">
<span class="section-kicker">06 / Pokémon Product Components</span>
<h2 id="pokemon-components-title">Pokémon 专属组件</h2>
<p class="lead">v3 新增一组真正贴近 Pokémon 产品场景的组件:搜索联想、进阶 Type 筛选、进化链、技能数据、弱点矩阵、队伍构建、对比面板、战斗菜单、背包、活动和奖励体系。</p>
</div>
<div class="grid two">
<article class="card padded">
<div class="card-title">
<h3>Pokémon Autocomplete</h3>
<span class="status-badge info">Combobox</span>
</div>
<div class="demo-surface">
<div class="field search-field autocomplete demo-open">
<label for="pokemonCombobox">Search Pokémon</label>
<input class="input" id="pokemonCombobox" type="search" role="combobox" aria-controls="pokemonSuggestions" aria-expanded="true" aria-autocomplete="list" value="Pi" />
<ul class="suggestion-list is-open" id="pokemonSuggestions" role="listbox" aria-label="Pokémon 搜索建议">
<li class="suggestion-item is-active" role="option" data-name="Pikachu" aria-selected="true">
<span class="pokeball mini-ball" aria-hidden="true"></span>
<div><strong>Pikachu</strong><span class="muted">#025 · Mouse Pokémon</span></div>
<span class="type-chip type-image-chip type-electric"><img src="frontend/public/types/small/13.png" alt="" loading="lazy" /><span class="sr-only">Electric</span></span>
</li>
<li class="suggestion-item" role="option" data-name="Raichu" style="display: none;">
<span class="pokeball mini-ball" aria-hidden="true"></span>
<div><strong>Raichu</strong><span class="muted">#026 · Mouse Pokémon</span></div>
<span class="type-chip type-image-chip type-electric"><img src="frontend/public/types/small/13.png" alt="" loading="lazy" /><span class="sr-only">Electric</span></span>
</li>
<li class="suggestion-item" role="option" data-name="Pichu">
<span class="pokeball mini-ball" aria-hidden="true"></span>
<div><strong>Pichu</strong><span class="muted">#172 · Tiny Mouse Pokémon</span></div>
<span class="type-chip type-image-chip type-electric"><img src="frontend/public/types/small/13.png" alt="" loading="lazy" /><span class="sr-only">Electric</span></span>
</li>
<li class="suggestion-item" role="option" data-name="Charizard" style="display: none;">
<span class="pokeball mini-ball" aria-hidden="true"></span>
<div><strong>Charizard</strong><span class="muted">#006 · Flame Pokémon</span></div>
<span class="type-chip type-image-chip type-fire"><img src="frontend/public/types/small/10.png" alt="" loading="lazy" /><span class="sr-only">Fire</span></span>
</li>
</ul>
</div>
<p class="field-note">用于 Pokédex、Team Builder、Shop search。结果项建议包含编号、名称、分类和 Type badge。</p>
</div>
</article>
<article class="card padded">
<div class="card-title">
<h3>Advanced Type Filter</h3>
<span class="status-badge success">Multi-select</span>
</div>
<div class="control-stack">
<div class="filter-summary">
<div>
<strong id="typeFilterSummary">Selected: Electric, Flying</strong>
<p class="muted">支持多选、清空、组合筛选和弱点模式。</p>
</div>
<button class="btn small ghost" type="button" id="clearTypeFilters">Clear</button>
</div>
<div class="type-filter-grid" aria-label="Type filter demo">
<button class="type-toggle" type="button" aria-pressed="true" data-type-name="Electric" aria-label="Electric type">
<span class="type-chip type-image-chip type-electric"><img src="frontend/public/types/small/13.png" alt="" loading="lazy" /><span class="sr-only">Electric</span></span>
</button>
<button class="type-toggle" type="button" aria-pressed="true" data-type-name="Flying" aria-label="Flying type">
<span class="type-chip type-image-chip type-flying"><img src="frontend/public/types/small/3.png" alt="" loading="lazy" /><span class="sr-only">Flying</span></span>
</button>
<button class="type-toggle" type="button" aria-pressed="false" data-type-name="Fire" aria-label="Fire type">
<span class="type-chip type-image-chip type-fire"><img src="frontend/public/types/small/10.png" alt="" loading="lazy" /><span class="sr-only">Fire</span></span>
</button>
<button class="type-toggle" type="button" aria-pressed="false" data-type-name="Water" aria-label="Water type">
<span class="type-chip type-image-chip type-water"><img src="frontend/public/types/small/11.png" alt="" loading="lazy" /><span class="sr-only">Water</span></span>
</button>
<button class="type-toggle" type="button" aria-pressed="false" data-type-name="Grass" aria-label="Grass type">
<span class="type-chip type-image-chip type-grass"><img src="frontend/public/types/small/12.png" alt="" loading="lazy" /><span class="sr-only">Grass</span></span>
</button>
<button class="type-toggle" type="button" aria-pressed="false" data-type-name="Psychic" aria-label="Psychic type">
<span class="type-chip type-image-chip type-psychic"><img src="frontend/public/types/small/14.png" alt="" loading="lazy" /><span class="sr-only">Psychic</span></span>
</button>
<button class="type-toggle" type="button" aria-pressed="false" data-type-name="Dragon" aria-label="Dragon type">
<span class="type-chip type-image-chip type-dragon"><img src="frontend/public/types/small/16.png" alt="" loading="lazy" /><span class="sr-only">Dragon</span></span>
</button>
<button class="type-toggle" type="button" aria-pressed="false" data-type-name="Fairy" aria-label="Fairy type">
<span class="type-chip type-image-chip type-fairy"><img src="frontend/public/types/small/18.png" alt="" loading="lazy" /><span class="sr-only">Fairy</span></span>
</button>
</div>
</div>
</article>
<article class="card padded">
<div class="card-title">
<h3>Evolution Chain</h3>
<span class="status-badge info">Detail page</span>
</div>
<div class="evolution-chain" aria-label="Pichu to Pikachu to Raichu evolution chain">
<div class="evolution-node">
<div class="evolution-sprite">172</div>
<strong>Pichu</strong>
<span class="type-chip type-image-chip type-electric"><img src="frontend/public/types/small/13.png" alt="" loading="lazy" /><span class="sr-only">Electric</span></span>
<p class="muted">High friendship</p>
</div>
<div class="evolution-node">
<div class="evolution-sprite">025</div>
<strong>Pikachu</strong>
<span class="type-chip type-image-chip type-electric"><img src="frontend/public/types/small/13.png" alt="" loading="lazy" /><span class="sr-only">Electric</span></span>
<p class="muted">Thunder Stone</p>
</div>
<div class="evolution-node">
<div class="evolution-sprite">026</div>
<strong>Raichu</strong>
<span class="type-chip type-image-chip type-electric"><img src="frontend/public/types/small/13.png" alt="" loading="lazy" /><span class="sr-only">Electric</span></span>
<p class="muted">Final form</p>
</div>
</div>
</article>
<article class="card padded">
<div class="card-title">
<h3>Move Cards</h3>
<span class="status-badge info">Power / Accuracy / PP</span>
</div>
<div class="move-grid">
<div class="move-card">
<div class="move-head"><strong>Thunderbolt</strong><span class="type-chip type-image-chip type-electric"><img src="frontend/public/types/small/13.png" alt="" loading="lazy" /><span class="sr-only">Electric</span></span></div>
<p class="muted">May leave the target with paralysis.</p>
<div class="move-meta"><span class="move-category">Special</span><span>Power 90</span><span>Acc 100</span><span>PP 15</span></div>
</div>
<div class="move-card">
<div class="move-head"><strong>Quick Attack</strong><span class="type-chip type-image-chip type-normal"><img src="frontend/public/types/small/1.png" alt="" loading="lazy" /><span class="sr-only">Normal</span></span></div>
<p class="muted">This move always goes first.</p>
<div class="move-meta"><span class="move-category">Physical</span><span>Power 40</span><span>Acc 100</span><span>PP 30</span></div>
</div>
<div class="move-card">
<div class="move-head"><strong>Iron Tail</strong><span class="type-chip type-image-chip type-steel"><img src="frontend/public/types/small/9.png" alt="" loading="lazy" /><span class="sr-only">Steel</span></span></div>
<p class="muted">May lower the target's Defense stat.</p>
<div class="move-meta"><span class="move-category">Physical</span><span>Power 100</span><span>Acc 75</span><span>PP 15</span></div>
</div>
<div class="move-card">
<div class="move-head"><strong>Electro Ball</strong><span class="type-chip type-image-chip type-electric"><img src="frontend/public/types/small/13.png" alt="" loading="lazy" /><span class="sr-only">Electric</span></span></div>
<p class="muted">The faster the user, the greater the power.</p>
<div class="move-meta"><span class="move-category">Special</span><span>Variable</span><span>Acc 100</span><span>PP 10</span></div>
</div>
</div>
</article>
</div>
<div class="grid two" style="margin-top: 16px;">
<article class="card padded">
<div class="card-title">
<h3>Weakness Matrix</h3>
<span class="status-badge warning">Electric / Flying sample</span>
</div>
<div class="weakness-matrix" aria-label="Type effectiveness matrix">
<div class="weakness-cell"><span class="type-chip type-image-chip type-ground"><img src="frontend/public/types/small/5.png" alt="" loading="lazy" /><span class="sr-only">Ground</span></span><span class="multiplier high">2x</span></div>
<div class="weakness-cell"><span class="type-chip type-image-chip type-rock"><img src="frontend/public/types/small/6.png" alt="" loading="lazy" /><span class="sr-only">Rock</span></span><span class="multiplier high">2x</span></div>
<div class="weakness-cell"><span class="type-chip type-image-chip type-ice"><img src="frontend/public/types/small/15.png" alt="" loading="lazy" /><span class="sr-only">Ice</span></span><span class="multiplier high">2x</span></div>
<div class="weakness-cell"><span class="type-chip type-image-chip type-fighting"><img src="frontend/public/types/small/2.png" alt="" loading="lazy" /><span class="sr-only">Fighting</span></span><span class="multiplier low">0.5x</span></div>
<div class="weakness-cell"><span class="type-chip type-image-chip type-flying"><img src="frontend/public/types/small/3.png" alt="" loading="lazy" /><span class="sr-only">Flying</span></span><span class="multiplier low">0.5x</span></div>
<div class="weakness-cell"><span class="type-chip type-image-chip type-steel"><img src="frontend/public/types/small/9.png" alt="" loading="lazy" /><span class="sr-only">Steel</span></span><span class="multiplier low">0.5x</span></div>
<div class="weakness-cell"><span class="type-chip type-image-chip type-bug"><img src="frontend/public/types/small/7.png" alt="" loading="lazy" /><span class="sr-only">Bug</span></span><span class="multiplier low">0.5x</span></div>
<div class="weakness-cell"><span class="type-chip type-image-chip type-grass"><img src="frontend/public/types/small/12.png" alt="" loading="lazy" /><span class="sr-only">Grass</span></span><span class="multiplier low">0.5x</span></div>
<div class="weakness-cell"><span class="type-chip type-image-chip type-electric"><img src="frontend/public/types/small/13.png" alt="" loading="lazy" /><span class="sr-only">Electric</span></span><span class="multiplier low">0.5x</span></div>
<div class="weakness-cell"><span class="type-chip type-image-chip type-ghost"><img src="frontend/public/types/small/8.png" alt="" loading="lazy" /><span class="sr-only">Ghost</span></span><span class="multiplier">1x</span></div>
<div class="weakness-cell"><span class="type-chip type-image-chip type-dragon"><img src="frontend/public/types/small/16.png" alt="" loading="lazy" /><span class="sr-only">Dragon</span></span><span class="multiplier">1x</span></div>
<div class="weakness-cell"><span class="type-chip type-image-chip type-fairy"><img src="frontend/public/types/small/18.png" alt="" loading="lazy" /><span class="sr-only">Fairy</span></span><span class="multiplier">1x</span></div>
</div>
</article>
<article class="card padded">
<div class="card-title">
<h3>Team Builder Slots</h3>
<span class="status-badge success">6 slots</span>
</div>
<div class="team-slots" aria-label="Team builder slots">
<button class="team-slot is-filled is-selected" type="button">
<span class="avatar-symbol" style="width: 58px; height: 58px; font-size: 1.35rem;">25</span>
<strong>Pikachu</strong>
<span class="type-chip type-image-chip type-electric"><img src="frontend/public/types/small/13.png" alt="" loading="lazy" /><span class="sr-only">Electric</span></span>
</button>
<button class="team-slot is-filled" type="button">
<span class="avatar-symbol" style="width: 58px; height: 58px; font-size: 1.35rem; background: var(--type-fire); color: #fff;">06</span>
<strong>Charizard</strong>
<span class="type-chip type-image-chip type-fire"><img src="frontend/public/types/small/10.png" alt="" loading="lazy" /><span class="sr-only">Fire</span></span>
</button>
<button class="team-slot is-filled" type="button">
<span class="avatar-symbol" style="width: 58px; height: 58px; font-size: 1.35rem; background: var(--type-water); color: #fff;">07</span>
<strong>Squirtle</strong>
<span class="type-chip type-image-chip type-water"><img src="frontend/public/types/small/11.png" alt="" loading="lazy" /><span class="sr-only">Water</span></span>
</button>
<button class="team-slot" type="button"><span class="pokeball" style="--ball-size: 42px;"></span><strong>Empty slot</strong><span class="muted">Add Pokémon</span></button>
<button class="team-slot" type="button"><span class="pokeball" style="--ball-size: 42px;"></span><strong>Empty slot</strong><span class="muted">Add Pokémon</span></button>
<button class="team-slot" type="button"><span class="pokeball" style="--ball-size: 42px;"></span><strong>Empty slot</strong><span class="muted">Add Pokémon</span></button>
</div>
</article>
</div>
<div class="grid two" style="margin-top: 16px;">
<article class="card padded">
<div class="card-title"><h3>Compare Panel</h3><span class="status-badge info">3-way compare</span></div>
<div class="compare-table" aria-label="Pokémon compare panel">
<div class="compare-row"><strong>Metric</strong><strong>Pikachu</strong><strong>Raichu</strong><strong>Jolteon</strong></div>
<div class="compare-row"><strong>Type</strong><span>Electric</span><span>Electric</span><span>Electric</span></div>
<div class="compare-row"><strong>HP</strong><span>35</span><span>60</span><span>65</span></div>
<div class="compare-row"><strong>Attack</strong><span>55</span><span>90</span><span>65</span></div>
<div class="compare-row"><strong>Speed</strong><span>90</span><span>110</span><span>130</span></div>
<div class="compare-row"><strong>Ability</strong><span>Static</span><span>Static</span><span>Volt Absorb</span></div>
</div>
</article>
<article class="card padded">
<div class="card-title"><h3>Base Stats Radar</h3><span class="status-badge info">SVG tokenized</span></div>
<div class="radar-wrap">
<svg class="radar-chart" viewBox="0 0 260 240" role="img" aria-label="Pikachu base stats radar chart">
<polygon class="radar-grid-line" points="130,20 225,75 225,165 130,220 35,165 35,75"></polygon>
<polygon class="radar-grid-line" points="130,55 195,93 195,147 130,185 65,147 65,93"></polygon>
<polygon class="radar-grid-line" points="130,90 165,110 165,130 130,150 95,130 95,110"></polygon>
<line class="radar-axis" x1="130" y1="120" x2="130" y2="20"></line>
<line class="radar-axis" x1="130" y1="120" x2="225" y2="75"></line>
<line class="radar-axis" x1="130" y1="120" x2="225" y2="165"></line>
<line class="radar-axis" x1="130" y1="120" x2="130" y2="220"></line>
<line class="radar-axis" x1="130" y1="120" x2="35" y2="165"></line>
<line class="radar-axis" x1="130" y1="120" x2="35" y2="75"></line>
<polygon class="radar-shape" points="130,84 168,102 162,139 130,175 92,142 76,94"></polygon>
<text class="radar-label" x="130" y="13">HP</text>
<text class="radar-label" x="240" y="76">ATK</text>
<text class="radar-label" x="240" y="169">DEF</text>
<text class="radar-label" x="130" y="236">SPD</text>
<text class="radar-label" x="20" y="169">SP.DEF</text>
<text class="radar-label" x="20" y="76">SP.ATK</text>
</svg>
<p class="muted">雷达图适合详情页摘要;精确对比仍建议使用条形图或 Compare Panel。</p>
</div>
</article>
</div>
<div class="grid three" style="margin-top: 16px;">
<article class="card padded">
<div class="card-title"><h3>Battle Action Menu</h3></div>
<div class="battle-action-menu" aria-label="Battle actions">
<button class="btn primary" type="button"><span class="icon" aria-hidden="true"></span>Fight</button>
<button class="btn blue" type="button"><span class="icon" aria-hidden="true"></span>Bag</button>
<button class="btn" type="button"><span class="icon" aria-hidden="true"></span>Pokémon</button>
<button class="btn red" type="button"><span class="icon" aria-hidden="true"></span>Run</button>
</div>
</article>
<article class="card padded">
<div class="card-title"><h3>Bag Item Card</h3></div>
<div class="control-stack">
<div class="bag-card">
<span class="item-icon"></span>
<div><strong>Hyper Potion</strong><p class="muted">Restores 120 HP to one Pokémon.</p><span class="status-badge info">Qty 12</span></div>
</div>
<div class="bag-card">
<span class="item-icon"><span class="pokeball mini-ball" aria-hidden="true"></span></span>
<div><strong>Ultra Ball</strong><p class="muted">A high-performance Ball with a better catch rate.</p><span class="status-badge warning">Qty 4</span></div>
</div>
</div>
</article>
<article class="card padded">
<div class="card-title"><h3>Event / Reward</h3></div>
<div class="control-stack">
<div class="event-card">
<div>
<strong>Electric Spotlight Weekend</strong>
<p class="muted">Featured Pokémon, bonus candy, limited raid tasks.</p>
<div class="control-row" style="margin-top: 8px;">
<span class="type-chip type-image-chip type-electric"><img src="frontend/public/types/small/13.png" alt="" loading="lazy" /><span class="sr-only">Electric</span></span>
<span class="status-badge success">Open</span>
</div>
</div>
<span class="countdown">18h</span>
</div>
<div class="reward-shelf" aria-label="Reward shelf">
<div class="reward"><span class="gym-badge">1</span><span>Badge</span></div>
<div class="reward"><span class="item-icon"></span><span>Potion</span></div>
<div class="reward"><span class="pokeball" style="--ball-size: 44px;"></span><span>Ball</span></div>
<div class="reward is-locked"><span class="gym-badge">?</span><span>Locked</span></div>
<div class="reward is-locked"><span class="item-icon"></span><span>Secret</span></div>
</div>
</div>
</article>
</div>
</div>
</section>
<section class="section" id="feedback" aria-labelledby="feedback-title">
<div class="container">
<div class="section-header">
<span class="section-kicker">07 / Feedback & Overlays</span>
<h2 id="feedback-title">反馈与覆盖层</h2>
<p class="lead">反馈组件需要清晰表达系统状态。Poké Ball 可以作为提示图标,但错误、成功、等待、解锁仍要配合文字和颜色。</p>
</div>
<div class="grid two">
<article class="card padded">
<div class="card-title"><h3>Alerts</h3></div>
<div class="control-stack">
<div class="alert success">
<span class="alert-icon"></span>
<div><strong>Pokémon caught!</strong><p class="muted">Pikachu has been added to your Pokédex.</p></div>
<button class="btn small ghost" type="button">Undo</button>
</div>
<div class="alert warning">
<span class="alert-icon">!</span>
<div><strong>Bag almost full</strong><p class="muted">You have 2 slots left. Transfer items before the next battle.</p></div>
<button class="btn small ghost" type="button">Manage</button>
</div>
<div class="alert danger">
<span class="alert-icon">×</span>
<div><strong>Connection lost</strong><p class="muted">Battle data will retry automatically.</p></div>
<button class="btn small ghost" type="button">Retry</button>
</div>
</div>
</article>
<article class="card padded">
<div class="card-title"><h3>Modal / Toast / Tooltip</h3></div>
<div class="demo-surface">
<div class="control-row">
<button class="btn primary" type="button" data-open-modal><span class="icon" aria-hidden="true"></span>Open Modal</button>
<button class="btn blue" type="button" id="toastButton"><span class="icon" aria-hidden="true"></span>Show Toast</button>
<span class="tooltip-demo">
<button class="btn icon-btn ghost" type="button" aria-label="查看捕获率提示"><span class="icon" aria-hidden="true">?</span></button>
<span class="tooltip-bubble" role="tooltip">捕获率提示需要解释公式来源和影响因素。</span>
</span>
</div>
<div class="empty-state">
<span class="pokeball" style="--ball-size: 62px;" aria-hidden="true"></span>
<div>
<h3>No Pokémon found</h3>
<p class="muted">调整类型筛选或搜索其它区域。</p>
</div>
<button class="btn primary" type="button">Reset filters</button>
</div>
</div>
</article>
<article class="card padded">
<div class="card-title"><h3>Loading States</h3></div>
<div class="demo-surface">
<div class="skeleton">
<div class="skeleton-box"></div>
<div class="skeleton-line" style="width: 72%;"></div>
<div class="skeleton-line" style="width: 92%;"></div>
<div class="skeleton-line" style="width: 48%;"></div>
</div>
</div>
</article>
<article class="card padded">
<div class="card-title"><h3>Motion Rules</h3></div>
<ul class="rule-list">
<li>按钮 hover 上浮 2pxactive 下压 2px时长 120ms 至 180ms。</li>
<li>捕获、升级、徽章解锁可使用 300ms 左右的弹性动效。</li>
<li>列表筛选和 Tab 切换不使用大幅移动,避免影响扫描。</li>
<li>必须支持 <code>prefers-reduced-motion</code>,关闭闪烁和持续动画。</li>
</ul>
</article>
</div>
</div>
</section>
<section class="section band" id="navigation" aria-labelledby="navigation-title">
<div class="container">
<div class="section-header">
<span class="section-kicker">08 / Navigation</span>
<h2 id="navigation-title">导航模式</h2>
<p class="lead">Pokémon 产品常见信息架构包括 Pokédex、Team、Bag、Battle、Events、Shop 和 Trainer Profile。桌面端可用侧边导航移动端使用底部导航。</p>
</div>
<div class="grid two">
<article class="card padded">
<div class="card-title"><h3>Desktop Shell</h3></div>
<div class="side-nav-demo">
<nav class="side-rail" aria-label="桌面侧边导航示例">
<a href="#" class="is-active"><span class="pokeball mini-ball" aria-hidden="true"></span>Pokédex</a>
<a href="#"><span class="icon" aria-hidden="true"></span>Team</a>
<a href="#"><span class="icon" aria-hidden="true"></span>Bag</a>
<a href="#"><span class="icon" aria-hidden="true"></span>Battle</a>
<a href="#"><span class="icon" aria-hidden="true"></span>Shop</a>
</nav>
<div class="nav-preview">
<div class="breadcrumb" aria-label="面包屑示例">
<span><a href="#">Home</a></span>
<span><a href="#">Pokédex</a></span>
<span>Pikachu</span>
</div>
<div class="list-row">
<span class="pokeball mini-ball"></span>
<div><strong>Current section</strong><p class="muted">Use blue fill for active destination.</p></div>
<button class="btn small primary" type="button">Open</button>
</div>
<div class="pagination" aria-label="分页示例">
<a class="page-link" href="#"></a>
<a class="page-link is-active" href="#">1</a>
<a class="page-link" href="#">2</a>
<a class="page-link" href="#">3</a>
<a class="page-link" href="#"></a>
<a class="page-link" href="#">9</a>
<a class="page-link" href="#"></a>
</div>
</div>
</div>
</article>
<article class="card padded">
<div class="card-title"><h3>Mobile Bottom Nav</h3></div>
<div class="demo-surface">
<nav class="bottom-nav" aria-label="移动端底部导航示例">
<a href="#" class="is-active"><span class="icon" aria-hidden="true"></span><span>Dex</span></a>
<a href="#"><span class="icon" aria-hidden="true"></span><span>Team</span></a>
<a href="#"><span class="icon" aria-hidden="true"></span><span>Bag</span></a>
<a href="#"><span class="icon" aria-hidden="true"></span><span>Profile</span></a>
</nav>
<ul class="rule-list">
<li>底部导航最多 5 项,当前项使用浅黄色背景和深蓝文字。</li>
<li>图标下方标签必须保留,不能只靠图标表达目的地。</li>
<li>一级导航和页面内 Tabs 分离,避免把筛选项放入全局导航。</li>
<li>活动页可使用顶部锚点导航,核心工具优先使用 App Shell。</li>
</ul>
</div>
</article>
</div>
</div>
</section>
<section class="section" id="templates" aria-labelledby="templates-title">
<div class="container">
<div class="section-header">
<span class="section-kicker">09 / Templates</span>
<h2 id="templates-title">页面模板</h2>
<p class="lead">以下模板把控件组合成可直接落地的页面结构,覆盖图鉴列表、详情页、战斗状态和背包网格。</p>
</div>
<div class="template-grid">
<article class="template-panel">
<div class="template-toolbar">
<strong>Pokédex Listing</strong>
<button class="btn small primary" type="button">Filter</button>
</div>
<div class="template-body">
<div class="field search-field">
<label for="templateSearch">Search</label>
<input class="input" id="templateSearch" type="search" placeholder="Search National Dex" />
</div>
<div class="control-row">
<span class="type-chip type-image-chip type-electric"><img src="frontend/public/types/13.png" alt="" loading="lazy" /><span class="sr-only">Electric</span></span>
<span class="type-chip type-image-chip type-fire"><img src="frontend/public/types/10.png" alt="" loading="lazy" /><span class="sr-only">Fire</span></span>
<span class="type-chip type-image-chip type-water"><img src="frontend/public/types/11.png" alt="" loading="lazy" /><span class="sr-only">Water</span></span>
<span class="type-chip type-image-chip type-grass"><img src="frontend/public/types/12.png" alt="" loading="lazy" /><span class="sr-only">Grass</span></span>
</div>
<div class="dex-grid">
<div class="dex-mini-card"><span class="pokeball mini-ball"></span><strong>#025 Pikachu</strong><span class="type-chip type-image-chip type-electric"><img src="frontend/public/types/13.png" alt="" loading="lazy" /><span class="sr-only">Electric</span></span></div>
<div class="dex-mini-card"><span class="pokeball mini-ball"></span><strong>#006 Charizard</strong><span class="type-chip type-image-chip type-fire"><img src="frontend/public/types/10.png" alt="" loading="lazy" /><span class="sr-only">Fire</span></span></div>
<div class="dex-mini-card"><span class="pokeball mini-ball"></span><strong>#007 Squirtle</strong><span class="type-chip type-image-chip type-water"><img src="frontend/public/types/11.png" alt="" loading="lazy" /><span class="sr-only">Water</span></span></div>
<div class="dex-mini-card"><span class="pokeball mini-ball"></span><strong>#133 Eevee</strong><span class="type-chip type-image-chip type-normal"><img src="frontend/public/types/1.png" alt="" loading="lazy" /><span class="sr-only">Normal</span></span></div>
<div class="dex-mini-card"><span class="pokeball mini-ball"></span><strong>#143 Snorlax</strong><span class="type-chip type-image-chip type-normal"><img src="frontend/public/types/1.png" alt="" loading="lazy" /><span class="sr-only">Normal</span></span></div>
<div class="dex-mini-card"><span class="pokeball mini-ball"></span><strong>#150 Mewtwo</strong><span class="type-chip type-image-chip type-psychic"><img src="frontend/public/types/14.png" alt="" loading="lazy" /><span class="sr-only">Psychic</span></span></div>
</div>
</div>
</article>
<div class="control-stack">
<article class="template-panel">
<div class="template-toolbar">
<strong>Pokémon Detail</strong>
<span class="dex-number">#025</span>
</div>
<div class="template-body">
<div class="detail-layout">
<div class="detail-art"><div class="pikachu-face" role="img" aria-label="Pikachu 示例"></div></div>
<div class="control-stack">
<div>
<h3>Pikachu</h3>
<p class="muted">Mouse Pokémon · Electric</p>
</div>
<div class="control-row">
<span class="type-chip type-image-chip type-electric"><img src="frontend/public/types/13.png" alt="" loading="lazy" /><span class="sr-only">Electric</span></span>
<span class="hp-pill">HP 35</span>
</div>
<div class="progress">
<div class="progress-label"><span>Friendship</span><span>84%</span></div>
<div class="progress-track"><span class="progress-fill" style="width: 84%;"></span></div>
</div>
<button class="btn primary" type="button">Add to team</button>
</div>
</div>
</div>
</article>
<article class="template-panel">
<div class="template-toolbar">
<strong>Battle & Bag</strong>
<span class="status-badge warning">Live</span>
</div>
<div class="template-body">
<div class="battle-hud">
<div class="hud-row"><strong>Raichu Lv. 36</strong><span>HP 62/90</span></div>
<div class="hp-track"><span class="hp-fill" style="width: 69%;"></span></div>
</div>
<div class="inventory-grid">
<div class="item-slot"><span class="pokeball mini-ball"></span>Poké Ball</div>
<div class="item-slot"><span class="icon"></span>Potion</div>
<div class="item-slot"><span class="icon"></span>Rare Candy</div>
<div class="item-slot"><span class="icon"></span>Badge</div>
</div>
</div>
</article>
</div>
</div>
</div>
</section>
<section class="section band" id="handoff" aria-labelledby="handoff-title">
<div class="container">
<div class="section-header">
<span class="section-kicker">10 / Delivery Guidelines</span>
<h2 id="handoff-title">交付规范</h2>
<p class="lead">设计稿和前端实现需要以 token 与官方资产目录为唯一来源避免组件各自写死颜色和尺寸。Logo、角色图、商品图与 Type 图片必须有授权来源和版本记录。</p>
</div>
<div class="grid two">
<article class="card padded">
<div class="card-title"><h3>设计验收清单</h3></div>
<ul class="checklist">
<li><span>所有页面使用同一套 Pokémon brand tokens、type assets、type tokens、surface tokens。</span></li>
<li><span>Logo、角色图、官方插画、商品图均标注授权来源和使用范围。</span></li>
<li><span>核心控件覆盖 default、hover、active、focus、disabled、loading、error 状态。</span></li>
<li><span>图鉴列表、详情、Team Builder、Move Data、Weakness Matrix、背包、战斗状态、活动卡片均有桌面和移动端布局。</span></li>
<li><span>Type 展示优先使用 <code>frontend/public/types/</code> 图片资产,隐藏文本和 CSS 类型色作为语义与兜底。</span></li>
</ul>
</article>
<article class="card padded">
<div class="card-title"><h3>前端验收清单</h3></div>
<ul class="checklist">
<li><span>CSS 变量集中声明,不在组件内硬编码品牌色和类型色。</span></li>
<li><span>控件高度不低于 44px键盘焦点清晰弹窗有 focus 管理和 ESC 关闭。</span></li>
<li><span>Tab、Modal、Toast、Switch、Range、Combobox、Type Filter、Team Slot 等控件具备基础 ARIA 语义。</span></li>
<li><span>支持明暗主题与 reduced motion动画不会阻塞主要任务流程。</span></li>
<li><span>表格、列表、图鉴网格在 320px 宽度下不产生内容重叠。</span></li>
</ul>
</article>
</div>
<div class="code-block" style="margin-top: 18px;">
<pre><code>:root {
--pokemon-yellow: #ffcb05;
--pokemon-blue: #2a75bb;
--pokemon-blue-deep: #003a70;
--pokemon-red: #ee1515;
--line-strong: #1f2a3b;
--radius-card: 8px;
--radius-control: 8px;
--shadow-control: 0 3px 0 var(--line-strong);
}
.btn.primary {
min-height: 44px;
border: 2px solid var(--line-strong);
border-radius: var(--radius-control);
background: var(--pokemon-yellow);
color: #172036;
box-shadow: var(--shadow-control);
}</code></pre>
</div>
</div>
</section>
</main>
<footer class="container footer">
<p><strong>Pokémon UI Library Guidelines v3.0</strong>。本文件按“已获授权可使用 Pokémon 官方元素”的项目前提设计,适合用作网页产品、活动页、会员中心、图鉴工具、电商体验和 Team / Battle / Event 工具的 UI library 基线。</p>
</footer>
<div class="toast-stack" aria-live="polite" aria-atomic="true">
<div class="toast" id="toast">
<span class="pokeball mini-ball" aria-hidden="true"></span>
<div>
<strong>Item added</strong>
<p class="muted">Potion has been added to your Bag.</p>
</div>
</div>
</div>
<div class="modal-backdrop" id="guidelineModal" role="presentation">
<section class="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
<div class="modal-header">
<h3 id="modalTitle">授权项目使用说明</h3>
<button class="btn icon-btn ghost" type="button" data-close-modal aria-label="关闭弹窗"><span class="icon" aria-hidden="true">×</span></button>
</div>
<div class="modal-body">
<p>这版 guidelines 默认项目已被允许使用 Pokémon 官方元素。落地时请把 Logo、角色图、商品图等官方素材作为独立资产管理并在组件层使用 tokens、type 图片资产、Poké Ball、Pokédex、Team、Battle 和 Reward 结构保持一致性。</p>
<div class="alert warning">
<span class="alert-icon">!</span>
<div>
<strong>资产与组件分离</strong>
<p class="muted">官方插画不要直接写进组件 CSS组件只依赖尺寸、颜色、状态和插槽。</p>
</div>
<span></span>
</div>
</div>
<div class="modal-footer">
<button class="btn ghost" type="button" data-close-modal>Cancel</button>
<button class="btn primary" type="button" data-close-modal>Got it</button>
</div>
</section>
</div>
<script>
const root = document.documentElement;
const themeToggle = document.getElementById('themeToggle');
const modal = document.getElementById('guidelineModal');
const toast = document.getElementById('toast');
const toastButton = document.getElementById('toastButton');
const captureRange = document.getElementById('captureRange');
const captureValue = document.getElementById('captureValue');
let lastFocused = null;
let toastTimer = null;
themeToggle?.addEventListener('click', () => {
const nextTheme = root.dataset.theme === 'night' ? '' : 'night';
if (nextTheme) {
root.dataset.theme = nextTheme;
} else {
delete root.dataset.theme;
}
});
document.querySelectorAll('[data-open-modal]').forEach((button) => {
button.addEventListener('click', () => {
lastFocused = document.activeElement;
modal.classList.add('is-open');
document.body.classList.add('lock-scroll');
modal.querySelector('[data-close-modal]')?.focus();
});
});
function closeModal() {
modal.classList.remove('is-open');
document.body.classList.remove('lock-scroll');
lastFocused?.focus();
}
document.querySelectorAll('[data-close-modal]').forEach((button) => {
button.addEventListener('click', closeModal);
});
modal?.addEventListener('click', (event) => {
if (event.target === modal) closeModal();
});
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && modal.classList.contains('is-open')) {
closeModal();
}
});
toastButton?.addEventListener('click', () => {
window.clearTimeout(toastTimer);
toast.classList.add('show');
toastTimer = window.setTimeout(() => {
toast.classList.remove('show');
}, 2800);
});
captureRange?.addEventListener('input', () => {
captureValue.value = captureRange.value;
captureValue.textContent = captureRange.value;
});
document.querySelectorAll('[data-stepper]').forEach((stepper) => {
const output = stepper.querySelector('output');
stepper.querySelectorAll('button').forEach((button) => {
button.addEventListener('click', () => {
const next = Math.max(0, Number(output.textContent) + Number(button.dataset.step));
output.textContent = next;
});
});
});
document.querySelectorAll('.segmented').forEach((group) => {
group.querySelectorAll('button').forEach((button) => {
button.addEventListener('click', () => {
group.querySelectorAll('button').forEach((item) => item.setAttribute('aria-pressed', 'false'));
button.setAttribute('aria-pressed', 'true');
});
});
});
document.querySelectorAll('[data-tabs]').forEach((tabs) => {
const buttons = tabs.querySelectorAll('[role="tab"]');
const panels = tabs.querySelectorAll('[role="tabpanel"]');
buttons.forEach((button) => {
button.addEventListener('click', () => {
buttons.forEach((item) => item.setAttribute('aria-selected', 'false'));
panels.forEach((panel) => panel.classList.remove('is-active'));
button.setAttribute('aria-selected', 'true');
tabs.querySelector(`#${button.getAttribute('aria-controls')}`)?.classList.add('is-active');
});
});
});
const pokemonCombobox = document.getElementById('pokemonCombobox');
const pokemonSuggestions = document.getElementById('pokemonSuggestions');
const suggestionItems = [...document.querySelectorAll('.suggestion-item')];
function updateSuggestions() {
const query = pokemonCombobox.value.trim().toLowerCase();
let visibleCount = 0;
suggestionItems.forEach((item) => {
const isMatch = item.dataset.name.toLowerCase().includes(query);
item.style.display = isMatch ? '' : 'none';
if (isMatch) visibleCount += 1;
});
pokemonSuggestions.classList.toggle('is-open', visibleCount > 0 && document.activeElement === pokemonCombobox);
pokemonCombobox.setAttribute('aria-expanded', String(visibleCount > 0 && document.activeElement === pokemonCombobox));
}
pokemonCombobox?.addEventListener('input', updateSuggestions);
pokemonCombobox?.addEventListener('focus', updateSuggestions);
pokemonCombobox?.addEventListener('blur', () => {
window.setTimeout(() => {
pokemonSuggestions.classList.remove('is-open');
pokemonCombobox.setAttribute('aria-expanded', 'false');
}, 120);
});
suggestionItems.forEach((item) => {
item.addEventListener('click', () => {
pokemonCombobox.value = item.dataset.name;
suggestionItems.forEach((option) => option.classList.remove('is-active'));
item.classList.add('is-active');
pokemonSuggestions.classList.remove('is-open');
pokemonCombobox.setAttribute('aria-expanded', 'false');
});
});
const typeFilterSummary = document.getElementById('typeFilterSummary');
const typeToggles = [...document.querySelectorAll('.type-toggle')];
function updateTypeSummary() {
const selectedTypes = typeToggles
.filter((button) => button.getAttribute('aria-pressed') === 'true')
.map((button) => button.dataset.typeName);
typeFilterSummary.textContent = selectedTypes.length ? `Selected: ${selectedTypes.join(', ')}` : 'Selected: none';
}
typeToggles.forEach((button) => {
button.addEventListener('click', () => {
const isPressed = button.getAttribute('aria-pressed') === 'true';
button.setAttribute('aria-pressed', String(!isPressed));
updateTypeSummary();
});
});
document.getElementById('clearTypeFilters')?.addEventListener('click', () => {
typeToggles.forEach((button) => button.setAttribute('aria-pressed', 'false'));
updateTypeSummary();
});
document.querySelectorAll('.team-slot').forEach((slot) => {
slot.addEventListener('click', () => {
document.querySelectorAll('.team-slot').forEach((item) => item.classList.remove('is-selected'));
slot.classList.add('is-selected');
});
});
const chipTooltip = document.createElement('div');
chipTooltip.className = 'chip-tooltip';
chipTooltip.setAttribute('role', 'tooltip');
document.body.appendChild(chipTooltip);
function getChipLabel(chip) {
return chip.dataset.tooltip || chip.querySelector('.sr-only')?.textContent.trim() || '';
}
function positionChipTooltip(chip) {
const rect = chip.getBoundingClientRect();
const viewportPadding = 12;
chipTooltip.style.left = `${Math.min(Math.max(rect.left + rect.width / 2, viewportPadding), window.innerWidth - viewportPadding)}px`;
chipTooltip.style.top = `${Math.max(rect.top - chipTooltip.offsetHeight - 8, viewportPadding)}px`;
}
function showChipTooltip(chip) {
const label = getChipLabel(chip);
if (!label) return;
chipTooltip.textContent = `${label} type`;
chipTooltip.classList.add('is-visible');
positionChipTooltip(chip);
}
function hideChipTooltip() {
chipTooltip.classList.remove('is-visible');
}
document.querySelectorAll('.type-image-chip').forEach((chip) => {
const label = getChipLabel(chip);
if (!label) return;
chip.dataset.tooltip = label;
chip.setAttribute('aria-label', `${label} type`);
const trigger = chip.closest('.type-toggle, .team-slot, .weakness-cell, .suggestion-item, .dex-mini-card, .move-head, .event-card') || chip;
trigger.addEventListener('mouseenter', () => showChipTooltip(chip));
trigger.addEventListener('mouseleave', hideChipTooltip);
trigger.addEventListener('focusin', () => showChipTooltip(chip));
trigger.addEventListener('focusout', hideChipTooltip);
});
window.addEventListener('scroll', hideChipTooltip, { passive: true });
window.addEventListener('resize', hideChipTooltip);
</script>
</body>
</html>