Commit d2d5399d authored by shrabbit's avatar shrabbit
Browse files

feat: 新增地区与角色页面与配套组件

- 新增 /world-and-character 页面,含 4 个章节(问题/步骤/结果/关联)

- PageHero、CodeBlock、RegionMap、StepIndicator、AssociationDiagram 五个组件

- channels.ts 数据:CHARACTER/NOTE 频道字典 + 角色样本 + 5 段代码片段

- 关联图通过 sChanId 反查地区,悬停切换角色 + SVG 连线
parent 87aeaf1a
Loading
Loading
Loading
Loading
+391 −0
Original line number Diff line number Diff line
<template>
  <div class="assoc">
    <div class="assoc__col assoc__col--left">
      <header class="assoc__col-head">
        <span class="assoc__col-tag">CHARACTER</span>
        <span class="assoc__col-title">角色原始数据</span>
      </header>

      <div
        v-for="(char, i) in characters"
        :key="char.title"
        class="assoc__char"
        :class="{ 'is-active': activeIdx === i }"
        @mouseenter="activeIdx = i"
        @focusin="activeIdx = i"
        tabindex="0"
      >
        <div class="assoc__char-icon">
          <img
            v-if="char.icon"
            :src="char.icon"
            :alt="char.title"
            loading="lazy"
            decoding="async"
          />
          <span v-else class="assoc__char-fallback">{{ char.title.charAt(0) }}</span>
        </div>
        <div class="assoc__char-info">
          <span class="assoc__char-name">{{ char.title }}</span>
          <span class="assoc__char-tag">sChanId</span>
          <div class="assoc__char-ids">
            <span
              v-for="id in char.sChanId"
              :key="id"
              class="assoc__char-id"
              :class="{ 'is-match': matchedRegionFor(char, id) }"
            >
              {{ id }}
            </span>
          </div>
        </div>
      </div>
    </div>

    <!-- 连接线 -->
    <div class="assoc__lines" aria-hidden="true">
      <svg viewBox="0 0 100 100" preserveAspectRatio="none">
        <line
          v-for="(line, i) in activeLines"
          :key="i"
          :x1="line.x1" :y1="line.y1"
          :x2="line.x2" :y2="line.y2"
          stroke="var(--color-accent)"
          stroke-width="0.4"
          stroke-dasharray="2 2"
          class="assoc__line"
          :style="{ animationDelay: `${i * 0.1}s` }"
        />
      </svg>
    </div>

    <div class="assoc__col assoc__col--right">
      <header class="assoc__col-head">
        <span class="assoc__col-tag">DICTIONARY</span>
        <span class="assoc__col-title">频道字典反查</span>
      </header>

      <div
        v-for="entry in characterChannels"
        :key="entry.id"
        class="assoc__region"
        :data-tone="entry.tone"
        :class="{ 'is-match': activeMatchIds.includes(String(entry.id)) }"
      >
        <span class="assoc__region-dot"></span>
        <span class="assoc__region-name">{{ entry.cnName }}</span>
        <span class="assoc__region-key">{{ entry.name }}</span>
        <span class="assoc__region-id">{{ entry.id }}</span>
      </div>
    </div>

    <div class="assoc__result">
      <span class="assoc__result-label">推理结果</span>
      <span class="assoc__result-name" :data-tone="activeRegion?.tone || 'none'">
        {{ activeCharacter?.title }}{{ activeRegion?.cnName || '' }}
      </span>
    </div>
  </div>
</template>

<script setup lang="ts">
import { characterChannels, characterSamples, type CharacterSample } from '~/data/channels'

const characters = characterSamples
const activeIdx = ref(0)
const activeCharacter = computed(() => characters[activeIdx.value])

/** 当前角色的所有匹配 ID(字符串形式) */
const activeMatchIds = computed(() => activeCharacter.value?.sChanId ?? [])

/** 当前角色的地区(剔除 detail) */
const activeRegion = computed(() => {
  if (!activeCharacter.value) return null
  for (const id of activeCharacter.value.sChanId) {
    const hit = characterChannels.find((c) => String(c.id) === id && c.name !== 'detail')
    if (hit) return hit
  }
  return null
})

const matchedRegionFor = (_char: CharacterSample, id: string) => {
  return characterChannels.some((c) => String(c.id) === id && c.name !== 'detail')
}

/** 简易连线坐标:从左侧角色 ID 列指向右侧匹配的地区行 */
const activeLines = computed(() => {
  const char = activeCharacter.value
  if (!char) return []
  const charIdx = characters.findIndex((c) => c.title === char.title)
  const charY = 0.18 + charIdx * 0.18

  return char.sChanId
    .map((id) => {
      const regionIdx = characterChannels.findIndex(
        (c) => String(c.id) === id && c.name !== 'detail'
      )
      if (regionIdx < 0) return null
      const regionY = 0.12 + regionIdx * 0.085
      return {
        x1: 0,
        y1: charY,
        x2: 100,
        y2: regionY
      }
    })
    .filter(Boolean) as { x1: number; y1: number; x2: number; y2: number }[]
})
</script>

<style scoped>
.assoc {
  display: grid;
  grid-template-columns: 1fr 80px 1fr;
  gap: var(--space-4);
  align-items: stretch;
  position: relative;
  padding: var(--space-6);
  background: var(--color-bg-elev);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-lg);
}

.assoc__col {
  display: flex;
  flex-direction: column;
}

.assoc__col-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding-bottom: var(--space-3);
  margin-bottom: var(--space-3);
  border-bottom: 1px solid var(--color-line-faint);
}

.assoc__col-tag {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.25em;
  color: var(--color-accent);
}

.assoc__col-title {
  font-size: 0.8125rem;
  color: var(--color-text-muted);
}

/* —— 左侧角色 —— */
.assoc__char {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: all var(--duration-base) var(--ease-out-quint);
  margin-bottom: var(--space-2);
  outline: none;
}

.assoc__char:hover,
.assoc__char:focus-visible,
.assoc__char.is-active {
  background: rgba(212, 164, 90, 0.05);
  border-color: var(--color-line);
}

.assoc__char-icon {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--color-bg-elev-2);
  border: 1px solid var(--color-line);
  flex-shrink: 0;
  display: grid;
  place-items: center;
}

.assoc__char-icon img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.assoc__char-fallback {
  font-family: var(--font-serif);
  color: var(--color-accent);
  font-size: 1.5rem;
}

.assoc__char-info {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1;
}

.assoc__char-name {
  font-family: var(--font-serif);
  font-size: 1rem;
  font-weight: 600;
  color: var(--color-text-primary);
}

.assoc__char-tag {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.15em;
  color: var(--color-text-muted);
}

.assoc__char-ids {
  display: flex;
  gap: 4px;
}

.assoc__char-id {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  padding: 1px 6px;
  border-radius: 2px;
  background: rgba(241, 233, 210, 0.05);
  color: var(--color-text-muted);
  border: 1px solid var(--color-line-faint);
  transition: all var(--duration-base) var(--ease-out-quint);
}

.assoc__char-id.is-match {
  background: rgba(212, 164, 90, 0.12);
  color: var(--color-accent);
  border-color: var(--color-accent);
}

/* —— 中间连线 —— */
.assoc__lines {
  position: relative;
  align-self: stretch;
  min-height: 200px;
}

.assoc__lines svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.assoc__line {
  opacity: 0;
  animation: line-appear 0.4s var(--ease-out-quint) forwards;
  filter: drop-shadow(0 0 4px var(--color-accent-glow));
}

@keyframes line-appear {
  to { opacity: 0.8; }
}

/* —— 右侧地区 —— */
.assoc__region {
  display: grid;
  grid-template-columns: 8px 1fr auto auto;
  gap: var(--space-3);
  align-items: center;
  padding: 8px 12px;
  border-bottom: 1px solid var(--color-line-faint);
  font-size: 0.8125rem;
  transition: background var(--duration-fast) var(--ease-out-quint);
}

.assoc__region:last-child { border-bottom: none; }

.assoc__region:hover {
  background: rgba(212, 164, 90, 0.04);
}

.assoc__region-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--tone-color, var(--color-text-muted));
  opacity: 0.5;
  transition: all var(--duration-base) var(--ease-out-quint);
}

.assoc__region[data-tone='anemo']    { --tone-color: var(--color-anemo); }
.assoc__region[data-tone='geo']      { --tone-color: var(--color-geo); }
.assoc__region[data-tone='electro']  { --tone-color: var(--color-electro); }
.assoc__region[data-tone='dendro']   { --tone-color: var(--color-dendro); }
.assoc__region[data-tone='hydro']    { --tone-color: var(--color-hydro); }
.assoc__region[data-tone='pyro']     { --tone-color: var(--color-pyro); }
.assoc__region[data-tone='cryo']     { --tone-color: var(--color-cryo); }
.assoc__region[data-tone='none']     { --tone-color: var(--color-accent); }

.assoc__region.is-match {
  background: rgba(212, 164, 90, 0.08);
}

.assoc__region.is-match .assoc__region-dot {
  opacity: 1;
  box-shadow: 0 0 8px currentColor;
}

.assoc__region-name {
  color: var(--color-text-primary);
}

.assoc__region-key {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--color-text-muted);
}

.assoc__region-id {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--tone-color, var(--color-accent));
  font-weight: 500;
}

.assoc__region.is-match .assoc__region-id {
  color: var(--color-accent);
}

/* —— 推理结果 —— */
.assoc__result {
  grid-column: 1 / -1;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: var(--space-4) var(--space-5);
  background: var(--color-bg-deep);
  border-top: 1px solid var(--color-line);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
  margin: var(--space-2) calc(-1 * var(--space-6)) calc(-1 * var(--space-6));
}

.assoc__result-label {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.assoc__result-name {
  font-family: var(--font-serif);
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--color-accent);
  transition: color var(--duration-base) var(--ease-out-quint);
}

@media (max-width: 860px) {
  .assoc {
    grid-template-columns: 1fr;
  }
  .assoc__lines { display: none; }
}
</style>
+204 −0
Original line number Diff line number Diff line
<template>
  <div class="code-block" :class="{ 'is-copied': justCopied }">
    <header class="code-block__header">
      <span class="code-block__lang">{{ lang || 'text' }}</span>
      <div class="code-block__actions">
        <span v-if="filename" class="code-block__filename">{{ filename }}</span>
        <button
          class="code-block__copy"
          type="button"
          :aria-label="justCopied ? '已复制' : '复制代码'"
          @click="copy"
        >
          <svg v-if="!justCopied" width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true">
            <rect x="9" y="9" width="11" height="11" rx="1.5" stroke="currentColor" stroke-width="1.6"/>
            <path d="M5 15V5a1 1 0 0 1 1-1h10" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
          </svg>
          <svg v-else width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true">
            <path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
          </svg>
          <span>{{ justCopied ? '已复制' : '复制' }}</span>
        </button>
      </div>
    </header>

    <pre class="code-block__pre"><code><ol class="code-block__lines"><li v-for="(line, i) in lines" :key="i"><span class="code-block__ln" aria-hidden="true">{{ i + 1 }}</span><span class="code-block__code" v-html="highlight(line)"></span></li></ol></code></pre>
  </div>
</template>

<script setup lang="ts">
interface Props {
  code: string
  lang?: string
  filename?: string
}
const props = defineProps<Props>()

const justCopied = ref(false)

const lines = computed(() => props.code.split('\n'))

const copy = async () => {
  try {
    await navigator.clipboard.writeText(props.code)
    justCopied.value = true
    setTimeout(() => (justCopied.value = false), 1600)
  } catch (e) {
    console.warn('复制失败', e)
  }
}

/**
 * 极简高亮:仅做关键字、字符串、注释、数字的简单着色。
 * 不引入完整 parser,避免增加包体积与复杂度。
 */
const highlight = (line: string) => {
  // 先转义 HTML
  let s = line
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')

  // 注释(行内)
  s = s.replace(/(\/\/.*)$/g, '<span class="cb-comment">$1</span>')

  // 字符串
  s = s.replace(
    /("[^"]*"|'[^']*'|`[^`]*`)/g,
    '<span class="cb-string">$1</span>'
  )

  // 关键字
  s = s.replace(
    /\b(var|let|const|function|return|if|else|for|foreach|new|public|private|static|partial|class|void|string|int|bool|async|await|true|false|null)\b/g,
    '<span class="cb-keyword">$1</span>'
  )

  // 数字
  s = s.replace(/\b(\d+)\b/g, '<span class="cb-number">$1</span>')

  return s
}
</script>

<style scoped>
.code-block {
  background: var(--color-bg-deep);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-md);
  overflow: hidden;
  position: relative;
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  line-height: 1.7;
  transition: border-color var(--duration-base) var(--ease-out-quint);
}

.code-block:hover { border-color: var(--color-line-strong); }

.code-block__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 14px;
  background: rgba(212, 164, 90, 0.04);
  border-bottom: 1px solid var(--color-line);
}

.code-block__lang {
  font-size: 0.6875rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--color-accent);
}

.code-block__actions {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.code-block__filename {
  font-size: 0.75rem;
  color: var(--color-text-muted);
}

.code-block__copy {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: transparent;
  border: 1px solid var(--color-line);
  color: var(--color-text-secondary);
  padding: 4px 10px;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.05em;
  cursor: pointer;
  border-radius: 2px;
  transition: all var(--duration-fast) var(--ease-out-quint);
}

.code-block__copy:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.code-block.is-copied .code-block__copy {
  border-color: var(--color-accent);
  color: var(--color-accent);
  background: rgba(212, 164, 90, 0.08);
}

.code-block__pre {
  margin: 0;
  padding: var(--space-4) var(--space-2) var(--space-4) 0;
  overflow-x: auto;
}

.code-block__lines {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: none;
}

.code-block__lines li {
  display: flex;
  align-items: baseline;
  gap: 0;
  padding: 0 var(--space-4) 0 0;
  transition: background var(--duration-fast) var(--ease-out-quint);
}

.code-block__lines li:hover {
  background: rgba(212, 164, 90, 0.04);
}

.code-block__ln {
  display: inline-block;
  min-width: 2.5em;
  padding-right: 1em;
  text-align: right;
  color: var(--color-text-muted);
  opacity: 0.5;
  user-select: none;
  font-size: 0.75rem;
}

.code-block__code {
  flex: 1;
  white-space: pre;
  color: var(--color-text-primary);
}

/* 高亮 token */
.code-block :deep(.cb-keyword) { color: var(--color-electro); }
.code-block :deep(.cb-string) { color: var(--color-anemo); }
.code-block :deep(.cb-comment) {
  color: var(--color-text-muted);
  font-style: italic;
  opacity: 0.7;
}
.code-block :deep(.cb-number) { color: var(--color-pyro); }
</style>
+153 −0
Original line number Diff line number Diff line
<template>
  <div class="page-hero">
    <div class="page-hero__bg">
      <div class="page-hero__grid"></div>
      <div class="page-hero__aurora"></div>
    </div>

    <div class="container page-hero__inner">
      <div class="page-hero__head" data-reveal>
        <NuxtLink to="/" class="page-hero__back">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true">
            <path d="M19 12H5M11 18l-6-6 6-6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
          </svg>
          返回首页
        </NuxtLink>
        <span class="page-hero__index">{{ index }}</span>
      </div>

      <h1 class="page-hero__title" data-reveal data-stagger="1">
        <slot name="title">{{ title }}</slot>
      </h1>

      <p class="page-hero__subtitle" data-reveal data-stagger="2">
        <slot name="subtitle">{{ subtitle }}</slot>
      </p>

      <div class="page-hero__meta" data-reveal data-stagger="3">
        <span v-for="tag in tags" :key="tag" class="tag">{{ tag }}</span>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
interface Props {
  index: string
  title?: string
  subtitle?: string
  tags?: string[]
}
withDefaults(defineProps<Props>(), {
  title: '',
  subtitle: '',
  tags: () => []
})
</script>

<style scoped>
.page-hero {
  position: relative;
  padding-top: var(--space-16);
  padding-bottom: var(--space-16);
  overflow: hidden;
  border-bottom: 1px solid var(--color-line);
}

.page-hero__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.page-hero__grid {
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(rgba(212, 164, 90, 0.03) 1px, transparent 1px),
    linear-gradient(90deg, rgba(212, 164, 90, 0.03) 1px, transparent 1px);
  background-size: 64px 64px;
  mask-image: linear-gradient(180deg, black, transparent 80%);
  -webkit-mask-image: linear-gradient(180deg, black, transparent 80%);
}

.page-hero__aurora {
  position: absolute;
  top: -40%;
  left: 10%;
  width: 500px;
  height: 500px;
  background: radial-gradient(circle, var(--tone, var(--color-accent)) 0%, transparent 60%);
  opacity: 0.12;
  filter: blur(60px);
  animation: float-slow 8s ease-in-out infinite;
}

.page-hero__inner {
  position: relative;
  z-index: 1;
}

.page-hero__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-6);
}

.page-hero__back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--color-text-muted);
  text-decoration: none;
  font-size: 0.75rem;
  font-family: var(--font-mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  transition: color var(--duration-base) var(--ease-out-quint);
}

.page-hero__back:hover {
  color: var(--color-accent);
}

.page-hero__back svg {
  transition: transform var(--duration-base) var(--ease-out-quint);
}

.page-hero__back:hover svg {
  transform: translateX(-3px);
}

.page-hero__index {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.25em;
  color: var(--color-text-muted);
}

.page-hero__title {
  font-family: var(--font-serif);
  font-size: clamp(2.5rem, 6vw, 4.5rem);
  font-weight: 700;
  line-height: 1.1;
  margin-bottom: var(--space-5);
  color: var(--color-text-primary);
}

.page-hero__subtitle {
  font-size: clamp(1rem, 1.4vw, 1.125rem);
  color: var(--color-text-secondary);
  max-width: 680px;
  line-height: 1.7;
  margin-bottom: var(--space-6);
}

.page-hero__meta {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}
</style>
+188 −0
Original line number Diff line number Diff line
<template>
  <div class="region-map">
    <header class="region-map__header">
      <span class="region-map__title">{{ title }}</span>
      <span class="region-map__count">{{ entries.length }}</span>
    </header>

    <div class="region-map__table">
      <div class="region-map__row region-map__row--head">
        <span>#</span>
        <span>键 (key)</span>
        <span>中文名</span>
        <span class="region-map__id-col">频道 ID</span>
      </div>

      <div
        v-for="(entry, i) in entries"
        :key="entry.name"
        class="region-map__row"
        :data-tone="entry.tone || 'none'"
        :style="{ animationDelay: `${i * 50}ms` }"
      >
        <span class="region-map__idx">{{ String(i + 1).padStart(2, '0') }}</span>
        <span class="region-map__key">
          <code>{{ entry.name }}</code>
        </span>
        <span class="region-map__cn">{{ entry.cnName }}</span>
        <span class="region-map__id">
          <span class="region-map__id-pill">{{ entry.id }}</span>
        </span>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import type { ChannelEntry } from '~/data/channels'

defineProps<{
  entries: ChannelEntry[]
  title: string
}>()
</script>

<style scoped>
.region-map {
  background: var(--color-bg-elev);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.region-map__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--color-line);
  background: linear-gradient(180deg, rgba(212, 164, 90, 0.05), transparent);
}

.region-map__title {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--color-accent);
}

.region-map__count {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--color-text-muted);
}

.region-map__table {
  display: grid;
  grid-template-columns: minmax(40px, auto) 1fr 1fr auto;
}

.region-map__row {
  display: contents;
}

.region-map__row > span {
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--color-line-faint);
  display: flex;
  align-items: center;
  font-size: 0.875rem;
  transition: background var(--duration-fast) var(--ease-out-quint);
}

.region-map__row:not(.region-map__row--head) > span {
  position: relative;
}

.region-map__row:not(.region-map__row--head):hover > span {
  background: rgba(212, 164, 90, 0.04);
}

.region-map__row > span:first-child {
  position: relative;
}

.region-map__row:not(.region-map__row--head) > span:first-child::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--tone-color, transparent);
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-out-quint);
}

.region-map__row:not(.region-map__row--head):hover > span:first-child::before {
  opacity: 1;
}

/* Tone 颜色 */
.region-map__row[data-tone='anemo']    { --tone-color: var(--color-anemo); }
.region-map__row[data-tone='geo']      { --tone-color: var(--color-geo); }
.region-map__row[data-tone='electro']  { --tone-color: var(--color-electro); }
.region-map__row[data-tone='dendro']   { --tone-color: var(--color-dendro); }
.region-map__row[data-tone='hydro']    { --tone-color: var(--color-hydro); }
.region-map__row[data-tone='pyro']      { --tone-color: var(--color-pyro); }
.region-map__row[data-tone='cryo']     { --tone-color: var(--color-cryo); }
.region-map__row[data-tone='none']     { --tone-color: var(--color-accent); }

.region-map__row--head > span {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  background: var(--color-bg-elev-2);
  border-bottom: 1px solid var(--color-line);
}

.region-map__idx {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--color-text-muted);
  letter-spacing: 0.05em;
}

.region-map__key code {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  color: var(--color-text-primary);
  background: rgba(212, 164, 90, 0.06);
  padding: 2px 8px;
  border-radius: 2px;
  border: 1px solid var(--color-line-faint);
}

.region-map__cn {
  color: var(--color-text-secondary);
  font-size: 0.875rem;
}

.region-map__id {
  justify-content: flex-end;
}

.region-map__id-pill {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  color: var(--tone-color, var(--color-accent));
  background: rgba(212, 164, 90, 0.06);
  border: 1px solid var(--color-line);
  padding: 2px 10px;
  border-radius: 999px;
  min-width: 56px;
  text-align: center;
  font-weight: 500;
}

@media (max-width: 540px) {
  .region-map__table {
    grid-template-columns: 32px 1fr auto;
  }
  .region-map__cn { display: none; }
  .region-map__row--head > span:nth-child(3) { display: none; }
}
</style>
+178 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading