/* ==========================================================
   portfolio-card.css — 作品集样式: §2-§3 项目卡本体 + 卡片扩展 FX — Backlog #152
   ------------------------------------------------------------

   加载序: CSS 第 6 / 37 (index.html <head> 第 6 个 <link>; 后加载的覆盖先加载的)
   ★ 全站 37 个 CSS 的完整加载序 + 职责表见 index.html 头注释「📂 文件结构速查」
     (单一事实源 —— 旧版 19 个文件各抄一份清单, 且停在 25/26/28/31 四个旧版本;
      加一个 CSS 要改 19 处, 编号还是"分组号"而非真实加载位, 现已收敛)
   [Backlog #152 · 2026-10-01] **portfolio-grid.css 按分节职责再拆 3 层。**
       Backlog #148 已把 14 个 @media 迁到 portfolio-grid-responsive.css;
       本轮继续按**分节职责**把基础层切开, 从 1 个文件变成 4 个基础层:

         portfolio-grid.css            第 1      19 条  §1  主题聚拢 (Theme Clustering)
         portfolio-card.css            第 2      17 条  §2-§3  项目卡本体 + 卡片扩展 FX
         portfolio-entry.css           第 3      44 条  §4  作品集网格 + 项目卡完整定义
         portfolio-card-entry.css      第 4      27 条  §5  入口卡 (STUDIO + LAB 双卡)
         portfolio-grid-responsive.css 第 5 最后   50 条  14 个 @media 响应式 + 覆盖节 (Backlog #148 已拆出, 本轮不动)

       ★ CORS 铁律: **禁 @import**。file:// 下 Origin=null, @import 会
         **静默失败、整段样式丢失**(不报错, 只是不生效)。必须多个独立
         <link> 靠标签顺序保证层叠顺序。
         (禁 @import 的原因见 PROJECT_NOTES.md §13.1 零构建原则)

       ★ 加载顺序: 上面 5 层**必须按所标次序**排。四段基础层的相对顺序与
         拆分前的规则顺序**完全一致**, 所以拆分本身不改变任何属性的计算值。
         portfolio-grid-responsive.css 是纯覆盖层, 必须**最后**。

       ★ 层叠中性 (Backlog #152 实测): 拆成 4 段后用浏览器 CSSOM (http:// 下
         document.styleSheets) 取出**真实解析出的 107 条规则**, 对 819 个
         (selector, 声明属性) 组合逐一求"最终胜出声明", 比较拆分前后两种顺序 ——
         **零差异**。原因: 跨节重复的顶层选择器只有 .project-card /
         .project-logo-wrapper / .project-logo 三个, 且重叠处的声明都带
         `!important`, 而 !important 压过一切非 important 声明, 先后顺序不影响。
         (CSS: !important > 非 important; 同为 important 时才比顺序)

       本文件: §2-§3 项目卡本体 + 卡片扩展 FX (CSSOM 实测 17 条规则)
         持有 .project-card / .project-logo* (§2) 与 .project-card.fluid-wake /
         #fluid-particle-canvas / .card-sonar-ripple* / .project-card.physical-water-ripple
         (§3 流体波纹 / 声纳扩散 / 物理水波) 等**卡片本体与特效**样式。
         ⚠ .project-card 在本文件与 portfolio-entry.css 都有定义, 且重叠处
         带 !important —— 本文件排在 portfolio-entry.css **之前**才是当前语义。
   ========================================================== */
/* ==========================================================
   2. 项目卡 (Project Card) — 完整抽离
   ------------------------------------------------------------
   【Backlog #30 · 卡片宽度自适应 + 日期完整可见】
   · 旧问题: aspect-ratio + max-height: 220px 固定卡片高度, 但大屏下
     .project-logo-wrapper clamp 上限 240px 会撑破卡片, 导致
     .project-meta (含日期) 被 overflow: hidden 裁掉
   · 新方案: 用 min-height 做视觉下限 + flex 自然增长, 让卡片按
     内容自适应高度, 日期永远显示完整
   · 关键三件套:
       .project-card          → min-height: 180px (视觉下限)
                                  (去掉 aspect-ratio 和 max-height)
       .project-logo-wrapper  → flex: 1 1 0; min-height: 0;
                                  (允许收缩, 不挤压 meta)
       .project-meta          → flex: 0 0 auto; min-height: 56px;
                                  (永远显示完整 tag + title + date)
   ========================================================== */
.project-card {
    padding: var(--space-4) var(--space-5);
    /* 视觉下限: 保证 tag + title + date 三行可见 */
    min-height: 180px;
    /* 不再用 aspect-ratio + max-height 锁死卡片高度:
       flex 自然布局让 logo 与 meta 协商高度, 日期不再被裁 */
    flex: 0 0 auto;                   /* 簇内: 不拉伸不收缩 */
    align-self: center;               /* 簇内: 水平居中 (不填满) */
    box-sizing: border-box;
}

.project-logo-wrapper {
    /* 智能响应式高度: 容纳大 logo (约卡片 70% 高) */
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 5px;
    overflow: hidden;
    position: relative;
    /* 关键: flex 自然占满剩余空间, min-height: 0 允许收缩到内容以下 */
    flex: 1 1 0;
    min-height: 0;
    max-height: 180px;                /* 防 logo 撑爆卡片 */
}

/* 空 logo-wrapper (O 卡片): 显示一个占位图标 */
.project-logo-wrapper:empty::before {
    content: "✦";
    font-size: var(--icon-logo-placeholder);
    color: var(--theme-color, #6a7378);
    opacity: 0.4;
    font-weight: 100;
}

.project-logo {
    width: clamp(50px, 8vw, 85px);
    height: clamp(50px, 8vw, 85px);
    flex-shrink: 0;
    max-width: 82%;
    max-height: 82%;
    object-fit: contain;
}


/* ==========================================================
   3. 流体波纹 / 声纳扩散 / 物理水波 (Project Card 扩展)
   ========================================================== */
.project-card {
    transition: transform 0.4s cubic-bezier(0.25, 1, 0.5, 1), box-shadow 0.4s, filter 0.3s !important;
}
.project-card.fluid-wake {
    animation: fluidRipple 0.8s ease-in-out !important;
    filter: drop-shadow(0 0 15px rgba(0, 242, 254, 0.3)) !important;
}

@keyframes fluidRipple {
    0%   { transform: scale(1) skewX(0deg); filter: blur(0px); }
    30%  { transform: scale(1.02) skewX(1deg); filter: blur(0.5px); }
    60%  { transform: scale(0.99) skewX(-0.5deg); filter: blur(0px); }
}

#fluid-particle-canvas {
    position: fixed;
    top: 0; left: 0;
    width: 100vw; height: 100vh;
    z-index: 9;
    pointer-events: none;
}

/* === 声纳扩散 (Diver Sonar) === */
.project-card {
    position: relative !important;
}
.card-sonar-ripple {
    position: absolute;
    top: 0; left: 0;
    width: 100%;
    height: 100%;
    border: 1px solid rgba(0, 242, 254, 0.6);
    border-radius: 0;
    pointer-events: none;
    opacity: 0;
    z-index: 1;
    clip-path: inherit;
}

.card-sonar-ripple.layer-1 {
    animation: sonarWaveExpand 1.6s cubic-bezier(0.1, 0.8, 0.3, 1) forwards;
}
.card-sonar-ripple.layer-2 {
    animation: sonarWaveExpand 1.6s cubic-bezier(0.1, 0.8, 0.3, 1) 0.3s forwards;
}
.card-sonar-ripple.layer-3 {
    animation: sonarWaveExpand 1.6s cubic-bezier(0.1, 0.8, 0.3, 1) 0.6s forwards;
}

@keyframes sonarWaveExpand {
    0%   { opacity: 0.9; transform: scale(0.96); }
    100% { opacity: 0;   transform: scale(1.05); }
}

/* === 物理水波置换 (SVG feDisplacementMap) === */
.project-card.physical-water-ripple {
    animation: realWaterDisplace 0.8s cubic-bezier(0.1, 0.8, 0.3, 1) forwards !important;
}

@keyframes realWaterDisplace {
    0%   { filter: url(#water-displacement-filter) drop-shadow(0 0 0px rgba(0,242,254,0)); }
    50%  { filter: url(#water-displacement-filter) drop-shadow(0 0 20px rgba(0,242,254,0.5)); }
    100% { filter: url(#water-displacement-filter) drop-shadow(0 0 0px rgba(0,242,254,0)); }
}
