/* ─────────────────────────────────────────────────────────────
   LanZ · 渠道 Agent 会员与组合页(/products/channel/)专属样式

   来源:外部设计交付(Lanzdata产品页面重设计.zip 的 membership/)。页面
   概念是**准入与组合**:首屏一张依赖星图讲清「产业链溯源是底座,招商 /
   客户 / 产业政策建在它之上,供需与渠道各自独立」,下半页是可勾选的
   组合台。全页不出价格。只被这一个页面引用。

   集成时做了七处改造, 都是本站硬约束逼出来的, 别改回去:

   1. **所有选择器收进 .channelx 作用域。** 设计稿用的是 .hero / .wrap /
      .btn / .card 这些通用类名, 而 style.css 全站加载。不隔离的话两边
      互相覆盖, 顶栏会散架。

   2. **.hero → .cn-hero、.dim → .cn-dim。** 光靠作用域压不住这两个:
      · style.css 的 .hero(798 行起)带着 padding 与 grid 排版,
        那些「我们没声明的属性」会照样落到本页首屏上。
      · style.css 的 .dim 是 display:flex 的指标块 —— 而本页的 .dim 挂在
        SVG 的 <g> 与 <path> 上。给 <g> 套 display:flex 会直接把星图的
        布局搅乱, 而且不报错。

   3. **动画名加了 cn- 前缀。** 设计稿的 @keyframes breathe 与 style.css
      里的同名, 后加载的那份会整体覆盖前一份。
      ⚠ 溯源页 cx-、招商页 iv-、政策页 pl-、客户页 cl-、供需页 mt-,
      六页各一套, 不要合并 —— 合并就等于六页共用一份动效, 改一边会
      静默动到另外五边。

   4. **字体换成站里的系统字体栈。** 原稿引 Google Fonts(Noto Sans SC +
      JetBrains Mono), 本站 CSP 是 default-src 'none' 起步, 外链字体会被
      浏览器直接拦掉, 而且不报错 —— 只会静默回退。SVG 里的 font-family
      属性也一并删掉了, 靠继承拿 --font-body。
      ⚠ 代价:原稿的 font-weight:900 在系统中文字体里没有对应字重,
      会回退到 700, 标题比设计稿略轻。

   5. **设计稿自带的胶囊顶栏与页脚样式整段删掉。** 那两块用站里统一的
      (build.sh 逐页比对, 不一致直接失败)。原稿 .hero 上的
      margin-top:-88px 是为了钻到悬浮胶囊底下, 本站顶栏不是悬浮胶囊,
      一并去掉。

   6. **设计稿自带的联系表单弹窗整段删掉。** 站里已经有一个
      —— main.js 第 9 段, 六个产品页与首页共用, 原生 <form> POST 到
      /api/demo, 无 JS 也能提交(降级成跳去 /demo/)。再写第二个弹窗
      等于把字段定义抄成第四份(现在是 main.js、demo/index.html、
      server/demo-form.py 三处), 而且 CSP 不放行 connect-src,
      原稿那种「提交后原地切确认态」必须走 fetch, 在本站根本跑不起来。
      本页的两个「联系我们」是普通链接, 样式由 .channelx .btn 管。

   7. **[data-rise] 的隐藏态挂在 html.cn-rise 上。** 原稿直接写
      [data-rise]{opacity:0}, JS 一挂各段标题就永远不显示。本站的规矩
      是「内容默认可见, JS 只负责隐藏」—— 见 style.css 的 .reveal。

   ── 语义约定(全页一致) ──────────────────────────────────────
   紫 = 手动选中 / 底座, 青 = 独立运行 / 依赖自动带入。
   ⚠ 深底上的青用 --cyan, 浅底上必须换成 --cyan-d;而汇总胶囊那种
     「浅青底压在白卡上」的场合 --cyan-d 只有 3.35:1, 不够 ——
     那里的文字用 --cyan-ink(#0A6274, 约 5.6:1)。这三档别混用。

   设计系统说明见交付附带的 README;色值是设计稿给的 hex,
   ⚠ 没有转成 OKLCH —— 与 tokens.css 的口径不一致, 是笔已知的账
   (另外五个 Agent 页同样如此)。
   ───────────────────────────────────────────────────────────── */
:root{
  --ink:#12112B;  /* 主文字 */
  --ink-2:#3A3752;  /* 正文 */
  --ink-3:#6B6884;  /* 弱文字 / 等宽标签 */
  --lite:#EFECF9;  /* 浅色段底色 —— ⚠ 平色, 勿改成渐变 */
  --dark:#0B0A1E;
  --dark-2:#08071A;
  --purple:#7B4DEB;  /* 手动选中 */
  --purple-d:#6D3FE0;  /* 浅底上的紫（文字可用） */
  --purple-l:#8B62F0;
  --purple-lt:#A98CFF;  /* 深底上的紫（文字可用） */
  --purple-pale:#B79EFF;
  --cyan:#3FD2E8;  /* 深底上的青 = 独立运行 */
  --cyan-d:#0E93A8;  /* 浅底上的青（描边与填充用） */
  --cyan-ink:#0A6274;  /* 浅青底上的青字（唯一够对比度的那一档） */
  --on-dark:#EDEBF7;
  --on-dark-2:#B4B1C9;
  --on-dark-3:#9E9BB8;
  --on-dark-4:#8A87A6;
  --grid:rgba(160,150,255,.055);
  --maxw:1280px;
  --pad:40px;
}
body{ background:var(--lite); color:var(--ink) }
.channelx, .channelx *{ box-sizing:border-box }
.channelx a{ color:var(--purple-d); text-decoration:none }
.channelx a:hover{ color:var(--purple-l) }
.channelx button{ font-family:inherit }
.channelx .mono{ font-family:var(--font-mono) }
.channelx .wrap{ max-width:var(--maxw); margin:0 auto; padding:0 var(--pad) }
.channelx .grid-tex{
  position:absolute;
  inset:0;
  pointer-events:none;
  background-image:linear-gradient(var(--grid) 1px,transparent 1px),linear-gradient(90deg,var(--grid) 1px,transparent 1px);
  background-size:64px 64px;
}

/* ---------- 动效基座 ---------- */
@keyframes cn-breathe{ 0%,100%{ opacity:.4 } 50%{ opacity:1 } }
@keyframes cn-rise{ from{ opacity:0; transform:translateY(26px) } to{ opacity:1; transform:none } }
@keyframes cn-halo{ 0%,100%{ transform:scale(1); opacity:.5 } 50%{ transform:scale(1.06); opacity:.85 } }
@keyframes cn-pop{
  0%{ opacity:0; transform:translateY(8px) scale(.94) }
  60%{ transform:translateY(0) scale(1.03) }
  100%{ opacity:1; transform:none }
}
@keyframes cn-tick{ from{ opacity:0; transform:scale(.4) } to{ opacity:1; transform:none } }
/* 隐藏态挂在 html.cn-rise 上:只有 JS 确认 IntersectionObserver 可用
   且用户没要求静止时才加这个类。见 channel.js 末段。 */
html.cn-rise .channelx [data-rise]{ opacity:0 }
.channelx [data-rise="in"]{ animation:cn-rise .9s cubic-bezier(.16,.84,.34,1) both }
.channelx .dot{
  width:6px;
  height:6px;
  border-radius:999px;
  background:var(--purple-l);
  animation:cn-breathe 2.4s ease-in-out infinite;
}

/* ---------- 按钮 ---------- */
.channelx .btn{
  display:inline-block;
  white-space:nowrap;
  padding:19px 46px;
  border-radius:999px;
  background:var(--purple);
  color:#fff;
  font-size:17px;
  font-weight:700;
  box-shadow:0 14px 44px rgba(123,77,235,.5);
}
.channelx .btn:hover{ background:var(--purple-l); color:#fff }

/* ---------- 通用 ---------- */
.channelx .sect-h2{
  margin:0;
  font-size:clamp(34px,3.6vw,46px);
  line-height:1.18;
  font-weight:900;
  letter-spacing:-.025em;
}
.channelx .sect-p{
  margin:24px 0 0;
  font-size:17.5px;
  line-height:1.9;
  color:var(--ink-2);
  max-width:27em;
}
.channelx .card{
  background:#fff;
  border-radius:16px;
  padding:26px 28px 30px;
  box-shadow:0 10px 34px rgba(18,17,43,.07);
}
.channelx .cap{
  display:flex;
  align-items:center;
  gap:10px;
  padding-bottom:16px;
  font-size:11.5px;
  letter-spacing:.1em;
  color:var(--ink-3);
}
.channelx .cap span{ white-space:nowrap }
.channelx .cap .r{ margin-left:auto }

/* ============ 首屏：依赖星图 ============ */
.channelx .cn-hero{
  position:relative;
  padding:186px 0 116px;
  /* 首屏占满一屏。min-height 不是 height —— 窄屏上内容会比 100vh 高,
     写死 height 会让它溢出到下一屏并被 overflow:hidden 切掉。
     flex + center 让内容在多出来的高度里居中, 而不是全顶在上内边距下面。
     与政策页 .pl-hero、客户页 .cl-hero 同一套写法。 */
  min-height:100vh;
  display:flex;
  align-items:center;
  overflow:hidden;
  color:var(--on-dark);
  background:radial-gradient(1100px 680px at 68% 44%,#231550 0%,#120D33 46%,#0B0A1E 100%);
}
/* .wrap 是 flex 子项, 不给 100% 会收缩到内容宽, 星图右列会被挤扁。 */
.channelx .cn-hero > .wrap{ width:100% }
.channelx .cn-hero .halo{
  position:absolute;
  top:44%;
  right:8%;
  width:760px;
  height:760px;
  transform:translateY(-50%);
  pointer-events:none;
  animation:cn-breathe 8s ease-in-out infinite;
  background:radial-gradient(circle at center,rgba(123,77,235,.3),rgba(11,10,30,0) 62%);
}
.channelx .cn-hero .ring{
  position:absolute;
  top:44%;
  right:8%;
  width:600px;
  height:600px;
  transform:translate(50%,-50%);
  border-radius:999px;
  /* ⚠ 实线, 不要改回虚线。这圈半径 300 的弧正好从右侧三个方框的边缘
     扫过去, 虚线(尤其配旋转)看着像方框自己带了一圈会动的虚框。 */
  border:1px solid rgba(171,140,255,.12);
  pointer-events:none;
}
.channelx .hero-grid{
  position:relative;
  display:grid;
  grid-template-columns:minmax(0,.92fr) minmax(0,1.08fr);
  gap:52px;
  align-items:center;
}
.channelx .cn-hero h1{
  margin:0;
  font-size:clamp(38px,4vw,58px);
  line-height:1.14;
  font-weight:900;
  letter-spacing:-.025em;
  color:#fff;
}
/* 渐变字。⚠ 与另外五个 Agent 页逐字相同, 改一处要六处一起改。
   color:transparent 收在 @supports 里:万一 background-clip:text
   不生效, 落回实色标题, 而不是一个看不见的大标题。 */
.channelx .cn-hero h1 em{ font-style:normal }
@supports (-webkit-background-clip:text) or (background-clip:text){
  .channelx .cn-hero h1 em{
    background:linear-gradient(96deg,#fff 16%,var(--purple-lt) 64%,var(--cyan) 100%);
    -webkit-background-clip:text;
    background-clip:text;
    color:transparent;
  }
}
.channelx .cn-hero p{
  margin:28px 0 0;
  font-size:18.5px;
  line-height:1.85;
  color:var(--on-dark-2);
  max-width:25em;
}
.channelx .hero-btns{ display:flex; flex-wrap:wrap; gap:14px; margin-top:40px }
.channelx .star{ position:relative; min-width:0 }
.channelx .star svg{ width:100%; height:auto; display:block; overflow:visible }
/* 依赖连线。⚠ 实线, 且不带流动动画 —— 原稿是 4 8 的虚线配 cn-flow
   蚂蚁线, 但底座到招商 / 产业政策那两条的水平落差只有 36px、垂直落差
   152px, 画出来几乎是贴着方框左边缘的一道竖线: 会动的虚线贴在小字框
   边上, 看着不像连线, 像方框自己有一圈滚动的虚线边框。 */
/* ⚠ 选择器必须带 path —— `dep` 这个类名在本页有**两个**用处:
   这里的连接线 <path class="dep">, 和有依赖关系的节点 <g class="ag dep">
   (见 channel.js 的 data-dep)。而 SVG 的 fill/stroke 是**继承**的:
   只写 .dep 的话, 下面这条 stroke 会顺着 <g> 落到节点里的 <text> 上,
   给 19px 的字套一圈 1.8px 的深紫描边 —— 「招商」两个字的描边直接糊
   成一坨, 看着像字没渲染出来。中招的正好是 dep 那三个(招商/客户/
   产业政策), base 与 solo 干净, 所以很容易误判成"某几个字的颜色不对"。

   这是文件头那条「.hero → .cn-hero、.dim → .cn-dim」纪律的同一类问题,
   只不过这次撞车发生在本文件内部, 不是和 style.css 之间。 */
.channelx path.dep{
  fill:none;
  stroke:#5A4A96;
  stroke-width:1.8;
  transition:stroke .45s, opacity .45s, stroke-width .45s;
}
.channelx path.dep.lit{ stroke:var(--purple-lt); stroke-width:2.4; opacity:1 }
.channelx path.dep.cn-dim{ opacity:.28 }
/* ⚠ 原稿这里是 cursor:pointer, 去掉了 —— 节点点了不会有任何事情发生
   (悬停接管高亮是有的, 点击没有)。手型光标是「这里能点」的承诺,
   而这张图整体 aria-hidden, 键盘根本走不进来, 没法把承诺兑现。 */
/* ⚠ transform-origin:center 在 SVG 里必须配 transform-box:fill-box,
   否则会以画布原点为中心缩放 —— 方框会飞出画面。 */
.channelx .ag rect{
  transition:fill .45s, stroke .45s, transform .45s;
  transform-origin:center;
  transform-box:fill-box;
}
.channelx .ag text{ transition:fill .45s }
.channelx .ag .box{ fill:#1A1640; stroke:#43377A }
.channelx .ag .nm{ font-size:19px; font-weight:700; fill:#D9D5F0 }
.channelx .ag.base .box{ fill:#2A1C63; stroke:#8B62F0 }
.channelx .ag.base .nm{ fill:#fff }
.channelx .ag.on .box{ fill:var(--purple); stroke:#CFC4FF; transform:scale(1.04) }
.channelx .ag.on .nm{ fill:#fff }
.channelx .ag.solo .box{ fill:#14213A; stroke:#2E5A70 }
.channelx .ag.cn-dim{ opacity:.4 }
.channelx .pulse{ fill:none; stroke:var(--purple-lt); opacity:0 }
.channelx .pulse.go{ animation:cn-halo 2.4s ease-in-out infinite; opacity:1 }

/* ============ 组合台 ============ */
.channelx .build{ position:relative; padding:116px 0 130px; background:var(--lite) }
.channelx .build-grid{
  display:grid;
  grid-template-columns:minmax(0,1.06fr) minmax(0,.94fr);
  gap:58px;
  align-items:start;
  margin-top:46px;
}
.channelx .picks{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px }
.channelx .pick{
  position:relative;
  appearance:none;
  border:0;
  cursor:pointer;
  text-align:left;
  padding:18px 19px 19px;
  border-radius:14px;
  background:#fff;
  box-shadow:0 4px 18px rgba(18,17,43,.06);
  transition:box-shadow .35s, background .35s, transform .35s;
}
.channelx .pick:hover{ transform:translateY(-2px); box-shadow:0 12px 30px rgba(18,17,43,.1) }
.channelx .pick[aria-pressed="true"]{
  background:rgba(123,77,235,.1);
  box-shadow:inset 0 0 0 1.5px var(--purple);
}
.channelx .pick .nm{
  display:block;
  font-size:17px;
  font-weight:900;
  letter-spacing:-.01em;
  color:var(--ink);
  padding-right:30px;
}
.channelx .pick .ds{ display:block; font-size:13px; color:var(--ink-3); margin-top:8px; line-height:1.6 }
.channelx .pick .req{
  display:inline-flex;
  align-items:center;
  gap:6px;
  margin-top:12px;
  padding:5px 10px;
  border-radius:999px;
  background:#F1EEFD;
  font-size:10.5px;
  color:var(--purple-d);
}
/* 勾选标记。默认 display:none, 选中才 flex —— 所以 align/justify 写在
   这里不起作用的那一半是故意的:display 一变就全套生效。 */
.channelx .pick .mark{
  position:absolute;
  right:15px;
  top:15px;
  width:22px;
  height:22px;
  border-radius:999px;
  background:var(--purple);
  display:none;
  align-items:center;
  justify-content:center;
  animation:cn-tick .35s cubic-bezier(.16,.84,.34,1) both;
}
.channelx .pick[aria-pressed="true"] .mark{ display:flex }
/* 自动带入态(底座):青色, 与手动选中的紫区分开。 */
.channelx .pick.auto{ background:rgba(63,210,232,.1); box-shadow:inset 0 0 0 1.5px var(--cyan-d) }
.channelx .pick.auto .mark{ background:var(--cyan-d); display:flex }
.channelx .pick.auto .req{ background:rgba(14,147,168,.12); color:var(--cyan-ink) }

.channelx .slip{ position:sticky; top:104px }
.channelx .slip .bag{
  display:flex;
  flex-wrap:wrap;
  gap:9px;
  margin-top:18px;
  min-height:96px;
  padding:18px;
  border-radius:22px;
  border:1px solid rgba(18,17,43,.09);
  background:#FBFAFE;
}
.channelx .slip .row{
  display:inline-flex;
  align-items:center;
  gap:8px;
  padding:11px 17px;
  border-radius:14px;
  background:rgba(123,77,235,.11);
  box-shadow:inset 0 0 0 1px rgba(123,77,235,.26);
  font-size:14px;
  font-weight:700;
  color:var(--purple-d);
  white-space:nowrap;
  animation:cn-pop .4s cubic-bezier(.16,.84,.34,1) both;
}
.channelx .slip .row i{ width:7px; height:7px; border-radius:999px; background:var(--purple); flex:none }
.channelx .slip .row.auto{
  background:rgba(63,210,232,.13);
  box-shadow:inset 0 0 0 1px rgba(14,147,168,.34);
  color:var(--cyan-ink);
}
.channelx .slip .row.auto i{ background:var(--cyan-ink) }
.channelx .slip .empty{ align-self:center; font-size:14px; line-height:1.7; color:var(--ink-3) }
.channelx .slip .sum{ display:flex; align-items:baseline; gap:10px; margin-top:26px }
.channelx .slip .sum b{ font-size:40px; font-weight:900; letter-spacing:-.04em; color:var(--purple-d) }
.channelx .slip .sum s{ text-decoration:none; font-size:14px; color:var(--ink-3) }
.channelx .slip .note{ margin:16px 0 0; font-size:13.5px; line-height:1.8; color:var(--ink-3) }
.channelx .slip .go{
  display:block;
  width:100%;
  margin-top:20px;
  padding:15px;
  border-radius:999px;
  text-align:center;
  background:var(--purple);
  color:#fff;
  font-size:15.5px;
  font-weight:700;
  box-shadow:0 10px 28px rgba(123,77,235,.34);
}
.channelx .slip .go:hover{ background:var(--purple-l); color:#fff }

/* ---------- 响应式 ---------- */
@media (max-width:1080px){
  .channelx .hero-grid,
  .channelx .build-grid{ grid-template-columns:minmax(0,1fr); gap:46px }
  .channelx .cn-hero{ padding:150px 0 96px }
  .channelx .cn-hero .ring{ display:none }
  .channelx .slip{ position:static }
}
/* ⚠ 窄屏下星图被压扁, 图内的产品名会掉到看不清的字号 ——
   实测 375px 下只有 7.4px, 本站的下限是 12px。六个方框里的字是
   这张图的全部信息(不像供需页那张还有别的读法), 所以不能像那边
   一样「窄屏只留节点」—— 这里把字号提上去, 靠 SVG 自己缩放。
   ⚠ 上限是 30:框宽 186、左内边距 20, 最长的「产业链溯源」五个字
     到 30 就正好顶到右边缘。再大就压到隔壁那一列上,
     而 SVG 不会像 HTML 那样换行或者裁掉, 只会安静地叠着画。 */
@media (max-width:720px){
  .channelx .picks{ grid-template-columns:minmax(0,1fr) }
  .channelx .ag .nm{ font-size:30px }
}
@media (prefers-reduced-motion:reduce){
  .channelx .dot,
  .channelx .cn-hero .halo,
  .channelx .cn-hero .ring,
  .channelx path.dep,
  .channelx .pulse.go{ animation:none }
  .channelx [data-rise="in"]{ animation:none }
  html.cn-rise .channelx [data-rise]{ opacity:1 }
  /* 初态一律直接置为终态 —— 不做动画就不能把内容留在初态里 */
  .channelx .slip .row,
  .channelx .pick .mark{ opacity:1; animation:none; transform:none }
}
