/* global React, window */
const { Reveal, GlassChip, Icon, LazyImage, LazyHoverVideo } = window;
const { useEffect, useRef, useState } = React;
const mediaSrc = (src) => encodeURI(src);

/* Data for the four roles */
const WORK = [
  {
    id: "hypic",
    name: "醒图 Hypic · AI 特效",
    en: "XINGTU / HYPIC",
    role: "AI 特效 0→1 负责人 · 供给侧玩法生态",
    time: "2025.03 — 至今",
    kicker: "醒图AI 创作工具",
    kickerSub: "从模型能力 → 到稳定持续出爆款的玩法生态",
    headline: "从「模型能做什么」，到「用户为什么想玩」。",
    summary:
      "醒图 AI 特效的核心问题，是打造一个用户能看得懂、愿意点、愿意玩、愿意分享的玩法生态。我负责将 Seed 系列模型能力转译为用户可感知的玩法，并搭建从模型接入、玩法包装、内容推广到爆款复盘的完整供给链路。",
    metrics: [
      { k: "全球 S 级爆款玩法", v: "74 个", note: "跨区域分发 · 持续出新" },
      { k: "AI 特效导出渗透率提升", v: "0.79% → 2.39%", note: "vs 编辑器整体" },
      { k: "AI 特效阶段拉新", v: "+30%", note: "单玩法高峰 · AI 特效驱动" }
    ],
    thinking: "我负责的不是「接入一个模型」，而是让模型长成一个个用户真的想玩的玩法。",
    layout: "supply",
    narratives: [
      {
        title: "供给侧 · 模型与玩法",
        summary: "围绕「模型接入、效果验证、玩法设计、内容分发」搭建完整闭环，推动 AI 特效从单点能力接入，演进为可持续出爆款的玩法生态。",
        items: [
          {
            idx: "01",
            title: "模型接入 × 测评 SOP × 差异化心智",
            body: "针对 AI 特效供给「难以快速识别模型优势、匹配合适玩法」的问题，主导 Seed 系列核心模型快速接入，搭建醒图模型测评集并沉淀测评 SOP，围绕人物氛围感、动态包装、live 活人感等方向放大醒图差异化心智；同时自建妆容迁移等垂类人像模型，补齐关键效果供给，推动全球跑出爆款共 386 个（如猫脸涂鸦 live、发丝光、冻伤妆）。"
          },
          {
            idx: "02",
            title: "图片玩法动态化 × 组件化提效",
            body: "为提升图片玩法在抖音的分发效果与供给效率，上线图片玩法动态化包装能力，推动图片 × 视频玩法自由组合；并通过组件化能力，将玩法上线效率从 1 周缩短到 1 天。"
          }
        ]
      },
      {
        title: "AI Agent 推广内容自动化生产",
        summary: "为解决 AI 特效推广内容「追热点慢、人工依赖重、供给效率低」的问题，我主导搭建推广内容生产 Agent，把创意输入、脚本规划、素材生成、包装成片和效果评测串成自动化闭环。",
        items: [
          {
            idx: "01",
            title: "内容策略结构化",
            body: "拆解爆款视频的文案、画面和包装规律，提炼并高优落地以三段式（教程 / 痛点 / 结果型）为逻辑框架的强真人口播感高转化内容。"
          },
          {
            idx: "02",
            title: "Agent 工作流搭建与测评",
            body: "将内容生产拆解为意图理解、脚本规划、素材生成、成片包装等原子 Skill，支持自动化调用。建立 MOS 评分与 Badcase 复盘机制，持续优化成片质量，离线成片已达到人工基线。支持推广热点 T+0 快速响应，大幅释放推广内容产能。"
          }
        ],
        videos: [
          { name: "潦草写生 2", src: "assets/agent/潦草写生2.mp4", io: { fn: "潦草写生", in: "assets/agent/io/潦草写生-01.jpg", out: "assets/agent/io/潦草写生-02.png" } },
          { name: "九宫格", src: "https://portfolio-handoff-large-assets.vercel.app/assets/agent/%E4%B9%9D%E5%AE%AB%E6%A0%BC.mp4", io: { fn: "宠物九宫格表情", in: "assets/agent/io/九宫格-01.png", out: "assets/agent/io/九宫格-02.png" } },
          { name: "万圣节", src: "assets/agent/万圣节.mp4", io: { fn: "万圣节变装", in: "assets/agent/io/万圣节-01.png", out: "assets/agent/io/万圣节-02.png" } },
          { name: "潦草写生", src: "assets/agent/潦草写生.mp4", io: { fn: "潦草写生", in: "assets/agent/io/潦草写生-01.jpg", out: "assets/agent/io/潦草写生-02.png" } },
          { name: "老鼠干", src: "assets/agent/老鼠干.mp4", io: { fn: "老鼠干玩偶", in: "assets/agent/io/老鼠干-01.png", out: "assets/agent/io/老鼠干-02.png" } },
          { name: "宠物九宫格", src: "assets/agent/宠物九宫格.mp4", io: { fn: "宠物九宫格表情", in: "assets/agent/io/九宫格-01.png", out: "assets/agent/io/九宫格-02.png" } },
          { name: "又是一年冬", src: "assets/agent/又是一年冬.mp4", io: { fn: "又是一年冬", in: "assets/agent/io/又是一年冬-01.png", out: "assets/agent/io/又是一年冬-02.png" } }
        ]
      }
    ],
    evolution: [
      {
        stage: "好玩",
        en: "PLAYFUL",
        desc: "创意反差、IP 保持、材质变化、互动动作，让用户第一眼产生尝试欲。",
        cases: [
          { name: "扭扭仙人掌", img: "assets/cases/好玩/扭扭仙人掌.gif" },
          { name: "沈星回老鼠干坐地铁", img: "assets/cases/好玩/沈星回老鼠干坐地铁.png" },
          { name: "fitcheck｜冲出屏幕", img: "assets/cases/好玩/fitcheck｜冲出屏幕.png" },
          { name: "假装在 Coachella 现场", img: "assets/cases/好玩/假装在cochella现场_毛毡视觉2（换背景）.png" },
          { name: "冰箱贴 × color walk", img: "assets/cases/好玩/冰箱贴 x color walk.png" },
          { name: "毛绒奶茶", img: "assets/cases/好玩/毛绒奶茶.png" },
          { name: "冰雕材质", img: "assets/cases/好玩/冰雕材质.png" },
          { name: "粉色亚克力", img: "assets/cases/好玩/粉色亚克力.png" },
          { name: "毛线娃娃", img: "assets/cases/好玩/毛线娃娃.png" },
          { name: "建筑冰箱贴 color walk", img: "assets/cases/好玩/建筑冰箱贴colorwalk.png" },
          { name: "漫画书微缩手办", img: "assets/cases/好玩/漫画书微缩手办.png" },
          { name: "电脑前手办小人", img: "assets/cases/好玩/电脑前手办小人.png" },
          { name: "相框树叶雕刻", img: "assets/cases/好玩/相框树叶雕刻.png" },
          { name: "博物馆展品", img: "assets/cases/好玩/博物馆展品.png" },
          { name: "巨大海报同框", img: "assets/cases/好玩/巨大海报同框.png" },
          { name: "怪奇物语对镜拍", img: "assets/cases/好玩/怪奇物语对镜拍.png" },
          { name: "实体互动-手拿贴纸", img: "assets/cases/好玩/实体互动-手拿贴纸.png" },
          { name: "宠物九宫格", img: "assets/cases/好玩/宠物九宫格.png" },
          { name: "对牌｜我是小厨师", img: "assets/cases/好玩/对牌｜我是小厨师.png" },
          { name: "穿越王者国标英雄", img: "assets/cases/好玩/穿越王者国标英雄.png" },
          { name: "闯入 F1 赛车现场", img: "assets/cases/好玩/闯入F1赛车现场.png" },
          { name: "游戏 CG 风格-羽毛球", img: "assets/cases/好玩/游戏cg风格-羽毛球.png" },
          { name: "头像感拉垮手绘", img: "assets/cases/好玩/头像感拉垮手绘.png" },
          { name: "老鼠干-萌宠", img: "assets/cases/好玩/老鼠干-萌宠.png" },
          { name: "负猫版负鼠表情包", img: "assets/cases/好玩/负猫版负鼠表情包.png" },
          { name: "测一测你是什么动物？", img: "assets/cases/好玩/测一测你是什么动物？.png" },
          { name: "派大星郁金香 live", img: "assets/cases/好玩/派大星郁金香live.png" },
          { name: "宠物财神爷｜马上发财", img: "assets/cases/好玩/宠物财神爷｜马上发财.png" },
          { name: "老鼠干回乡过年", img: "assets/cases/好玩/老鼠干回乡过年.png" }
        ]
      },
      {
        stage: "好看",
        en: "AESTHETIC",
        desc: "写真、光影、氛围、妆造、色彩，让用户获得稳定的美感收益。",
        cases: [
          { name: "Jennie 同款红发写真", img: "assets/cases/好看/Jennie同款红发写真.png" },
          { name: "【男性】自然发丝光", img: "assets/cases/好看/【男性】自然发丝光.png" },
          { name: "夏日氧气感写真", img: "assets/cases/好看/夏日氧气感写真.png" },
          { name: "发光人像（爆闪）", img: "assets/cases/好看/发光人像（爆闪_泰国服饰底图）.png" },
          { name: "春日韩系 CCD", img: "assets/cases/好看/春日韩系ccd.png" },
          { name: "佳能 G12 光影氛围", img: "assets/cases/好看/佳能G12光影氛围.png" },
          { name: "二次元光影破碎感", img: "assets/cases/好看/二次元光影破碎感.png" },
          { name: "万物爆闪", img: "assets/cases/好看/万物爆闪.jpg" },
          { name: "韩系夏日氛围感", img: "assets/cases/好看/韩系夏日氛围感.png" },
          { name: "瞳孔特写变装", img: "assets/cases/好看/瞳孔特写变装.gif" },
          { name: "蓝色细闪妆容", img: "assets/cases/好看/蓝色细闪妆容.png" },
          { name: "窗缝眼神光", img: "assets/cases/好看/窗缝眼神光.png" },
          { name: "窗边写真", img: "assets/cases/好看/窗边写真.png" },
          { name: "晚自习氛围感写真", img: "assets/cases/好看/晚自习氛围感写真.png" },
          { name: "列车氛围感自拍", img: "assets/cases/好看/列车氛围感自拍.png" },
          { name: "海上直升机写真", img: "assets/cases/好看/海上直升机写真.png" },
          { name: "海棠花写真二宫格", img: "assets/cases/好看/海棠花写真二宫格.png" },
          { name: "新春金鱼胶片写真", img: "assets/cases/好看/新春金鱼胶片写真.png" },
          { name: "明星情侣电影感烟花对拍", img: "assets/cases/好看/明星情侣电影感烟花对拍.jpg" },
          { name: "棒球赛动态女生", img: "assets/cases/好看/棒球赛动态女生.png" },
          { name: "萌娃樱花三宫格", img: "assets/cases/好看/萌娃樱花三宫格.png" },
          { name: "美式证件照", img: "assets/cases/好看/美式证件照.png" },
          { name: "雨中撑伞气氛", img: "assets/cases/好看/雨中撑伞气氛.png" },
          { name: "逆光彩虹光晕", img: "assets/cases/好看/逆光彩虹光晕.png" },
          { name: "车窗与春天", img: "assets/cases/好看/车窗与春天.gif" },
          { name: "躺在春天里", img: "assets/cases/好看/躺在春天里.png" }
        ]
      },
      {
        stage: "活人感",
        en: "ALIVE",
        desc: "把 AI 效果放回真实场景，让内容更像真实抓拍，是真实生活里的表达。",
        cases: [
          { name: "plog 人物", img: "assets/cases/活人感/plog人物.png" },
          { name: "AI 画展", img: "assets/cases/活人感/AI画展.png" },
          { name: "一键手绘注释", img: "assets/cases/活人感/一键手绘注释.png" },
          { name: "一键旅行手帐风格", img: "assets/cases/活人感/一键旅行手帐风格.png" },
          { name: "二次元 × 立牌同框", img: "assets/cases/活人感/二次元x立牌同框.png" },
          { name: "手机出框自拍", img: "assets/cases/活人感/手机出框自拍.png" },
          { name: "手拿谷子货架", img: "assets/cases/活人感/手拿谷子货架.png" },
          { name: "发财出框写真", img: "assets/cases/活人感/发财出框写真.png" },
          { name: "财神变装", img: "assets/cases/活人感/财神变装.gif" },
          { name: "麻将谐音梗-我发财了", img: "assets/cases/活人感/麻将谐音梗-我发财了.png" },
          { name: "测测谁是马年会发财的人", img: "assets/cases/活人感/测测谁是马年会发财的人.png" },
          { name: "影子涂鸦", img: "assets/cases/活人感/影子涂鸦.png" },
          { name: "母亲节拍立得", img: "assets/cases/活人感/母亲节拍立得.png" },
          { name: "毕业季 × 黑板不念了", img: "assets/cases/活人感/毕业季x黑板不念了.png" },
          { name: "千禧辣妹｜古早感头像加载", img: "assets/cases/活人感/千禧辣妹｜古早感头像加载.png" },
          { name: "everskies 同款像素小人", img: "assets/cases/活人感/everskies同款像素小人.png" },
          { name: "小女警画风小人同框", img: "assets/cases/活人感/小女警画风小人同框.png" },
          { name: "萌宠潦草写生 Live", img: "assets/cases/活人感/萌宠潦草写生Live.gif" }
        ]
      }
    ],
    makeupTransfer: {
      title: "妆容迁移",
      en: "MAKEUP TRANSFER",
      desc: "面向用户「仿妆同款」需求，支持淡妆、浓妆、创意妆等风格一键迁移，在保留核心妆感特征的同时，实现不同人脸的自然适配。",
      groups: [
        { name: "奶酪粉颊妆",     ref: "assets/makeup/01-ref.png", orig: "assets/makeup/01-orig.png", result: "assets/makeup/01-result.png" },
        { name: "氧气感淡妆",     ref: "assets/makeup/02-ref.png", orig: "assets/makeup/02-orig.jpg", result: "assets/makeup/02-result.png" },
        { name: "冷调奶系妆",     ref: "assets/makeup/03-ref.png", orig: "assets/makeup/03-orig.jpg", result: "assets/makeup/03-result.png" },
        { name: "韩系水光妆",     ref: "assets/makeup/04-ref.png", orig: "assets/makeup/04-orig.jpg", result: "assets/makeup/04-result.png" },
        { name: "晒伤感度假妆",   ref: "assets/makeup/05-ref.png", orig: "assets/makeup/05-orig.jpg", result: "assets/makeup/05-result.png" },
        { name: "高级感烟熏妆",     ref: "assets/makeup/06-ref.png", orig: "assets/makeup/06-orig.png", result: "assets/makeup/06-result.png" },
        { name: "复古红唇妆",     ref: "assets/makeup/07-ref.png", orig: "assets/makeup/07-orig.jpg", result: "assets/makeup/07-result.png" },
        { name: "羽睫钻饰创意妆", ref: "assets/makeup/08-ref.jpg", orig: "assets/makeup/08-orig.jpg", result: "assets/makeup/08-result.png" },
        { name: "亮片钻饰节日妆", ref: "assets/makeup/09-ref.jpg", orig: "assets/makeup/09-orig.jpg", result: "assets/makeup/09-result.png" },
        { name: "霓虹绿图形妆",   ref: "assets/makeup/10-ref.jpg", orig: "assets/makeup/10-orig.jpg", result: "assets/makeup/10-result.png" },
        { name: "紫调水钻泪眼妆", ref: "assets/makeup/11-ref.jpg", orig: "assets/makeup/11-orig.jpg", result: "assets/makeup/11-result.png" },
        { name: "太阳图腾创意妆", ref: "assets/makeup/12-ref.jpg", orig: "assets/makeup/12-orig.jpg", result: "assets/makeup/12-result.png" }
      ]
    },
    productPages: {
      title: "产品核心页面",
      desc: "从首页 AI 模板分发、灵感 feed，到模板详情与一键编辑结果页，构成 AI 玩法「发现 → 体验 → 出片」的完整链路。",
      shots: [
        { src: "assets/xingtu/01-home.png",   caption: "首页 · AI 模板" },
        { src: "assets/xingtu/02-feed.png",   caption: "灵感 feed · AI 玩法" },
        { src: "assets/xingtu/03-detail.png", caption: "模板详情页" },
        { src: "assets/xingtu/04-result.png", caption: "一键编辑 · 结果页" },
        { src: "assets/xingtu/05-combo.gif", caption: "玩法组合 文生图x裸眼3D" }
      ]
    }
  },
  {
    id: "jianying-ai",
    name: "剪映中国 · AI 营销成片",
    en: "JIANYING · AI MARKETING",
    role: "AI 成片 0→1 负责人 · 中小商家硬广带货",
    time: "2023.11 — 2025.02",
    kicker: "剪映 AI 营销成片",
    kickerSub: "让中小商家，2 小时的剪辑变成 1 分钟。",
    summary:
      "面向中小商家短视频带货场景，我负责 AI 成片生成工具从 0 到 1 搭建，围绕脚本、画面、配乐、剪辑编排设计多模态成片 workflow，帮助商家快速生成可投放的带货视频。",
    metrics: [
      { k: "使用 MAU", v: "303w" },
      { k: "剪映 AI 工具", v: "Top 1" }
    ],
    layout: "supply",
    narratives: [
      {
        items: [
          {
            idx: "01",
            title: "商业化场景落地",
            body: "围绕「商品名输入 → 批量生成带货视频 → 导出投放」的商家链路，完成 AI 成片工具上线与转化闭环。使用 MAU 从 0 增长至 147w+，使用-导出转化率达到 23%。"
          },
          {
            idx: "02",
            title: "多模态成片 Workflow",
            body: "设计脚本生成、商品图理解、画面匹配、配乐推荐与剪辑编排流程，降低商家从素材到成片的创作门槛。将人工剪辑平均 2 小时+ 的流程压缩到 1 分钟级生成。"
          },
          {
            idx: "03",
            title: "算法策略迭代",
            body: "包含多模态生文 MLLM 优化、文画匹配 embedding 优化、端到端智能包装优化、卖点推荐智能图谱搭建与智能配乐，使 MOS 评分 3.6 → 4.0，采纳率 64% → 77%，硬广生文质量达到业界 Top 1。"
          },
          {
            idx: "04",
            title: "本地生活场景拓展",
            body: "基于内容分析识别高频投放场景，重点拓展美食、生活服务等行业模板。上线 2 个月内完成生服能力建设，牵引三方算法上线，POI 结构化信息接入后链路使用率 15% → 31%。"
          }
        ],
        videos: [
          { name: "电商 · 头绳", src: "assets/marketing/电商 - 头绳.mp4" },
          { name: "电商 · 去污湿巾", src: "assets/marketing/电商- 去污湿巾.mp4" },
          { name: "电商 · 榴莲冰淇淋", src: "assets/marketing/电商-榴莲冰淇淋.mp4" },
          { name: "生服 · 丽人", src: "assets/marketing/生服 - 丽人.mp4" },
          { name: "生服 · 密室", src: "assets/marketing/生服 - 密室.mp4" }
        ]
      }
    ]
  },
  {
    id: "capcut",
    name: "CapCut · 视频编辑器重构",
    en: "CAPCUT · EDITOR REBUILD",
    role: "产品体验设计师",
    time: "2022.08 — 2023.11",
    kicker: "CapCut AI 视频创作工具",
    summary:
      "CapCut 视频编辑器 + 智能小工具：负责编辑器重构。旧版编辑器空态引导弱、小屏竖屏画布空间小、编辑操作复杂；新版更重画布上屏编辑体验，同时聚焦降低操作复杂度并优化操作动线。优化后新用户使导 26% → 28%；字幕模块渗透 3% → 8%。",
    metrics: [
      { k: "新用户使用-导出转化", v: "26% → 28%" },
      { k: "字幕模块渗透", v: "3% → 8%" }
    ],
    layout: "supply",
    beforeAfter: {
      title: "编辑器重构 · 改版前后",
      desc: "围绕「空态引导」与「上屏编辑」两条核心动线重构编辑器：从弱引导、小画布、操作割裂，走向强引导、大画布、画布与轨道时间实时联动。",
      groups: [
        {
          label: "空态",
          en: "EMPTY STATE",
          desc: "旧版空态仅有一块黑色画布与零散入口，用户不知从何开始；新版以素材区前置、明确的上传引导与示例占位，让「第一步做什么」一目了然。",
          before: { src: "assets/capcut/empty-before.png", name: "空态 · 改版前" },
          after: { src: "assets/capcut/empty-after.gif", name: "空态 · 改版后" }
        },
        {
          label: "上屏编辑 · 轨道时间联动",
          en: "ON-CANVAS EDITING",
          desc: "旧版画布小、属性面板与轨道割裂，改字幕要在右侧面板里来回切换；新版主打「上屏编辑」体验——放大画布，让用户直接在画面上拖拽调整字幕宽度、双击修改内容、就地缩放与定位元素，所见即所得。画布元素与时间轴轨道实时联动：拖动轨道即定位画面，选中画面即高亮对应轨道，编辑动线更短、更直觉。",
          before: { src: "assets/capcut/edit-before.png", name: "上屏编辑 · 改版前" },
          after: { src: "https://portfolio-handoff-large-assets.vercel.app/assets/capcut/edit-after.gif", name: "上屏编辑 · 改版后" }
        }
      ]
    }
  },
  {
    id: "class",
    name: "剪映中国 · 创作课堂",
    en: "JIANYING · CREATOR CLASS",
    role: "设计 Owner · 课程商业化 / 创作者成长",
    time: "2021.04 — 2022.08",
    kicker: "剪映创作课堂",
    kickerSub: "把「学剪辑」做成一件，创作者愿意来上的事。",
    summary:
      "作为设计 Owner 服务商业化目标，完成剪映课堂付费课程建设、直播 / 录播课型 + 学练评链路建设、端内首页分发 + 端外推广，搭建从内容分发到付费转化的完整成长链路。实现课程 GMV 1755w，付费人数 9w 人。",
    metrics: [
      { k: "课程 GMV", v: "1755w" },
      { k: "付费人数", v: "9w" }
    ],
    layout: "supply",
    productPages: {
      title: "产品核心页面",
      desc: "从首页分发、免费热门玩法、课程详情，到直播课型与关注 feed，构成创作者「发现 → 学习 → 付费 → 留存」的完整链路。",
      shots: [
        { src: "assets/class/01-home.png",   caption: "首页 · 推荐分发" },
        { src: "assets/class/02-hot.png",     caption: "免费 · 最热玩法" },
        { src: "assets/class/03-detail.png",  caption: "课程详情页" },
        { src: "assets/class/04-live.png",    caption: "直播 · 课型" },
        { src: "assets/class/05-follow.png",  caption: "关注 · 创作者 feed" }
      ]
    }
  }
];

/* Sidebar */
function WorkSidebar({ active, onJump }) {
  return (
    <aside className="hidden lg:block w-44 flex-none">
      <div className="sticky top-32 pl-4">
        <ol className="flex flex-col gap-4">
          {WORK.map((w, i) => {
            const on = i === active;
            return (
              <li key={w.id}>
                <button
                  onClick={() => onJump(i)}
                  className={"group text-left w-full transition-all " + (on ? "" : "opacity-50 hover:opacity-90")}
                >
                  <div className="flex items-baseline gap-3">
                    <span className={"font-mono text-[10px] tabular-nums " + (on ? "text-[#ADF01D]" : "text-white/40")}>0{i + 1}</span>
                    <div>
                      <div className={"font-cn text-base leading-tight transition-colors " + (on ? "text-[#ADF01D]" : "text-white")}>{w.name}</div>
                    </div>
                  </div>
                  {on && (
                    <div className="mt-2 ml-7 h-[2px] w-12 bg-gradient-to-r from-[#ADF01D] to-transparent rounded-full"></div>
                  )}
                </button>
              </li>
            );
          })}
        </ol>
      </div>
    </aside>
  );
}

/* Helpers — placeholder visuals (for each layout) */
function PhonePh({ idx = 1, ratio = "9/19", label = "" }) {
  return (
    <div className="ph rounded-[20px] flex flex-col items-center justify-end p-2 relative" style={{ aspectRatio: ratio }}>
      <span className="absolute top-2 left-1/2 -translate-x-1/2 h-1 w-8 rounded-full bg-white/20"></span>
      <span className="ph-label mb-1">{label || `SCREEN 0${idx}`}</span>
    </div>
  );
}

function FeatureCard({ children, className = "" }) {
  return <div className={"liquid-glass rounded-2xl p-5 " + className}>{children}</div>;
}

/* Layout A — Hero block + 6 thumbnails strip */
function LayoutThumbs({ w }) {
  return (
    <div className="space-y-8">
      <HeroBlock w={w} />
      <Reveal y={20}>
        <div className="liquid-glass rounded-3xl p-6 md:p-8">
          <div className="flex items-baseline justify-between mb-5">
            <div className="font-cn text-lg font-bold text-white/95">代表功能 / 模块</div>
            <div className="font-mono text-[10px] text-white/45 tracking-[0.2em]">06 MODULES</div>
          </div>
          <div className="grid grid-cols-3 md:grid-cols-6 gap-3">
            {w.tags.slice(0, 6).map((t, i) => (
              <div key={i} className="ph rounded-2xl relative overflow-hidden" style={{ aspectRatio: "3/4" }}>
                <span className="absolute top-2 left-2 ph-label">0{i + 1}</span>
                <span className="absolute bottom-2 left-2 right-2 font-cn text-xs text-white/90">{t}</span>
              </div>
            ))}
          </div>
        </div>
      </Reveal>
    </div>
  );
}

/* Layout B — Before / After + iteration metrics */
function LayoutBeforeAfter({ w }) {
  return (
    <div className="space-y-8">
      <HeroBlock w={w} />
      <Reveal y={20}>
        <div className="liquid-glass rounded-3xl p-6 md:p-8">
          <div className="flex items-baseline justify-between mb-5">
            <div className="font-cn text-lg font-bold text-white/95">改版前后 · 中小商家硬广带货视频创作</div>
            <div className="font-mono text-[10px] text-white/45 tracking-[0.2em]">BEFORE / AFTER</div>
          </div>
          <div className="grid grid-cols-2 gap-4">
            {[
              { label: "BEFORE", note: "人工剪辑 · 2 小时 / 支 · 中小商家成本极高", color: "text-rose-200/80" },
              { label: "AFTER · AI 成片", note: "AI workflow · 1 分钟 / 支 · MOS 4.0 / 采纳率 77%", color: "text-emerald-200/90" }
            ].map((b, i) => (
              <div key={i} className="ph rounded-2xl relative" style={{ aspectRatio: "16/10" }}>
                <span className={"absolute top-3 left-3 font-mono text-[11px] tracking-[0.25em] " + b.color}>{b.label}</span>
                <span className="absolute bottom-3 left-3 right-3 font-cn text-sm text-white/85">{b.note}</span>
                <span className="ph-label absolute right-3 top-3">SCREEN RECORDING</span>
              </div>
            ))}
          </div>
          {w.extraMetrics && (
            <div className="grid grid-cols-2 gap-4 mt-5">
              {w.extraMetrics.map((m, i) => (
                <div key={i} className="liquid-glass rounded-2xl p-4">
                  <div className="font-mono text-[10px] text-white/50 tracking-[0.25em] mb-1">{m.label}</div>
                  <div className="font-heading italic text-white text-2xl leading-tight">{m.v}</div>
                  <div className="font-cn text-[12px] text-white/70 mt-1">{m.note}</div>
                </div>
              ))}
            </div>
          )}
        </div>
      </Reveal>
    </div>
  );
}

/* Layout D — Supply-side ecosystem (醒图 AI 特效) */
function LayoutSupply({ w }) {
  const [activeVideo, setActiveVideo] = useState(null);
  const [ioPop, setIoPop] = useState(null); // { io, rect } floating input→output preview

  // Close on Esc
  useEffect(() => {
    if (!activeVideo) return;
    const onKey = (e) => { if (e.key === "Escape") setActiveVideo(null); };
    window.addEventListener("keydown", onKey);
    document.body.style.overflow = "hidden";
    return () => {
      window.removeEventListener("keydown", onKey);
      document.body.style.overflow = "";
    };
  }, [activeVideo]);

  return (
    <>
    <div className="space-y-10">
      {/* Hero block — custom for 醒图 */}
      <Reveal>
        <div className="flex items-start justify-between gap-6 md:gap-10 flex-wrap">
          <div className="flex-1 min-w-[280px]">
            <h3 className="font-cn text-[1.7rem] sm:text-4xl md:text-5xl lg:text-6xl text-white font-bold leading-[1.15] tracking-tight mb-3">
              {w.kicker}
            </h3>
            {w.kickerSub && (
              <p className="hidden">
                {w.kickerSub}
              </p>
            )}

            <div className="flex items-center gap-3 flex-wrap mb-6">
              <span className="font-cn text-[15px] md:text-base text-white/80">{w.role}</span>
              <span className="font-mono text-[12px] text-white/35">·</span>
              <span className="font-mono text-[14px] md:text-[15px] text-white/65 tracking-[0.15em]">{w.time}</span>
            </div>

            <p className="font-cn text-white/80 text-[15px] md:text-base leading-[1.95] max-w-2xl">
              {w.summary}
            </p>
          </div>

          {w.metrics && w.metrics.length > 0 && (
            <div className="grid grid-cols-2 gap-2.5 md:flex md:flex-col md:gap-3 w-full md:w-72 flex-none">
              {w.metrics.map((m, i) => (
                <div key={i} className="liquid-glass rounded-xl md:rounded-2xl px-3 py-2.5 md:px-4 md:py-3">
                  <div className="font-mono text-[9px] md:text-[10px] text-white/55 tracking-[0.14em] md:tracking-[0.2em] mb-1 md:mb-1.5">{m.k}</div>
                  <div className="font-cn font-light text-[1.4rem] md:text-[2.25rem] text-white leading-none tracking-tight tabular-nums">{m.v}</div>
                </div>
              ))}
            </div>
          )}
        </div>
      </Reveal>

      {/* Work narratives — each is a glass card with its own title/summary + numbered items */}
      {(() => {
        const list = w.narratives || (w.breakdown ? [{ items: w.breakdown }] : []);
        return list.map((n, ni) => (
          <Reveal key={ni} y={20}>
            <div className="liquid-glass rounded-2xl md:rounded-3xl p-5 md:p-10">
              {n.title && (
                <div className="font-cn text-xl md:text-3xl text-white font-bold mb-3 leading-snug">{n.title}</div>
              )}
              {n.summary && (
                <p className="font-cn text-[13px] md:text-[15px] text-white/65 leading-[1.85] md:leading-[1.9] max-w-3xl mb-6 md:mb-8">{n.summary}</p>
              )}
              <div className="space-y-7 md:space-y-9">
                {n.items.map((b, i) => (
                  <div key={i} className="flex flex-col md:flex-row gap-1.5 md:gap-7">
                    <span className="font-mono text-[2rem] md:text-[3.3rem] font-black italic text-[#ADF01D]/90 tracking-tight tabular-nums leading-none md:pt-1 flex-none md:w-20">{b.idx}</span>
                    <div className="flex-1">
                      <div className="font-cn text-lg md:text-2xl text-white font-bold mb-2 md:mb-2.5 leading-snug">{b.title}</div>
                      <p className="font-cn text-sm md:text-base text-white/70 leading-[1.9] md:leading-[2] max-w-3xl">{b.body}</p>
                    </div>
                  </div>
                ))}
              </div>

              {/* Optional video reel for this narrative */}
              {n.videos && n.videos.length > 0 && (
                <div className="mt-10 pt-8 border-t border-white/10">
                  <div className="flex items-baseline justify-between mb-5">
                    <div className="font-cn text-base font-bold text-white/95">{n.videosTitle || "agent 输出成片示例"}</div>
                    <span className="md:hidden font-mono text-[10px] tracking-[0.2em] text-white/35">双列展示</span>
                  </div>
                  <div className="md:overflow-x-auto no-scrollbar md:-mx-1 md:px-1 pb-2" style={{ scrollSnapType: "x proximity", WebkitOverflowScrolling: "touch" }}>
                    <div className="grid grid-cols-2 gap-2.5 md:flex md:gap-3 md:w-max">
                      {n.videos.map((v, i) => {
                        const isVideo = /\.(mp4|webm|mov|m4v)$/i.test(v.src);
                        return (
                          <button
                            key={i}
                            type="button"
                            onClick={() => setActiveVideo(v)}
                            onMouseEnter={(e) => { if (v.io) setIoPop({ io: v.io, rect: e.currentTarget.getBoundingClientRect() }); }}
                            onMouseLeave={() => setIoPop(null)}
                            className="group ph rounded-lg md:rounded-xl relative md:flex-none w-full md:w-[150px] overflow-hidden cursor-pointer text-left"
                            style={{ aspectRatio: "9/16", scrollSnapAlign: "start" }}
                          >
                            {isVideo ? (
                              <LazyHoverVideo
                                src={v.src}
                                className="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
                              />
                            ) : (
                              <LazyImage
                                src={v.src}
                                alt={v.name}
                                className="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
                              />
                            )}
                            {/* Play icon overlay (video only) */}
                            {isVideo && (
                              <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
                                <div className="relative">
                                  {/* Outer halo ring (appears on hover) */}
                                  <div className="absolute inset-[-7px] rounded-full border border-white/30 scale-90 opacity-0 group-hover:scale-100 group-hover:opacity-100 transition-all duration-300"></div>
                                  {/* Main button */}
                                  <div className="relative w-10 h-10 rounded-full border border-white/70 bg-black/45 backdrop-blur-md flex items-center justify-center transition-all duration-300 group-hover:bg-white group-hover:border-white group-hover:scale-105 group-hover:shadow-[0_0_22px_rgba(255,255,255,0.4)]">
                                    <svg width="11" height="11" viewBox="0 0 12 12" className="ml-[2px] fill-white transition-colors duration-300 group-hover:fill-black">
                                      <path d="M2 1.5l8 4.5-8 4.5V1.5z" />
                                    </svg>
                                  </div>
                                </div>
                              </div>
                            )}
                            {/* Hover name */}
                            <div className="absolute inset-x-0 bottom-0 px-2 pb-2 pt-6 bg-gradient-to-t from-black/95 via-black/50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none">
                              <span className="font-cn text-[11px] text-white leading-tight line-clamp-2">{v.name}</span>
                            </div>
                          </button>
                        );
                      })}
                    </div>
                  </div>
                </div>
              )}

              {/* Evolution lives inside the first narrative card */}
              {ni === 0 && w.evolution && (
                <div className="mt-12 pt-10 border-t border-white/10">
                  <div className="flex items-baseline justify-between mb-5 md:mb-6">
                    <div className="font-cn text-base md:text-lg font-bold text-white/95">演进线 · 爆款风格</div>
                    <span className="md:hidden font-mono text-[10px] tracking-[0.2em] text-white/35">横滑查看 →</span>
                  </div>

                  {/* Evolution arrow */}
                  <div className="flex items-center gap-3 md:gap-6 mb-7 md:mb-8 flex-wrap">
                    {w.evolution.map((e, i) => (
                      <React.Fragment key={i}>
                        <div className="flex flex-col">
                          <span className="font-cn text-lg md:text-3xl text-white font-bold">{e.stage}</span>
                        </div>
                        {i < w.evolution.length - 1 && (
                          <span className="font-mono text-lg md:text-2xl text-[#ADF01D]/60">→</span>
                        )}
                      </React.Fragment>
                    ))}
                  </div>

                  <div className="space-y-7">
                    {w.evolution.map((e, i) => (
                      <div key={i}>
                        {e.desc && (
                          <p className="font-cn text-[13px] md:text-[15px] text-white/70 leading-relaxed mb-3 max-w-3xl">
                            <span className="font-cn text-sm md:text-lg text-white mr-2 font-bold">{e.stage}、</span>{e.desc}
                          </p>
                        )}
                        <div className="overflow-x-auto no-scrollbar -mx-1 px-1 pb-2" style={{ scrollSnapType: "x proximity", WebkitOverflowScrolling: "touch" }}>
                          <div className="flex gap-2" style={{ width: "max-content" }}>
                            {e.cases.map((c, j) => {
                              const name = typeof c === "string" ? c : c.name;
                              const img = typeof c === "string" ? null : c.img;
                              return (
                                <div
                                  key={j}
                                  className="group ph rounded-lg md:rounded-xl relative flex-none overflow-hidden w-[76px] md:w-[100px]"
                                  style={{ aspectRatio: "9/16", scrollSnapAlign: "start" }}
                                >
                                  {img && (
                                    <LazyImage
                                      src={img}
                                      alt={name}
                                      className="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
                                    />
                                  )}
                                  {!img && (
                                    <span className="absolute top-1.5 left-1.5 ph-label text-[8px]">0{j + 1}</span>
                                  )}
                                  <div className="absolute inset-x-0 bottom-0 px-2 pb-2 pt-6 bg-gradient-to-t from-black/95 via-black/55 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none">
                                    <span className="font-cn text-[10px] text-white leading-tight line-clamp-2">{name}</span>
                                  </div>
                                </div>
                              );
                            })}
                          </div>
                        </div>
                      </div>
                    ))}
                  </div>
                </div>
              )}

              {/* Makeup transfer — grouped triplets (参考妆容 / 原图 / 结果图) */}
              {ni === 0 && w.makeupTransfer && (
                <div className="mt-12 pt-10 border-t border-white/10">
                  <div className="flex items-baseline justify-between mb-3">
                    <div className="font-cn text-base md:text-lg font-bold text-white/95">{w.makeupTransfer.title}</div>
                  </div>
                  <p className="font-cn text-[13px] md:text-[15px] text-white/70 leading-relaxed mb-5 md:mb-6 max-w-3xl">
                    {w.makeupTransfer.desc}
                  </p>
                  <div className="overflow-x-auto no-scrollbar -mx-1 px-1 pb-2" style={{ scrollSnapType: "x proximity", WebkitOverflowScrolling: "touch" }}>
                    <div className="flex gap-4" style={{ width: "max-content" }}>
                      {w.makeupTransfer.groups.map((g, gi) => {
                        const trio = [
                          { src: g.ref, label: "参考妆容" },
                          { src: g.orig, label: "原图" },
                          { src: g.result, label: "结果图" }
                        ];
                        return (
                          <div key={gi} className="flex-none" style={{ scrollSnapAlign: "start" }}>
                            <div className="liquid-glass rounded-xl md:rounded-2xl p-2 md:p-2.5">
                              <div className="flex gap-1 md:gap-1.5">
                                {trio.map((t, ti) => (
                                  <button
                                    key={ti}
                                    type="button"
                                    onClick={() => setActiveVideo({ src: t.src, name: g.name + " · " + t.label })}
                                    className="group relative overflow-hidden rounded-md md:rounded-lg bg-white/5 cursor-pointer w-[66px] md:w-[96px]"
                                    style={{ aspectRatio: "3/4" }}
                                  >
                                    <LazyImage
                                      src={t.src}
                                      alt={g.name + " " + t.label}
                                      className="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
                                    />
                                    <span className="absolute inset-x-0 bottom-0 text-center font-cn text-[9px] text-white/90 py-0.5 bg-gradient-to-t from-black/80 to-transparent pt-3">{t.label}</span>
                                    {/* arrow between original and result */}
                                  </button>
                                ))}
                              </div>
                              <div className="mt-2 text-center font-cn text-[11px] md:text-[12px] text-white/85 tracking-wide">{g.name}</div>
                            </div>
                          </div>
                        );
                      })}
                    </div>
                  </div>
                </div>
              )}

              {/* Product core pages — compact phone screens, horizontal scroll */}
              {ni === 0 && w.productPages && (
                <div className="mt-12 pt-10 border-t border-white/10">
                  <div className="mb-3 font-cn text-lg font-bold text-white/95">{w.productPages.title}</div>
                  <p className="font-cn text-sm md:text-[15px] text-white/70 leading-relaxed mb-6 max-w-3xl">
                    {w.productPages.desc}
                  </p>
                  <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3">
                      {w.productPages.shots.map((s, si) => (
                        <button
                          key={si}
                          type="button"
                          onClick={() => setActiveVideo({ src: s.src, name: s.caption })}
                          className="group text-left cursor-pointer w-full"
                        >
                          <div className="relative rounded-[22px] overflow-hidden border border-white/12 bg-black"
                               style={{ aspectRatio: "1080 / 2340", boxShadow: "0 10px 30px -12px rgba(0,0,0,0.7)" }}>
                            <LazyImage
                              src={s.src}
                              alt={s.caption}
                              className="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-[1.03]"
                            />
                          </div>
                          <div className="mt-2 text-center font-cn text-[12px] text-white/80">{s.caption}</div>
                        </button>
                      ))}
                  </div>
                </div>
              )}
            </div>
          </Reveal>
        ));
      })()}

      {/* Before / After comparison — paired groups (空态 → 上屏编辑) */}
      {w.beforeAfter && (
        <Reveal y={20}>
          <div className="liquid-glass rounded-2xl md:rounded-3xl p-5 md:p-10">
            <div className="font-cn text-xl md:text-3xl text-white font-bold mb-3 leading-snug">
              {w.beforeAfter.title}
            </div>
            <p className="font-cn text-[13px] md:text-[15px] text-white/65 leading-[1.9] max-w-3xl mb-7 md:mb-9">
              {w.beforeAfter.desc}
            </p>

            <div className="space-y-12">
              {w.beforeAfter.groups.map((g, gi) => (
                <div key={gi}>
                  {/* group heading */}
                  <div className="flex items-baseline gap-3 flex-wrap mb-2">
                    <span className="font-mono text-lg font-bold italic text-[#ADF01D]/80 tracking-wide tabular-nums">{String(gi + 1).padStart(2, "0")}</span>
                    <span className="font-cn text-lg md:text-xl text-white font-bold">{g.label}</span>
                    <span className="font-mono text-[10px] text-white/40 tracking-[0.2em]">{g.en}</span>
                  </div>
                  {g.desc && (
                    <p className="font-cn text-sm md:text-[15px] text-white/60 leading-[1.85] max-w-3xl mb-5">{g.desc}</p>
                  )}

                  {/* before / after pair */}
                  <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
                    {[
                      { ...g.before, tag: "改版前", en: "BEFORE", accent: "text-rose-200/80", ring: "border-rose-300/20" },
                      { ...g.after, tag: "改版后", en: "AFTER", accent: "text-emerald-200/90", ring: "border-emerald-300/25" }
                    ].map((s, si) => (
                      <button
                        key={si}
                        type="button"
                        onClick={() => setActiveVideo({ src: s.src, name: s.name, wide: true })}
                        className={"group relative block w-full text-left rounded-2xl overflow-hidden border bg-black/40 cursor-pointer transition-colors hover:border-white/30 " + s.ring}
                      >
                        <div className="relative" style={{ aspectRatio: "2160 / 1122" }}>
                          <LazyImage
                            src={s.src}
                            alt={s.name}
                            className="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-[1.02]"
                          />
                          {/* corner tag */}
                          <span className="absolute top-3 left-3 flex items-center gap-2 px-2.5 py-1 rounded-full bg-black/55 backdrop-blur-md border border-white/15">
                            <span className={"font-mono text-[9px] font-bold tracking-[0.22em] " + s.accent}>{s.en}</span>
                            <span className="font-cn text-[11px] font-bold text-white/90">{s.tag}</span>
                          </span>
                          {/* zoom hint */}
                          <span className="absolute bottom-3 right-3 w-7 h-7 rounded-full bg-black/45 backdrop-blur-md border border-white/20 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
                            <svg width="12" height="12" viewBox="0 0 16 16" fill="none">
                              <circle cx="7" cy="7" r="4.5" stroke="white" strokeWidth="1.3" />
                              <path d="M10.5 10.5L14 14" stroke="white" strokeWidth="1.3" strokeLinecap="round" />
                            </svg>
                          </span>
                        </div>
                      </button>
                    ))}
                  </div>
                </div>
              ))}
            </div>
          </div>
        </Reveal>
      )}

      {/* Product core pages — standalone card (for cases without narratives, e.g. 创作课堂) */}
      {w.productPages && !(w.narratives || w.breakdown) && (
        <Reveal y={20}>
          <div className="liquid-glass rounded-2xl md:rounded-3xl p-5 md:p-10">
            <div className="font-cn text-xl md:text-3xl text-white font-bold mb-3 leading-snug">{w.productPages.title}</div>
            <p className="font-cn text-[15px] md:text-base text-white/65 leading-[1.95] max-w-3xl mb-7">
              {w.productPages.desc}
            </p>
            <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3">
                {w.productPages.shots.map((s, si) => (
                  <button
                    key={si}
                    type="button"
                    onClick={() => setActiveVideo({ src: s.src, name: s.caption })}
                    className="group text-left cursor-pointer w-full"
                  >
                    <div className="relative rounded-[22px] overflow-hidden border border-white/12 bg-black"
                         style={{ aspectRatio: "1080 / 2340", boxShadow: "0 10px 30px -12px rgba(0,0,0,0.7)" }}>
                      <LazyImage
                        src={s.src}
                        alt={s.caption}
                        className="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-[1.03]"
                      />
                    </div>
                    <div className="mt-2 text-center font-cn text-[12px] text-white/80">{s.caption}</div>
                  </button>
                ))}
            </div>
          </div>
        </Reveal>
      )}
    </div>

    {/* Video / image lightbox — portaled to <body> so position:fixed escapes
        the StickyWorkCard's CSS transform (a transformed ancestor would
        otherwise become the containing block and clip/mis-position it). */}
    {activeVideo && ReactDOM.createPortal((() => {
      const isVideo = /\.(mp4|webm|mov|m4v)$/i.test(activeVideo.src);
      return (
        <div
          className="fixed inset-0 z-[100] bg-black/90 backdrop-blur-md flex items-center justify-center p-6 md:p-12"
          onClick={() => setActiveVideo(null)}
        >
          <button
            type="button"
            onClick={(e) => { e.stopPropagation(); setActiveVideo(null); }}
            className="absolute top-6 right-6 w-11 h-11 rounded-full bg-white/10 hover:bg-white/20 border border-white/20 flex items-center justify-center text-white text-2xl leading-none transition-colors z-10"
            aria-label="Close"
          >×</button>
          <div className={"relative w-full " + (activeVideo.wide ? "max-w-[1100px]" : "max-w-[440px]")} onClick={(e) => e.stopPropagation()}>
            {isVideo ? (
              <video
                key={activeVideo.src}
                src={activeVideo.src}
                ref={(el) => { if (el) el.play().catch(() => {}); }}
                controls
                autoPlay
                playsInline
                className="w-full rounded-2xl shadow-2xl bg-black max-h-[80vh]"
                style={{ aspectRatio: "9/16", objectFit: "contain" }}
              />
            ) : (
              <img
                src={activeVideo.src}
                alt={activeVideo.name}
                className="w-full rounded-2xl shadow-2xl bg-black max-h-[80vh] object-contain"
              />
            )}
            <div className="mt-3 text-center font-cn text-sm text-white/85">{activeVideo.name}</div>
          </div>
        </div>
      );
    })(), document.body)}

    {/* Hover floating window — input → output preview for agent promo videos */}
    {ioPop && ReactDOM.createPortal((() => {
      const popW = 256;
      const placeBelow = ioPop.rect.top < 250;
      const left = Math.max(10, Math.min(window.innerWidth - popW - 10, ioPop.rect.left + ioPop.rect.width / 2 - popW / 2));
      const style = placeBelow
        ? { left, top: ioPop.rect.bottom + 12, width: popW }
        : { left, top: ioPop.rect.top - 12, width: popW, transform: "translateY(-100%)" };
      return (
        <div
          className="fixed z-[95] pointer-events-none rounded-2xl border border-white/15 bg-[#0c0c0e]/95 backdrop-blur-xl p-3.5 shadow-[0_24px_60px_-12px_rgba(0,0,0,0.8)]"
          style={style}
        >
          <div className="font-cn text-[17px] font-bold text-white leading-tight mb-2.5">输入信息</div>
          <div className="font-cn text-[13px] text-white/55 leading-relaxed mb-0.5">功能名称：{ioPop.io.fn}</div>
          <div className="font-cn text-[13px] text-white/55 leading-relaxed mb-3">玩法 before / after 图</div>
          <div className="flex items-center gap-2.5">
            <div className="flex-1 relative rounded-lg overflow-hidden bg-black/40" style={{ aspectRatio: "3/4" }}>
              <LazyImage src={mediaSrc(ioPop.io.in)} alt="before" className="absolute inset-0 w-full h-full object-cover" rootMargin="1200px" />
            </div>
            <span className="font-mono text-xl text-[#ADF01D]/80 flex-none">→</span>
            <div className="flex-1 relative rounded-lg overflow-hidden bg-black/40" style={{ aspectRatio: "3/4" }}>
              <LazyImage src={mediaSrc(ioPop.io.out)} alt="after" className="absolute inset-0 w-full h-full object-cover" rootMargin="1200px" />
            </div>
          </div>
        </div>
      );
    })(), document.body)}
    </>
  );
}

/* Layout C — two stacked narrative blocks (supply side + promotion side) */
function LayoutStacked({ w }) {
  const blocks = w.extraBlocks || [
    { kicker: "01 · SCREEN", title: "占位标题", body: "占位描述" },
    { kicker: "02 · SCREEN", title: "占位标题", body: "占位描述" }
  ];
  return (
    <div className="space-y-8">
      <HeroBlock w={w} />
      <Reveal y={20}>
        <div className="liquid-glass rounded-3xl p-6 md:p-8 space-y-5">
          <div className="flex items-baseline justify-between">
            <div className="font-cn text-lg font-bold text-white/95">两条线 · 供给 × 推广</div>
            <div className="font-mono text-[10px] text-white/45 tracking-[0.2em]">02 NARRATIVES</div>
          </div>

          {blocks.map((b, i) => (
            <div key={i} className="grid grid-cols-1 md:grid-cols-5 gap-4 items-stretch">
              <div className="ph rounded-2xl relative md:col-span-3" style={{ aspectRatio: "16/8" }}>
                <span className="absolute top-3 left-3 font-mono text-[10px] text-white/55 tracking-[0.25em]">{b.kicker}</span>
                <span className="absolute bottom-3 left-3 right-3 font-cn text-sm text-white/85">{b.title}</span>
              </div>
              <div className="liquid-glass rounded-2xl p-5 md:col-span-2 flex flex-col justify-center">
                <div className="font-mono text-[10px] text-white/45 tracking-[0.25em] mb-2">{b.kicker.split("·")[0].trim()}</div>
                <div className="font-cn text-lg text-white leading-snug mb-3">{b.title}</div>
                <div className="font-cn text-sm text-white/70 leading-relaxed">{b.body}</div>
              </div>
            </div>
          ))}
        </div>
      </Reveal>
    </div>
  );
}

/* Shared hero block at top of each app section */
function HeroBlock({ w }) {
  return (
    <Reveal>
      <div className="flex items-start justify-between gap-8 flex-wrap">
        <div className="flex-1 min-w-[280px]">
          <div className="flex items-center gap-3 flex-wrap mb-3">
            <span className="font-mono text-[14px] md:text-[15px] text-white/65 tracking-[0.15em]">{w.time}</span>
            <span className="font-mono text-[12px] text-white/45">·</span>
            <span className="font-cn text-[15px] md:text-base text-white/80">{w.role}</span>
          </div>
          <h3 className="font-cn text-3xl md:text-5xl font-bold text-white leading-[1.1] tracking-tight max-w-3xl">
            {w.headline}
          </h3>
          <p className="font-cn text-white/70 text-base md:text-lg mt-5 leading-relaxed max-w-2xl">
            {w.summary}
          </p>
        </div>

        <div className="flex flex-col gap-3 w-full md:w-72 flex-none">
          {w.metrics.map((m, i) => (
            <div key={i} className="liquid-glass rounded-2xl px-4 py-3">
              <div className="font-mono text-[10px] text-white/50 tracking-[0.2em]">{m.k}</div>
              <div className="font-heading italic text-3xl text-white leading-tight">{m.v}</div>
              <div className="font-cn text-[11px] text-white/55 mt-0.5">{m.note}</div>
            </div>
          ))}
        </div>
      </div>

      {/* thinking quote */}
      <div className="mt-6 flex items-start gap-4 pl-1">
        <span className="font-heading italic text-5xl text-[#ADF01D]/70 leading-none">"</span>
        <p className="font-cn text-base md:text-lg text-white/90 italic leading-relaxed max-w-3xl pt-2">
          {w.thinking}
        </p>
      </div>
    </Reveal>
  );
}

/* Sticky stacking card wrapper for each work case */
function StickyWorkCard({ index, total, children, refSetter, id }) {
  const wrapRef = useRef(null);
  const { motion, useScroll, useTransform } = window.Motion || {};
  const Wrap = motion ? motion.div : "div";
  const isLast = index === total - 1;

  let scaleStyle = {};
  if (motion && !isLast) {
    const { scrollYProgress } = useScroll({
      target: wrapRef,
      offset: ["start start", "end start"]
    });
    // Stay full size while card is in view, shrink as next card pushes it out.
    const scale = useTransform(scrollYProgress, [0, 0.55, 1], [1, 1, 0.93]);
    const opacity = useTransform(scrollYProgress, [0, 0.55, 1], [1, 1, 0.55]);
    scaleStyle = { scale, opacity };
  }

  return (
    <div
      ref={(el) => { wrapRef.current = el; if (refSetter) refSetter(el); }}
      id={id}
      className="work-sticky-wrap scroll-mt-32"
      style={{ paddingBottom: isLast ? 0 : 80 }}
    >
      <Wrap
        className="sticky"
        style={{
          position: "sticky",
          top: `${100 + index * 16}px`,
          transformOrigin: "50% 0%",
          ...scaleStyle
        }}
      >
        <div
          className="rounded-[22px] md:rounded-[48px] border border-white/10 bg-black p-5 md:p-8 lg:p-10"
          style={{ boxShadow: "0 -30px 60px -30px rgba(0,0,0,.8), 0 30px 80px -30px rgba(0,0,0,.9)" }}
        >
          {children}
        </div>
      </Wrap>
    </div>
  );
}

/* Main Work Section */
function Work() {
  const [active, setActive] = useState(0);
  const refs = useRef([]);

  useEffect(() => {
    // Sticky cards stack at top:~100-116px. The "active" card is the last one
    // whose top edge has scrolled up into the sticky zone. A scroll probe is
    // far more reliable here than an IntersectionObserver center heuristic,
    // because the tall wrappers overlap in the viewport.
    const PROBE = 240;
    const onScroll = () => {
      let best = 0;
      refs.current.forEach((el, i) => {
        if (!el) return;
        if (el.getBoundingClientRect().top <= PROBE) best = i;
      });
      setActive(best);
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
    };
  }, []);

  const onJump = (i) => {
    const el = refs.current[i];
    if (!el) return;
    const top = el.getBoundingClientRect().top + window.scrollY - 116;
    window.scrollTo({ top, behavior: "smooth" });
  };

  return (
    <section id="work" className="relative bg-black">
      {/* section header */}
      <div className="max-w-7xl mx-auto px-6 md:px-12 pt-32 pb-4">
        <div className="font-mono text-[10px] text-white/45 tracking-[0.3em]">// 02 — WORK EXPERIENCE</div>
      </div>

      <div className="max-w-7xl mx-auto px-6 md:px-12 pb-32">
        <div className="flex gap-12">
          <WorkSidebar active={active} onJump={onJump} />

          <div className="flex-1 min-w-0">
            {WORK.map((w, i) => (
              <StickyWorkCard
                key={w.id}
                index={i}
                total={WORK.length}
                id={"work-" + w.id}
                refSetter={(el) => (refs.current[i] = el)}
              >
                {w.layout === "beforeafter" ? <LayoutBeforeAfter w={w} />
                  : w.layout === "stacked" ? <LayoutStacked w={w} />
                  : w.layout === "supply" ? <LayoutSupply w={w} />
                  : <LayoutThumbs w={w} />}
              </StickyWorkCard>
            ))}
          </div>
        </div>
      </div>

      <div className="hairline max-w-7xl mx-auto"></div>
    </section>
  );
}

Object.assign(window, { Work });
