<script setup lang="ts">
import { computed, ref } from 'vue'
import { RouterLink } from 'vue-router'

type StageTone = 'indigo' | 'blue' | 'cyan' | 'amber' | 'green' | 'violet'

interface FlowStage {
  id: string
  index: string
  title: string
  subtitle: string
  summary: string
  input: string
  output: string
  tools: string[]
  constraints: string[]
  tone: StageTone
}

const stages: FlowStage[] = [
  {
    id: 'context',
    index: '01',
    title: '上下文就绪',
    subtitle: 'CONTEXT READY',
    summary: '调度器装配需求、参考视频和拓展点位，并在模型运行前预创建唯一 run_id。',
    input: 'S / A 级需求、业务日期、参考视频、目的点 / 关键点 / 灵感点',
    output: 'FindDemandContext、输入快照、status=running 的运行记录',
    tools: ['调度器预创建（非 Agent Tool）'],
    constraints: ['没有有效点位的需求不进入运行', '同一业务日与需求只自动执行一次', '后续写库必须复用预创建 run_id'],
    tone: 'indigo',
  },
  {
    id: 'search',
    index: '02',
    title: '搜索召回',
    subtitle: 'SEARCH & RECALL',
    summary: '模型规划多个语义不同的搜索词，按关键词、翻页、标签或作者扩展候选。',
    input: '需求意图、参考标题、拓展点位、既有搜索前沿',
    output: '搜索页、原始搜索证据、带 candidate_id 的候选记录',
    tools: ['batch_search_and_record', 'douyin_search', 'douyin_search_tikhub', 'douyin_user_videos'],
    constraints: ['批量搜索最多 6 个任务、每项最多 2 页', '每页立即落库，不覆盖历史搜索', '同一视频被不同搜索命中时保留独立候选'],
    tone: 'blue',
  },
  {
    id: 'evidence',
    index: '03',
    title: '证据补全',
    subtitle: 'EVIDENCE',
    summary: '对高潜候选获取详情、内容点赞画像与作者粉丝画像，并统一标准化年龄证据。',
    input: 'run_id、候选 candidate_ids、搜索阶段已有的基础信息',
    output: '详情证据、双侧画像、年龄标准化结果、刷新后的门禁状态',
    tools: ['douyin_detail', 'batch_fetch_portraits', 'get_content_fans_portrait', 'get_account_fans_portrait', 'normalize_age_portraits'],
    constraints: ['详情和批量画像单次最多 8 条', '优先使用 run_id + candidate_ids 新模式', '数据缺失是未知，不直接等于负证据'],
    tone: 'cyan',
  },
  {
    id: 'decision',
    index: '04',
    title: '评分与门禁',
    subtitle: 'DECISION GATE',
    summary: '模型判断 R / E / S / V，代码用冻结的 P0 规则快照决定候选能否进入主推荐。',
    input: '相关性、受众倾向、分享信号、时效、时长与互动证据',
    output: 'primary / rejected / pending_evaluation 与版本化门禁结果',
    tools: ['batch_update_video_discovery_candidates'],
    constraints: ['primary 必须通过 P0 门禁', '未补齐证据时自动回到 pending_evaluation', '门禁失败的 primary 自动重分类为 rejected'],
    tone: 'amber',
  },
  {
    id: 'verify',
    index: '05',
    title: '状态核对',
    subtitle: 'STATE VERIFY',
    summary: '重新从数据库读取运行、搜索和候选，判断继续搜索是否还能改变结果。',
    input: '当前 run_id 与已经持久化的全量执行轨迹',
    output: 'run / searches / candidates 的一致性快照',
    tools: ['query_video_discovery_state'],
    constraints: ['最终判断以数据库状态为准', '需要区分 primary_count 与 valid_primary_count', '剩余前沿无信息价值时才停止搜索'],
    tone: 'green',
  },
  {
    id: 'finish',
    index: '06',
    title: '完成与输出',
    subtitle: 'FINALIZE',
    summary: '归档未评估候选、计算业务结果，再由模型输出面向用户的执行摘要。',
    input: '最终候选池、意图摘要、停止原因',
    output: 'finished / failed 与 goal_met / partial / no_match / failed',
    tools: ['update_video_discovery_run_status'],
    constraints: ['finished 会归档全部待评估候选', '有效 primary ≥ 5 才是 goal_met', '最终文本不能替代 outcome_status'],
    tone: 'violet',
  },
]

const activeStageId = ref('search')
const activeStage = computed(() => stages.find((stage) => stage.id === activeStageId.value) ?? stages[0])

const dataStores = [
  { name: 'RUN', table: 'video_discovery_run', detail: '运行状态 · 业务结果 · 规则快照' },
  { name: 'SEARCH', table: 'video_discovery_search', detail: '搜索词 · 父子关系 · 分页状态' },
  { name: 'CANDIDATE', table: 'video_discovery_candidate', detail: '候选证据 · R/E/S/V · 最终分池' },
  { name: 'EVIDENCE', table: 'video_discovery_evidence', detail: '搜索 / 详情 / 画像原始响应' },
  { name: 'GATE', table: 'video_discovery_gate_evaluation', detail: '规则版本 · 证据版本 · 失败原因' },
]
</script>

<template>
  <div class="framework-page">
    <header class="page-hero">
      <div class="hero-copy">
        <span class="eyebrow">FIND AGENT · SYSTEM MAP</span>
        <h1>寻找 Agent 框架流程</h1>
        <p>从标准需求输入到可追溯候选输出，展示模型循环、工具编排、证据门禁和持久化状态之间的关系。</p>
        <div class="hero-actions">
          <RouterLink to="/find-agent-records" class="primary-link">查看运行记录 <span>→</span></RouterLink>
          <a href="#stage-flow" class="secondary-link">浏览执行阶段</a>
        </div>
      </div>

      <div class="runtime-boundary" aria-label="运行边界">
        <span class="boundary-label">RUNTIME BOUNDARY</span>
        <div class="boundary-grid">
          <div><small>MODEL</small><strong>Gemini 3 Flash</strong></div>
          <div><small>REACT</small><strong>最多 60 轮</strong></div>
          <div><small>TIMEOUT</small><strong>默认 600 秒</strong></div>
          <div><small>TOOLS</small><strong>13 个注册工具</strong></div>
        </div>
      </div>
    </header>

    <section class="architecture-map" aria-labelledby="architecture-title">
      <div class="section-heading">
        <div>
          <span>01 · ORCHESTRATION</span>
          <h2 id="architecture-title">框架总览</h2>
        </div>
        <p>模型负责决定下一步，工具负责读取与写入事实，完成守卫负责阻止没有业务结果的提前结束。</p>
      </div>

      <div class="kernel-flow">
        <article class="boundary-node input-node">
          <div class="node-icon">IN</div>
          <div>
            <span>标准任务输入</span>
            <strong>需求 + 参考视频 + 拓展点位</strong>
            <small>调度器预创建 run_id</small>
          </div>
        </article>

        <span class="flow-connector" aria-hidden="true"><i /><b>→</b></span>

        <article class="agent-kernel">
          <header>
            <div>
              <span>FIND_AGENT KERNEL</span>
              <strong>LLM ReAct 控制器</strong>
            </div>
            <em>异步顺序工具调用</em>
          </header>
          <div class="react-loop" aria-label="ReAct 循环">
            <div><b>01</b><span>Reason</span><small>理解状态</small></div>
            <i>→</i>
            <div><b>02</b><span>Act</span><small>选择工具</small></div>
            <i>→</i>
            <div><b>03</b><span>Observe</span><small>解析结果</small></div>
            <i>↺</i>
          </div>
          <footer>
            <span><i class="dot model" />系统 Prompt</span>
            <span><i class="dot tool" />Tool Registry</span>
            <span><i class="dot guard" />Completion Guard</span>
          </footer>
        </article>

        <span class="flow-connector" aria-hidden="true"><i /><b>→</b></span>

        <article class="boundary-node output-node">
          <div class="node-icon">OUT</div>
          <div>
            <span>结构化业务结果</span>
            <strong>候选池 + 证据 + 结果状态</strong>
            <small>goal_met / partial / no_match</small>
          </div>
        </article>
      </div>

      <div class="guard-rail">
        <span>完成守卫</span>
        <i />
        <p><strong>status=finished</strong> 且存在业务结果时，模型的无工具回答才可成为最终输出。</p>
      </div>
    </section>

    <section id="stage-flow" class="stage-section" aria-labelledby="stage-title">
      <div class="section-heading">
        <div>
          <span>02 · BUSINESS FLOW</span>
          <h2 id="stage-title">六阶段执行链</h2>
        </div>
        <p>选择阶段查看真实输入、输出、注册工具与代码硬约束。</p>
      </div>

      <div class="stage-flow" role="tablist" aria-label="寻找 Agent 执行阶段">
        <template v-for="(stage, index) in stages" :key="stage.id">
          <button
            type="button"
            role="tab"
            :aria-selected="activeStageId === stage.id"
            :class="['stage-node', stage.tone, { active: activeStageId === stage.id }]"
            @click="activeStageId = stage.id"
          >
            <span class="stage-index">{{ stage.index }}</span>
            <small>{{ stage.subtitle }}</small>
            <strong>{{ stage.title }}</strong>
            <i>{{ stage.id === 'context' ? '调度入口' : `${stage.tools.length} 工具` }}</i>
          </button>
          <span v-if="index < stages.length - 1" class="stage-arrow" aria-hidden="true"><i />→</span>
        </template>
      </div>

      <article class="stage-detail" :class="activeStage.tone">
        <div class="detail-lead">
          <span>{{ activeStage.index }} / {{ activeStage.subtitle }}</span>
          <h3>{{ activeStage.title }}</h3>
          <p>{{ activeStage.summary }}</p>
        </div>

        <dl class="io-grid">
          <div>
            <dt><span>IN</span> 阶段输入</dt>
            <dd>{{ activeStage.input }}</dd>
          </div>
          <div>
            <dt><span>OUT</span> 阶段输出</dt>
            <dd>{{ activeStage.output }}</dd>
          </div>
        </dl>

        <div class="detail-columns">
          <section>
            <span class="detail-label">CALLABLE TOOLS</span>
            <div class="tool-list">
              <code v-for="tool in activeStage.tools" :key="tool">{{ tool }}</code>
            </div>
          </section>
          <section>
            <span class="detail-label">HARD CONSTRAINTS</span>
            <ul>
              <li v-for="constraint in activeStage.constraints" :key="constraint">{{ constraint }}</li>
            </ul>
          </section>
        </div>
      </article>
    </section>

    <section class="data-plane" aria-labelledby="data-title">
      <div class="section-heading compact">
        <div>
          <span>03 · DATA PLANE</span>
          <h2 id="data-title">证据与状态底座</h2>
        </div>
        <p>Agent Tools 不直接写 SQL，统一经过 Service 与 Repository 操作持久化实体。</p>
      </div>

      <div class="data-pipeline">
        <div class="service-gateway">
          <span>SERVICE GATEWAY</span>
          <strong>VideoDiscoveryService</strong>
          <small>事务 · 序列化 · 规则快照</small>
        </div>
        <span class="data-arrow">↓</span>
        <div class="store-grid">
          <article v-for="store in dataStores" :key="store.name">
            <span>{{ store.name }}</span>
            <strong>{{ store.table }}</strong>
            <small>{{ store.detail }}</small>
          </article>
        </div>
      </div>
    </section>

    <footer class="page-footnote">
      <span><i class="legend-dot model" />模型决策</span>
      <span><i class="legend-dot tool" />工具执行</span>
      <span><i class="legend-dot guard" />代码门禁</span>
      <p>当前实现 · 2026-08-04</p>
    </footer>
  </div>
</template>

<style scoped>
.framework-page {
  --ink: #182033;
  --muted: #6d7586;
  --line: #e3e6ed;
  display: flex;
  max-width: 1480px;
  margin: 0 auto;
  padding: 0 4px 38px;
  flex-direction: column;
  gap: 22px;
  color: var(--ink);
}

.page-hero,
.architecture-map,
.stage-section,
.data-plane {
  border: 1px solid var(--line);
  border-radius: 20px;
  background: rgba(255, 255, 255, .94);
  box-shadow: 0 14px 42px rgba(35, 43, 64, .055);
}

.page-hero {
  position: relative;
  display: grid;
  min-height: 260px;
  padding: 38px 42px;
  overflow: hidden;
  grid-template-columns: minmax(0, 1.15fr) minmax(420px, .85fr);
  align-items: center;
  gap: 38px;
  background:
    radial-gradient(circle at 88% 12%, rgba(103, 98, 207, .14), transparent 31%),
    linear-gradient(135deg, #fff 0 57%, #f8f8fc 57%);
}

.page-hero::after {
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(rgba(89, 98, 127, .045) 1px, transparent 1px),
    linear-gradient(90deg, rgba(89, 98, 127, .045) 1px, transparent 1px);
  background-size: 28px 28px;
  mask-image: linear-gradient(90deg, transparent 46%, #000);
  content: '';
  pointer-events: none;
}

.hero-copy,
.runtime-boundary { position: relative; z-index: 1; }
.eyebrow,
.section-heading div > span,
.boundary-label,
.detail-label {
  color: #6c6bd0;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .12em;
}

.hero-copy h1 {
  margin: 8px 0 10px;
  font-size: clamp(30px, 3vw, 45px);
  line-height: 1.1;
  letter-spacing: -.035em;
}

.hero-copy > p {
  max-width: 650px;
  margin: 0;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.8;
}

.hero-actions { display: flex; margin-top: 22px; align-items: center; gap: 12px; }
.hero-actions a {
  display: inline-flex;
  height: 38px;
  padding: 0 15px;
  align-items: center;
  gap: 14px;
  border-radius: 9px;
  font-size: 13px;
  font-weight: 750;
  text-decoration: none;
}
.primary-link { background: #5e5dc8; color: #fff; }
.primary-link:hover { background: #5352bb; }
.secondary-link { border: 1px solid #dfe1e8; background: #fff; color: #697184; }
.secondary-link:hover { border-color: #c9c9eb; color: #5555b7; }

.runtime-boundary {
  padding: 20px;
  border: 1px solid rgba(98, 96, 194, .16);
  border-radius: 16px;
  background: rgba(255, 255, 255, .78);
  backdrop-filter: blur(9px);
}
.boundary-grid { display: grid; margin-top: 12px; grid-template-columns: 1fr 1fr; gap: 8px; }
.boundary-grid div { padding: 12px; border-radius: 10px; background: rgba(244, 244, 250, .88); }
.boundary-grid small,
.boundary-grid strong { display: block; }
.boundary-grid small { color: #9a9fac; font-size: 10px; font-weight: 800; letter-spacing: .08em; }
.boundary-grid strong { margin-top: 4px; font-size: 14px; }

.architecture-map,
.stage-section,
.data-plane { padding: 28px 30px; }
.section-heading { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; }
.section-heading h2 { margin: 3px 0 0; font-size: 22px; letter-spacing: -.02em; }
.section-heading > p { max-width: 610px; margin: 0; color: var(--muted); font-size: 13px; text-align: right; }
.section-heading.compact { align-items: center; }

.kernel-flow {
  display: grid;
  margin-top: 28px;
  grid-template-columns: minmax(190px, .8fr) 58px minmax(430px, 1.55fr) 58px minmax(200px, .8fr);
  align-items: center;
}

.boundary-node {
  display: flex;
  min-height: 126px;
  padding: 18px;
  align-items: center;
  gap: 13px;
  border: 1px solid #e4e6ed;
  border-radius: 14px;
  background: #fafbfc;
}
.boundary-node .node-icon {
  display: grid;
  width: 38px;
  height: 38px;
  flex: 0 0 auto;
  place-items: center;
  border-radius: 10px;
  background: #ececfa;
  color: #6261c5;
  font-size: 10px;
  font-weight: 900;
}
.boundary-node span,
.boundary-node strong,
.boundary-node small { display: block; }
.boundary-node span { color: #8e95a3; font-size: 11px; font-weight: 800; }
.boundary-node strong { margin-top: 5px; font-size: 14px; line-height: 1.45; }
.boundary-node small { margin-top: 6px; color: #969dab; font-size: 11px; }

.flow-connector { display: flex; align-items: center; color: #8d91b7; }
.flow-connector i { height: 1px; flex: 1; background: #d9dbe6; }
.flow-connector b { font-size: 18px; font-weight: 500; }

.agent-kernel {
  overflow: hidden;
  border: 1px solid #cbcaee;
  border-radius: 16px;
  background: linear-gradient(145deg, #fbfbff, #f3f3fb);
  box-shadow: 0 10px 30px rgba(78, 76, 165, .08);
}
.agent-kernel > header { display: flex; padding: 15px 18px; align-items: center; justify-content: space-between; border-bottom: 1px solid #ddddef; }
.agent-kernel header span,
.agent-kernel header strong { display: block; }
.agent-kernel header span { color: #7170cd; font-size: 10px; font-weight: 850; letter-spacing: .1em; }
.agent-kernel header strong { margin-top: 3px; font-size: 16px; }
.agent-kernel header em { padding: 4px 8px; border-radius: 999px; background: #e8e8f8; color: #6665bd; font-size: 10px; font-style: normal; font-weight: 750; }
.react-loop { display: grid; padding: 18px; grid-template-columns: 1fr 24px 1fr 24px 1fr 28px; align-items: center; }
.react-loop > div { display: flex; min-height: 70px; padding: 10px 8px; flex-direction: column; align-items: center; justify-content: center; border: 1px solid #dfdfed; border-radius: 11px; background: #fff; }
.react-loop > div b { color: #8f8ed5; font-size: 10px; }
.react-loop > div span { margin-top: 2px; font-size: 13px; font-weight: 800; }
.react-loop > div small { color: #969cab; font-size: 10px; }
.react-loop > i { color: #9494bc; font-size: 17px; font-style: normal; text-align: center; }
.agent-kernel > footer { display: flex; padding: 10px 18px; flex-wrap: wrap; gap: 15px; border-top: 1px solid #ddddef; color: #747b8a; font-size: 10px; }
.agent-kernel footer span { display: inline-flex; align-items: center; gap: 5px; }
.dot,
.legend-dot { width: 7px; height: 7px; border-radius: 50%; }
.model { background: #6665ce; }
.tool { background: #2795be; }
.guard { background: #df9c28; }

.guard-rail { display: grid; margin-top: 14px; padding: 11px 15px; grid-template-columns: auto minmax(30px, 1fr) auto; align-items: center; gap: 12px; border-radius: 10px; background: #fff8ea; color: #8c651d; font-size: 12px; }
.guard-rail > span { font-weight: 850; }
.guard-rail > i { height: 1px; background: #ead7ad; }
.guard-rail p { margin: 0; }

.stage-flow { display: grid; margin-top: 28px; grid-template-columns: 1fr 22px 1fr 22px 1fr 22px 1fr 22px 1fr 22px 1fr; align-items: center; }
.stage-node {
  position: relative;
  display: flex;
  min-width: 0;
  min-height: 116px;
  padding: 15px 13px;
  flex-direction: column;
  align-items: flex-start;
  border: 1px solid #e2e4ea;
  border-radius: 13px;
  background: #fafbfc;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease;
}
.stage-node:hover { transform: translateY(-2px); border-color: #cfd2de; box-shadow: 0 8px 22px rgba(40, 48, 69, .06); }
.stage-node.active { border-color: var(--tone); background: var(--tone-soft); box-shadow: 0 8px 24px var(--tone-shadow); }
.stage-node.indigo { --tone: #6665ca; --tone-soft: #f1f1ff; --tone-shadow: rgba(89, 88, 192, .12); }
.stage-node.blue { --tone: #497dcc; --tone-soft: #eff6ff; --tone-shadow: rgba(67, 117, 193, .12); }
.stage-node.cyan { --tone: #2997ae; --tone-soft: #effafb; --tone-shadow: rgba(40, 143, 164, .12); }
.stage-node.amber { --tone: #cf8b25; --tone-soft: #fff8ea; --tone-shadow: rgba(198, 132, 32, .12); }
.stage-node.green { --tone: #369873; --tone-soft: #effaf6; --tone-shadow: rgba(49, 145, 108, .12); }
.stage-node.violet { --tone: #845dbd; --tone-soft: #f7f1fd; --tone-shadow: rgba(121, 80, 174, .12); }
.stage-index { display: grid; width: 25px; height: 21px; place-items: center; border-radius: 6px; background: #e9eaf0; color: #737b8a; font-size: 10px; font-weight: 900; }
.stage-node.active .stage-index { background: var(--tone); color: #fff; }
.stage-node small { margin-top: 12px; overflow: hidden; color: #9a9fac; font-size: 9px; font-weight: 800; letter-spacing: .08em; text-overflow: ellipsis; white-space: nowrap; }
.stage-node strong { margin-top: 2px; font-size: 14px; }
.stage-node > i { margin-top: auto; color: #9ca2ae; font-size: 10px; font-style: normal; }
.stage-arrow { display: flex; align-items: center; color: #aaaeb9; font-size: 12px; }
.stage-arrow i { height: 1px; flex: 1; background: #dadde4; }

.stage-detail {
  --tone: #497dcc;
  --tone-soft: #eff6ff;
  display: grid;
  margin-top: 18px;
  padding: 22px;
  grid-template-columns: minmax(190px, .8fr) minmax(260px, 1.2fr) minmax(0, 1.35fr);
  gap: 22px;
  border: 1px solid color-mix(in srgb, var(--tone) 28%, #e2e4ea);
  border-radius: 15px;
  background: linear-gradient(135deg, var(--tone-soft), #fff 55%);
}
.stage-detail.indigo { --tone: #6665ca; --tone-soft: #f1f1ff; }
.stage-detail.blue { --tone: #497dcc; --tone-soft: #eff6ff; }
.stage-detail.cyan { --tone: #2997ae; --tone-soft: #effafb; }
.stage-detail.amber { --tone: #cf8b25; --tone-soft: #fff8ea; }
.stage-detail.green { --tone: #369873; --tone-soft: #effaf6; }
.stage-detail.violet { --tone: #845dbd; --tone-soft: #f7f1fd; }
.detail-lead > span { color: var(--tone); font-size: 10px; font-weight: 850; letter-spacing: .1em; }
.detail-lead h3 { margin: 5px 0 6px; font-size: 20px; }
.detail-lead p { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.7; }
.io-grid { display: grid; margin: 0; gap: 9px; }
.io-grid div { padding: 11px 12px; border-left: 2px solid var(--tone); background: rgba(255, 255, 255, .72); }
.io-grid dt { color: #7f8796; font-size: 10px; font-weight: 800; }
.io-grid dt span { margin-right: 5px; color: var(--tone); }
.io-grid dd { margin: 4px 0 0; font-size: 12px; line-height: 1.55; }
.detail-columns { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.detail-columns section { min-width: 0; }
.tool-list { display: flex; margin-top: 9px; flex-wrap: wrap; gap: 6px; }
.tool-list code { padding: 5px 7px; border: 1px solid #e0e2e9; border-radius: 6px; background: #fff; color: #4d5566; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10px; overflow-wrap: anywhere; }
.detail-columns ul { display: grid; margin: 9px 0 0; padding: 0; gap: 7px; list-style: none; }
.detail-columns li { position: relative; padding-left: 13px; color: #626b7b; font-size: 11px; line-height: 1.45; }
.detail-columns li::before { position: absolute; top: 6px; left: 0; width: 5px; height: 5px; border-radius: 50%; background: var(--tone); content: ''; }

.data-pipeline { display: flex; margin-top: 24px; flex-direction: column; align-items: center; }
.service-gateway { width: min(420px, 100%); padding: 13px 18px; border: 1px solid #d9dbea; border-radius: 12px; background: #f7f7fc; text-align: center; }
.service-gateway span,
.service-gateway strong,
.service-gateway small { display: block; }
.service-gateway span { color: #7675cc; font-size: 9px; font-weight: 850; letter-spacing: .12em; }
.service-gateway strong { margin-top: 3px; font-size: 14px; }
.service-gateway small { margin-top: 2px; color: #9298a5; font-size: 10px; }
.data-arrow { color: #a4a8b4; line-height: 28px; }
.store-grid { display: grid; width: 100%; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; }
.store-grid article { min-width: 0; padding: 13px; border-top: 2px solid #6d6ccb; background: #f8f9fb; }
.store-grid span,
.store-grid strong,
.store-grid small { display: block; }
.store-grid span { color: #7372ca; font-size: 9px; font-weight: 900; letter-spacing: .09em; }
.store-grid strong { margin-top: 5px; overflow: hidden; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; text-overflow: ellipsis; white-space: nowrap; }
.store-grid small { margin-top: 5px; color: #8b92a0; font-size: 10px; line-height: 1.4; }

.page-footnote { display: flex; padding: 0 5px; align-items: center; gap: 17px; color: #8a91a0; font-size: 10px; }
.page-footnote > span { display: inline-flex; align-items: center; gap: 6px; }
.page-footnote p { margin: 0 0 0 auto; }

@media (max-width: 1180px) {
  .kernel-flow { grid-template-columns: 1fr 35px minmax(390px, 1.4fr) 35px 1fr; }
  .stage-flow { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
  .stage-arrow { display: none; }
  .stage-detail { grid-template-columns: 1fr 1.4fr; }
  .detail-columns { grid-column: 1 / -1; }
  .store-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (max-width: 900px) {
  .page-hero { padding: 30px; grid-template-columns: 1fr; }
  .runtime-boundary { max-width: 620px; }
  .section-heading { align-items: flex-start; flex-direction: column; gap: 8px; }
  .section-heading > p { max-width: none; text-align: left; }
  .kernel-flow { grid-template-columns: 1fr; gap: 10px; }
  .flow-connector { width: 1px; height: 24px; margin: 0 auto; flex-direction: column; }
  .flow-connector i { width: 1px; }
  .flow-connector b { transform: rotate(90deg); }
  .guard-rail { grid-template-columns: auto 1fr; }
  .guard-rail p { grid-column: 1 / -1; }
}

@media (max-width: 700px) {
  .framework-page { gap: 14px; }
  .page-hero,
  .architecture-map,
  .stage-section,
  .data-plane { border-radius: 15px; }
  .page-hero,
  .architecture-map,
  .stage-section,
  .data-plane { padding: 22px 18px; }
  .hero-actions { align-items: stretch; flex-direction: column; }
  .hero-actions a { justify-content: center; }
  .boundary-grid { grid-template-columns: 1fr; }
  .react-loop { grid-template-columns: 1fr; gap: 6px; }
  .react-loop > i { transform: rotate(90deg); }
  .stage-flow { grid-template-columns: 1fr 1fr; }
  .stage-detail { padding: 17px; grid-template-columns: 1fr; }
  .detail-columns { grid-column: auto; grid-template-columns: 1fr; }
  .store-grid { grid-template-columns: 1fr 1fr; }
  .page-footnote { flex-wrap: wrap; }
  .page-footnote p { width: 100%; margin-left: 0; }
}

@media (max-width: 430px) {
  .stage-flow,
  .store-grid { grid-template-columns: 1fr; }
}
</style>
