/* ==========================================================================
   页面层 Pages：产品详情页（安全加密盘 / 单向光闸 共用）
   依赖：tokens.css, base.css, layout.css, components.css
   ========================================================================== */

/* ---------- 产品首屏 ---------- */
.product-hero {
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  gap: 48px;
  align-items: center;
  padding: var(--space-9) 0 var(--space-8);
}

/*
 * 首屏带真实界面截图时，让配图列占更大空间：
 * 截图原始宽度 1400px，按 1:1 分栏缩到 ~550px 后界面文字会糊成一片。
 */
.product-hero:has(.hero-visual img) {
  grid-template-columns: 1fr 1.25fr;
  gap: 44px;
}

.product-hero h1 {
  font-size: var(--font-size-h1-sub);
  font-weight: 700;
  letter-spacing: -.02em;
  line-height: 1.2;
}

.product-hero p {
  margin-top: 18px;
  color: var(--color-text-2);
  font-size: var(--font-size-lead);
  line-height: var(--line-height-relaxed);
}

/*
 * 首屏中文产品全称（如「安全隐身加密存储和分发系统」）。
 *
 * 视觉上它是 h1 的第二行主标题，与上面的英文品牌名共同构成「英文品牌 + 中文全称」，
 * 因此紧贴 h1、不加段间距。语义上刻意用 <p> 而非第二个 <h1>：
 * 一个页面只保留一个主标题，标题树保持 h1 → h2（.section-title）→ h4 的节奏，
 * 不为了字号再塞一个标题级别进去。读屏顺序仍是「USB VAULT」→ 中文全称 → 描述。
 *
 * 写成 class 而不是内联 style 的原因：内联样式优先级高于外部样式表，
 * 手机上无法用媒体查询把 40px 收下来，长中文标题会把窄屏撑成多行孤字。
 */
.product-hero .hero-subtitle {
  margin-top: 2px;
  color: var(--color-text);
  font-size: clamp(26px, 4.2vw, 40px);
  /*
   * 只写 800：字体缺 800 字重时浏览器会自动就近取 bold，
   * 不需要再写一遍 font-weight: bold 做「降级」。
   */
  font-weight: 800;
  letter-spacing: -.01em;
  line-height: 1.3;
}

.product-hero .cta {
  margin-top: 28px;
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
}

/* 产品配图占位框：替换为真实图片时使用 <img> 放在同一容器内 */
/*
 * 产品配图容器。
 * 有图时（内含 <img>）按图片自身比例撑开，占位态（纯文字）保留 16/10 框。
 */
.hero-visual {
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-bg-soft);
  color: var(--color-text-3);
  font-size: 14px;
  text-align: center;
  /* 图片铺满容器且不溢出圆角 */
  overflow: hidden;
}

/* 占位态才需要固定比例；带图时由图片高度决定，避免多余留白 */
.hero-visual:not(:has(img)) {
  aspect-ratio: 16 / 10;
}

/*
 * 轮播态：轮播自己管理视口比例（见 .hero-carousel-viewport 的 aspect-ratio），
 * 外层容器不再额外撑高，只保留边框与圆角做「画框」。
 * display:block 是必须的 —— 默认的 flex 居中会把视口压成内容宽度。
 */
.hero-visual:has(.hero-carousel) {
  display: block;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-bg-soft);
}

/* ---------- 产品图库轮播 ---------- */
/*
 * 轮播专属时长：整幅图横向滑动的低速动效。
 * 与 --transition-base（.25s，用于悬停/边框等微交互）区分开，
 * 调整按钮反馈速度时不会意外把轮播改快。
 */
.hero-carousel {
  --carousel-slide-duration: 0.55s;
  position: relative;
  width: 100%;
}

/*
 * 视口尺寸恒定，这是「切换不抖动」的核心。
 *
 * 历史写法让「当前图」留在文档流里撑开容器高度，两张图宽高比又不同
 * （one_gateway 1.624 / opt400 1.681），于是每次切换容器高度都跟着变，
 * 下方所有内容（核心能力、应用场景…）整体上下位移，观感就是「页面被拉扯」。
 *
 * 现在改为：视口用 aspect-ratio 定死高度，幻灯片横向排成一条轨道，
 * 切换时整条轨道平移，容器高度与页面其余部分的位置全程不变。
 *
 * 比例取 16/10（1.6）：两张图分别是 1.624 与 1.681，都极接近 1.6，
 * 配合 object-fit: cover 时裁切量不到 5%，而若取任一图的原始比例，
 * 另一张就会有明显留边。
 *
 * border-radius 直接写死而不是 inherit：.hero-carousel 自己不带圆角
 * （圆角在更外层的 .hero-visual 上），inherit 会继承到 0，四角变成直角，
 * 与外面的大圆角画框对不上。
 */
.hero-carousel-viewport {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border-radius: var(--radius-lg);
  background: var(--color-bg-soft);
}

/*
 * 轮播模式下视口变成横向轨道窗口：flex + nowrap，子元素各占 100% 宽并排。
 * 只在 ready 时生效 —— 脚本没跑起来时保持普通块级流，
 * 两张图纵向堆叠依次可见（安全基线）。
 */
.hero-carousel[data-carousel-ready] .hero-carousel-viewport {
  display: flex;
  flex-wrap: nowrap;
}

/*
 * 幻灯片默认全部可见（安全基线）。
 * 只有 JS 成功初始化并打上 data-carousel-ready 之后，才把它们排成轨道 ——
 * 这样脚本加载失败/被拦截时，用户看到的是两张图依次纵向排列，而不是空白容器。
 */
.hero-carousel-slide {
  margin: 0;
  opacity: 1;
  visibility: visible;
}

/*
 * 进入轮播模式：幻灯片横向排成一条轨道，整条轨道平移。
 *
 * 关键点：
 *   · 用 flex 把两张图横向排开，每张各占视口 100% 宽（flex: 0 0 100%），
 *     轨道总宽 200%，视口 overflow:hidden 只露出当前一张；
 *   · 位移量由脚本写成单一行内 translateX（见下方「位移规则」），
 *     整条轨道作为一个整体移动，所有图位移完全同步；
 *   · 只动 transform —— 合成层上完成，不触发 layout / paint，
 *     滚动与切页时都不会跟着掉帧；
 *   · 轨道内全部图（含克隆件）位移量必须完全一致，任何一张单独落后都会
 *     出现「后一张追不上前一张」的错位。所以位移统一由 JS 一次写死，
 *     而不是让 CSS 按 :nth-child 逐条累加；
 *   · 非当前图保持 visibility:hidden 会打断滑动动画（移出视野过程中就被隐藏），
 *     因此这里不隐藏，改用 pointer-events 与 aria-hidden 隔离交互与读屏。
 */
.hero-carousel[data-carousel-ready] .hero-carousel-slide {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 非当前图不可点：避免滑出视野的图仍然响应灯箱点击 */
  pointer-events: none;
  /*
   * 整幅图横向滑过约一个屏宽，0.25s 的微交互时长在这里会显得「闪一下」，
   * 用 0.55s + ease-in-out 让起步和收尾都更柔和，观感接近原生轮播。
   * 时长单独定义而不复用 --transition-base，避免以后微调按钮动效时
   * 连带把轮播改快。
   */
  transition: transform var(--carousel-slide-duration) ease-in-out;
  will-change: transform;
}

.hero-carousel[data-carousel-ready] .hero-carousel-slide.is-active {
  pointer-events: auto;
}

/*
 * reduced-motion：关掉位移动画，回到「直接切换」。
 * 用户明确要求减少动效时，滑动带来的前庭刺激比「画面突变」更难接受，
 * 所以这里必须尊重偏好，不能因为好看就忽略。
 */
@media (prefers-reduced-motion: reduce) {
  .hero-carousel[data-carousel-ready] .hero-carousel-slide {
    transition: none;
  }
}

/*
 * ---- 位移规则：脚本统一写单一 translateX ----
 *
 * 轨道是并排的一条：第 n 张图的左边缘在 n×100% 处，视口宽 100%。
 * 要看第 i 张，让整条轨道移动 -(i×100%) 即可；所有幻灯片的位移量
 * 完全相同，所以它们作为整体一起移动，衔接处不会错位。
 *
 * 为什么改成由 JS 写行内 transform，而不是 CSS :nth-child 累积：
 *   「始终向左切」需要在末尾克隆一张首图（.is-clone），滑到克隆位
 *   （画面仍是第 1 张）后再无动画归位到真实第 1 张。用 :nth-child 累积时，
 *   归位这一帧要把每张图同时从 -200% / -100% / 0 推回 0，
 *   跨越整整 2 个视口宽 —— 这些中间帧是真实渲染的，视觉上就是
 *   一次明显的「画面倒带」（用户报的「先向左、再向右」）。
 *   改为单一轨道位移后，归位只发生在 -100% → 0 两个「相邻停靠点」之间，
 *   而这两处画面完全相同（都是第 1 张），无动画跳转因此不可见。
 *
 * 唯一的代价是 CSS 不再自己知道索引，需要由 carousel.js 的 applyState()
 * 写入。索引仍同步写在 viewport 的 data-active-index 上，便于调试与测试。
 */
.hero-carousel[data-carousel-ready] .hero-carousel-slide {
  /* 由 carousel.js 覆盖为 translateX(-i×100%)，这里给初始值 */
  transform: translateX(0);
}
/*
 * 克隆件对用户不可见也不可交互：它在 i=0/1 时只是排在视口右侧外面，
 * 但它复制了真实图的可点击按钮与 alt 文案，必须从读屏树和 Tab 键序列里摘掉，
 * 否则读屏会念出三张图（实际只有两张）、Tab 也会在看不见的按钮上停一次。
 */
.hero-carousel-slide.is-clone {
  pointer-events: none;
}

/*
 * 瞬移期：脚本在跳回 i=0 时给视口加 .is-snap，让位移即刻生效；
 * 跳跃被渲染出来后由脚本（两帧 rAF）摘掉，恢复平滑滑动。
 *
 * 两个 !important 都是必需的，缺一个就会出现「先左滑到第 1 张、
 * 紧接着快速右滑两次」的观感：
 *   · transition:none —— transition 声明在
 *     .hero-carousel[data-carousel-ready] .hero-carousel-slide 上，
 *     选择器特异性更高，不加 !important 压不住；
 *   · transform —— carousel.js 把位移写成**行内** style.transform，
 *     行内样式优先级高于任何样式表规则，只有 !important 能盖过它。
 *     这样即使脚本清行内值的那一帧被浏览器合并优化，轨道仍然瞬时归零。
 */
.hero-carousel-viewport.is-snap .hero-carousel-slide,
.hero-carousel-viewport.is-snap .hero-carousel-slide.is-clone {
  transition: none !important;
  transform: translateX(0) !important;
}

/*
 * 触发按钮（点击放大）铺满整张幻灯片，保证图片任意位置都能点开灯箱。
 * height:100% 让按钮与幻灯片同高，避免只有图片本身可点。
 */
.hero-carousel[data-carousel-ready] .hero-carousel-slide .hero-visual-trigger {
  width: 100%;
  height: 100%;
}

.hero-carousel-slide .hero-visual-trigger {
  width: 100%;
}

/*
 * 图片铺满幻灯片内框。
 *
 * 用 cover 而不是 contain：两张图都是白底设备图，比例 1.624 / 1.681
 * 与视口的 1.6 极其接近，cover 只裁掉 1.5%~5% 的边缘白边，
 * 设备主体完全保留；换成 contain 则会在上下或左右留出明显的白条，
 * 看起来像「图没铺满」。因为都是白底，裁切处不会出现突兀的断口。
 * display:block 必须有：<img> 默认是行内元素，会因基线留出约 4px 的缝隙。
 */
.hero-carousel[data-carousel-ready] .hero-carousel-slide img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/*
 * 圆点叠在图片上，白底图上默认的浅色点几乎看不见。
 * 底衬由下面的 .hero-carousel-dots 负责，这里只调亮点本身的对比度。
 */
.hero-carousel[data-carousel-ready] .hero-carousel-dot {
  background: rgba(255, 255, 255, 0.55);
}

.hero-carousel[data-carousel-ready] .hero-carousel-dot.is-active {
  background: #fff;
}

/*
 * 两张图都按容器宽度铺满。display:block 必须有：
 * <img> 默认是行内元素，会因基线留出约 4px 的缝隙，
 * 在圆角容器里表现为图片底部一条背景色。
 */
.hero-carousel-slide img {
  display: block;
  width: 100%;
  height: auto;
}

/* 圆点指示器：叠在图片底部居中，半透明底保证浅色图上也能看清 */
.hero-carousel-dots {
  position: absolute;
  left: 50%;
  bottom: 12px;
  transform: translateX(-50%);
  display: flex;
  gap: 8px;
  padding: 6px 10px;
  border-radius: var(--radius-pill);
  background: rgba(12, 16, 24, .42);
  /* 允许点击穿透到图片的空白处，只有圆点本身可点 */
  z-index: 2;
}

.hero-carousel-dot {
  /* 供 ::before 的透明热区定位使用，缺了它热区会跑到最近的定位祖先前 */
  position: relative;
  width: 8px;
  height: 8px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, .5);
  cursor: pointer;
  transition: background var(--transition-fast), transform var(--transition-fast);
}

/* 圆点本身只有 8px，视觉小但点击区域需要够大：用透明外扩补足到 24px */
.hero-carousel-dot::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 24px;
  height: 24px;
  margin: -12px 0 0 -12px;
  border-radius: 50%;
}

.hero-carousel-dot.is-active {
  background: #fff;
}

.hero-carousel-dot:hover {
  background: rgba(255, 255, 255, .8);
}

.hero-carousel-dot:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

.hero-visual img {
  display: block;
  width: 100%;
  height: auto;
  /* 暗色界面截图自带底色，去掉可能的细缝 */
  vertical-align: middle;
}

/* ---------- 资质证书 ---------- */
/*
 * 证书卡片：上半是证书原件缩略图，下半是名称/编号。
 *
 * 尺寸刻意做小：证书是 A4 竖向长图，若按两列满宽铺开，单张高度接近 880px，
 * 整块区域会占掉近一屏，反而把「核心能力」「常见问题」挤到很远，
 * 页面重心变得很奇怪。这里限制整块最大宽度 + 缩小列宽，
 * 让证书退为「可核验的凭证缩略图」，需要看清细节时点开灯箱即可。
 */
.license-grid {
  margin-top: var(--space-6);
  display: grid;
  /* 固定两列：本页恰好两份证书，窄屏时降为单列 */
  grid-template-columns: repeat(2, 1fr);
  gap: 16px;
  /* 关键：整块限宽并居中，证书不再撑满内容区 */
  max-width: 520px;
  margin-left: auto;
  margin-right: auto;
}

.license-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  margin: 0;
  overflow: hidden;
  background: var(--color-bg-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-text);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.license-card:hover {
  border-color: var(--color-primary);
  box-shadow: var(--shadow-md);
}

/*
 * 证书图容器（本身是个 button，用于点开灯箱）。
 * 高度交给图片自身比例决定（img 用 height:auto），不写死 aspect-ratio ——
 * 两者并存时若四舍五入有偏差，contain 会在框内留出窄边，
 * 让证书看起来像被塞进一个略大的白框里。
 * 两张证书都是同一份 A4 尺寸，比例一致，卡片高度天然对齐。
 */
.license-figure {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: var(--color-bg-soft);
  cursor: zoom-in;
  overflow: hidden;
}

.license-figure img {
  display: block;
  width: 100%;
  /* 不指定高度：图片按自身比例铺满宽度，容器随之撑开，绝不拉伸 */
  height: auto;
  vertical-align: middle;
  transition: transform var(--transition-base);
}

.license-card:hover .license-figure img {
  /* 轻微放大提示「可点开细看」，容器 overflow:hidden 保证不溢出 */
  transform: scale(1.02);
}

/* 键盘可达性：默认焦点环之外再强调一次，与其他卡片交互保持一致 */
.license-figure:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: -4px;
}

/* 缩略图变小后，文字区同步收紧内边距，避免「小图配大留白」 */
.license-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  padding: var(--space-3) var(--space-3) var(--space-3);
}

.license-name {
  font-size: var(--font-size-sm);
  font-weight: 600;
}

.license-meta {
  color: var(--color-text-3);
  font-size: var(--font-size-xs);
  /* 编号+有效期一行放不下时优雅换行，不撑破卡片 */
  overflow-wrap: anywhere;
}

/* ---------- 响应式 ---------- */

@media (max-width: 1100px) {
  /* 中等屏两列变窄，头部标题容易折出单字，稍收字号 */
  .product-hero h1 {
    font-size: clamp(30px, 3.4vw, 42px);
  }

  .product-hero .hero-subtitle {
    font-size: clamp(26px, 4vw, 34px);
  }
}

@media (max-width: 860px) {
  /*
   * 单列堆叠。带图时 .product-hero:has(...) 的特异性更高，
   * 因此这里必须同样写出 :has() 才能覆盖两列定义。
   */
  .product-hero,
  .product-hero:has(.hero-visual img) {
    grid-template-columns: 1fr;
    padding: var(--space-8) 0 var(--space-6);
  }

  /* 证书卡片两列在平板上会把文件名挤成两三行，降为单列 */
  .license-grid {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 768px) {
  /* 小屏收紧产品标题，避免长标题把窄屏撑成多行孤字 */
  .product-hero h1 {
    font-size: clamp(28px, 8vw, 38px);
  }

  /*
   * 窄屏下 16/10 的视口会非常矮，设备图被压成一条，
   * 细节全看不清。改为 4/3 给图片更多竖直空间；
   * 视口仍是固定比例，切换依然不产生任何位移。
   */
  .hero-carousel-viewport {
    aspect-ratio: 4 / 3;
  }

  /* 中文全称同步收紧：14 个汉字在 40px 下于窄屏会折成 3 行以上 */
  .product-hero .hero-subtitle {
    font-size: clamp(24px, 6.4vw, 30px);
    line-height: 1.35;
  }
}

@media (max-width: 480px) {
  /* 与首页一致：极窄屏按钮等宽铺满 */
  .product-hero .cta {
    flex-direction: column;
    align-items: stretch;
  }

  .product-hero .cta .btn {
    width: 100%;
    justify-content: center;
  }
}
