/* ============================================================
   ROOY · 04b ROOY profile（彩蛋）
   ───────────────────────────────────────────────────────────
   需求（P1 / 支线）：让 ROOY 有"真实社交媒体人物"的存在感。从
   04 主对话头像进入。

   结构（参照 IG / Threads profile）：
     · Avatar + verified ✓ + 实时状态（"在缆车上看你滑"）
     · 3 stat 横排（一起滑过 / 看过 / 笔记）
     · Highlights rail（6 story-circle 玻璃化小封面）
     · Tabs：关于 / 教学笔记置顶 / 喜好 / 最近动态（默认动态）

   交互：头像滚动折叠动画 —— 默认 tab 选了"动态"因为内容超一屏能
   触发折叠 + nav pill 淡入；改默认会让交互演示不起来。
   ============================================================ */

function RooyProfileScreen({ onNav }) {
  const [scrollTop, setScrollTop] = React.useState(0);
  const [liked, setLiked] = React.useState(false);
  /* Default tab = 动态：内容超一屏才能演示 hero 折叠 + pill 淡入。
     改 default 会让交互演示失效。 */
  const [activeTab, setActiveTab] = React.useState('resort');
  const scrollRef = React.useRef(null);
  const rafRef = React.useRef(0);
  const snapTimerRef = React.useRef(null);

  /* Frame width — measured at runtime so hero collapse math
     (avatar centering, name center X) tracks the actual stage
     width. The stage now fills the viewport (no more 372 cap),
     so this can be 320 on SE up to 430 on Pro Max. Fallback 372
     covers the first paint before ResizeObserver fires; the
     useLayoutEffect below overwrites it before the browser
     commits the first frame. */
  const rootRef = React.useRef(null);
  const [FW, setFW] = React.useState(372);
  React.useLayoutEffect(() => {
    const el = rootRef.current;
    if (!el) return;
    const w0 = el.getBoundingClientRect().width;
    if (w0 > 0) setFW(w0);
    const ro = new ResizeObserver((entries) => {
      const w = entries[0].contentRect.width;
      if (w > 0) setFW(w);
    });
    ro.observe(el);
    return () => ro.disconnect();
  }, []);

  /* rAF-throttled scroll handler + snap-to-endpoint debounce.
     Snap 的作用是"别让用户长时间停在 hero 半折叠态"——半折叠的 avatar
     尺寸 + name 半位移视觉上不稳，停下来看会一直觉得画面"在动"。
     滚动停止 200ms 后，scrollTop ∈ (0, COLLAPSE) 则 smooth 滚到最近端点
     (< COLLAPSE/2 回 0，否则去 COLLAPSE)。fully collapsed / fully expanded
     时不动手。maxScroll < COLLAPSE 时不能去 COLLAPSE，只 snap 回 0.
     200ms（原 150）—— 100~180ms 区间用户慢速浏览的指尖停顿会误触发，
     200ms 几乎只在用户真正松手后才触发，不再"和手势打架"。 */
  const handleScroll = (e) => {
    const target = e.currentTarget;
    if (rafRef.current) return;
    rafRef.current = requestAnimationFrame(() => {
      setScrollTop(target.scrollTop);
      rafRef.current = 0;
    });
    if (snapTimerRef.current) clearTimeout(snapTimerRef.current);
    snapTimerRef.current = setTimeout(() => {
      const sc = scrollRef.current;
      if (!sc) return;
      const st = sc.scrollTop;
      if (st <= 0 || st >= COLLAPSE) return;
      const maxScroll = sc.scrollHeight - sc.clientHeight;
      const canReachCollapsed = maxScroll >= COLLAPSE;
      const dest = canReachCollapsed && st >= COLLAPSE / 2 ? COLLAPSE : 0;
      sc.scrollTo({ top: dest, behavior: 'smooth' });
    }, 200);
  };

  /* Tab switch — reset scroll so each tab starts at the top, hero expanded.
     Without this, switching from a long tab (notes) to short (about) leaves
     the hero stuck in collapsed state with empty space below. 同时 cancel
     待执行的 snap timer，否则 snap 会在 tab 切完 200ms 后误触发. */
  const onPickTab = (id) => {
    setActiveTab(id);
    if (snapTimerRef.current) clearTimeout(snapTimerRef.current);
    if (scrollRef.current) scrollRef.current.scrollTop = 0;
  };

  /* Hero collapses 208 → 84pt over 140px of scroll.
     v5：从 220 → 140，让 04b 短 tab（喜好/动态）也能在自然 maxScroll
     范围内完整折叠到 compact 状态。原 220 是为视觉舒展挑的——但实测
     4 个 tab 都没法在那个距离内完整收掉。140 让 morph 快 35%，但
     仍然是平滑曲线（easeInOutCubic + rAF 节流），不显仓促。 */
  const COLLAPSE = 140;

  /* Dynamic bottom spacer — 原生 iOS 解法 (Apple Music / Photos / IG
     profile)：当 tab 内容不足以让 scrollTop 达到 COLLAPSE 时，contentSize
     兜底, 用透明 spacer 把 scrollHeight 补到至少 `clientHeight + COLLAPSE`,
     hero 不会"弹回"。
     单帧 rAF 不够稳健 —— tab 切换后 layout 几帧才稳定 (font subset / svg
     reflow)，第一次测量可能拿到瞬时态。多次 setTimeout (0/40/120/240ms)
     覆盖整个 layout-stable 窗口，最终值锁定。Math.max(200, …) 兜底：哪怕
     一次都没测准，spacer 至少 200pt 也保证短 tab 能滚到 collapse. */
  const [spacerH, setSpacerH] = React.useState(280);
  React.useLayoutEffect(() => {
    const sc = scrollRef.current;
    if (!sc) return;
    const measure = () => {
      setSpacerH((cur) => {
        const containerH = sc.clientHeight;
        const contentH = sc.scrollHeight - cur;
        const need = Math.max(200, COLLAPSE + 48 - (contentH - containerH));
        return Math.abs(need - cur) > 0.5 ? need : cur;
      });
    };
    const ids = [0, 40, 120, 240].map((d) => setTimeout(measure, d));
    return () => ids.forEach(clearTimeout);
  }, [activeTab]);

  const tRaw = Math.min(1, scrollTop / COLLAPSE);
  const t = tRaw < 0.5
    ? 4 * tRaw * tRaw * tRaw
    : 1 - Math.pow(-2 * tRaw + 2, 3) / 2;
  const lerp = (a, b) => a + (b - a) * t;

  /* 私信 pill — clean two-state crossfade replacing the v2 9-property morph.
     Pill fades in only after hero is mostly collapsed (tRaw 0.6 → 0.92)
     so it lands beside the compact avatar, not mid-transition. The inline
     私信 button in the action row is a normal flex item — scrolls off
     naturally with the rest of the row. No shape interpolation, no
     intermediate "neither rect nor pill" state.
     用 tRaw（normalized）而非 scrollTop，让阈值不依赖 COLLAPSE 绝对值。 */
  const pillRaw = Math.max(0, Math.min(1, (tRaw - 0.6) / 0.32));
  const pillEase = 1 - Math.pow(1 - pillRaw, 3);   // easeOutCubic
  const pillOpacity = pillEase;
  const pillScale = 0.92 + 0.08 * pillEase;
  const pillTY = (1 - pillEase) * -6;

  /* v5：hero 整体上收。原 avTop 50pt + heroH 248 让 avatar 顶部距状态栏
     有 100+pt 空白，太空。avTop → 28（接近 compact 24），整组 name/tagline
     等同步上移 22pt；heroH 248 → 208 同步收 40pt，去掉 tagline 下面无用的
     34pt empty space。 */
  const heroH = lerp(208, 84);
  const avSize = lerp(92, 36);
  const avLeft = lerp((FW - 92) / 2, 14);
  const avTop = lerp(28, 24);
  const avLetter = lerp(40, 18);

  const nameSize = lerp(28, 17);
  const nameCenterX = lerp(FW / 2, 60);
  const nameAnchor  = lerp(50, 0);
  /* Compact name: top 32. 状态合并进 name 行后变单行 ~20pt 高（17pt h1
     lineHeight 1.15）；avatar center cy = 24+18 = 42；single-line center
     32+10 = 42，对齐 avatar 中心。
     Expanded：avatar bottom = 28+92=120，gap 16 → name top 136. */
  const nameTop = lerp(136, 32);

  /* Tagline — only thing left in the fading subtext block. Expanded 168
     紧贴 name bottom (~136+32=168)；compact position 不可见（subOpacity=0）。 */
  const subOpacity = Math.max(0, 1 - tRaw * 2.2);
  const subTop = lerp(168, 130);

  /* Status "在缆车上" — 04a 风格：expanded 完全不渲染，折叠到 ~45% 后才
     fade-in 出现在 ROOY 名右侧，inline `· 在缆车上`（和 rooy.jsx 04a 里
     `· 教学中` 完全同结构 / 同 token）。
     去掉了旧的顶部 chip（位置 / 背景 / 缩放变量都不再需要）。 */
  const statusInlineOpacity = Math.min(1, Math.max(0, (tRaw - 0.45) / 0.45));

  const coverOpacity = Math.max(0, 1 - tRaw * 1.3);

  return (
    <div ref={rootRef} style={{
      height: '100%', display: 'flex', flexDirection: 'column',
      background: R.bg, color: R.ink, ...T.subhead,
      position: 'relative', overflow: 'hidden'
    }}>
      <StatusBar />

      {/* 私信 pill — top-right, only visible after hero collapses.
          Crossfades with the inline 私信 button in the action row (which
          scrolls off naturally). Position is fixed to where the compact
          avatar lands at top:78, h:36 → vertical center 96, pill h:30 → top 81.
          No path morphing, no shape interpolation. */}
      <button onClick={() => onNav && onNav('back')} aria-label="私信" style={{
        position: 'absolute', zIndex: 30,
        /* top = StatusBar height + 27px (居中 36pt avatar): 设计稿 54+27=81;
           PWA 下 StatusBar 退化成 safe-area spacer, 用 calc 跟随变化. */
        top: (typeof window !== 'undefined' && window.__ROOY_HIDE_MOCK_STATUSBAR)
          ? 'calc(env(safe-area-inset-top, 0px) + 27px)'
          : 81,
        right: 14,
        height: 30, padding: '0 14px',
        borderRadius: 999,
        background: R.ink, color: '#fff',
        border: 'none', cursor: 'pointer',
        ...T.footnote, fontWeight: 600,
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 5,
        boxShadow: Shadow.lift,
        opacity: pillOpacity,
        transform: `translateY(${pillTY}px) scale(${pillScale})`,
        pointerEvents: pillOpacity > 0.4 ? 'auto' : 'none',
        willChange: 'opacity, transform'
      }}>
        <ChatGlyph size={13} /> 私信
      </button>

      {/* Scroll container */}
      <div
        ref={scrollRef}
        onScroll={handleScroll}
        style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden', position: 'relative' }}>

        {/* ── Hero — sticky-collapsing header ────────────── */}
        <div style={{
          height: heroH,
          position: 'sticky', top: 0, zIndex: 10,
          background: R.bg, flexShrink: 0
        }}>
          {/* Topographic banner — subtle ski-terrain vibe */}
          <div style={{
            position: 'absolute', inset: 0,
            opacity: coverOpacity * 0.55, pointerEvents: 'none'
          }}>
            <TopoBG opacity={0.08} color={R.rooy} dense />
          </div>
          {/* ROOY 色 radial glow — 弥散感衰减到 0.65，避开 PRODUCT.md
              anti-ref "冥想 app 弥散渐变 / fintech 渐变 hero 卡"。
              改用 12%（原 18%）+ 64% radius（原 62%）让 glow 更收敛、
              tone 更"晨光"，主视觉 weight 留给 avatar 本身。 */}
          <div style={{
            position: 'absolute', inset: 0,
            background: `radial-gradient(72% 78% at 50% 4%, color-mix(in srgb, ${R.rooy} 12%, transparent) 0%, transparent 64%)`,
            opacity: coverOpacity * 0.65, pointerEvents: 'none'
          }} />
          {/* Bottom hairline — appears in compact state */}
          <div style={{
            position: 'absolute', left: 0, right: 0, bottom: 0,
            height: 0.5, background: R.hair, opacity: t
          }} />

          {/* Avatar — interpolated.
              boxShadow 持续衰减（不再在 t=0.6 跳成 'none'）：原 toggle 让
              drop shadow 在中段突然消失 + 1.6px R.bg 环瞬间脱落，看起来
              像"闪一下"。0.10→0 用 1-t 平滑衰减，halo ring 沿 lerp(4,0)
              同步收到 0，整段折叠过程 0 discrete change. 权重对齐 Shadow.hero
              量级（6/20）——原 14/32 /0.22 撞 Linear/shadcn anti-pattern，
              近白 iOS 调性下 avatar 的分量靠尺寸承载、不靠重 drop. */}
          <div style={{
            position: 'absolute',
            top: avTop, left: avLeft,
            width: avSize, height: avSize, borderRadius: '50%',
            background: R.ink, color: '#fff',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            boxShadow: `0 6px 20px oklch(17% 0.005 250 / ${0.10 * (1 - t)}), 0 0 0 ${lerp(4, 0)}px ${R.bg}`
          }}>
            <span style={{
              fontFamily: '"SF Pro Display", system-ui',
              fontSize: avLetter, fontWeight: 700, color: '#fff',
              lineHeight: 1, display: 'inline-block', textAlign: 'center',
              /* Optical centering — SF Pro R's leg shifts visual mass right,
                 so the geometric center reads slightly left. Nudge right by
                 a fraction of the cap-height to compensate. */
              transform: `translateX(${avLetter * 0.04}px)`
            }}>R</span>
          </div>

          {/* Name + verified + (compact-only) inline status. translateX 让
              name 从居中 → 紧贴 avatar，无 textAlign 跳变。
              Status `· 在缆车上` 用 position:absolute, left:100% 挂在 flex
              container 右侧外，**不参与 flex 宽度**——避免它在 tRaw=0.45
              进/出 DOM 时让 flex width 跳变、再叠加 translateX(-N%) 锚点
              位移，造成 ROOY + ✓ 整体瞬时水平跳一下（用户看到的"闪"）。
              opacity fade 仍由 statusInlineOpacity 控制. */}
          <div style={{
            position: 'absolute',
            top: nameTop,
            left: nameCenterX,
            transform: `translateX(-${nameAnchor}%)`,
            display: 'flex', alignItems: 'center', gap: 6,
            whiteSpace: 'nowrap'
          }}>
            <h1 style={{
              fontFamily: '"PingFang SC", -apple-system, "SF Pro Display", system-ui',
              fontSize: nameSize, fontWeight: 700,
              letterSpacing: '-0.02em',
              color: R.ink, lineHeight: 1.15
            }}>ROOY</h1>
            <VerifiedBadge size={lerp(20, 15)} />
            <span style={{
              ...T.eyebrow, color: R.rooy,
              position: 'absolute',
              left: '100%',
              top: '50%',
              transform: 'translateY(-50%)',
              marginLeft: 8,
              opacity: statusInlineOpacity,
              pointerEvents: 'none',
              whiteSpace: 'nowrap',
            }}>· 在缆车上</span>
          </div>

          {/* Subtext — tagline only, centered always. Fades out by mid-scroll. */}
          <div style={{
            position: 'absolute',
            top: subTop,
            left: 0, right: 0,
            textAlign: 'center',
            opacity: subOpacity,
            pointerEvents: subOpacity < 0.5 ? 'none' : 'auto'
          }}>
            <p style={{ ...T.body, color: R.ink2 }}>你的滑雪搭子</p>
          </div>

        </div>

        {/* ── Action row — 私信 + 喜欢. 紧接 hero 下方（stats 已移到数据
             tab）。和下方 tabs 之间靠 HighlightsRail 的 marginTop 拉开。
             行内 私信 自然滚出屏；顶部 pill (上面 absolute) 在 hero
             折叠完成后淡入接替。 ──── */}
        <div style={{
          padding: '12px 16px 0',
          display: 'flex', gap: 8,
        }}>
          <button onClick={() => onNav && onNav('back')} style={{
            flex: 1,
            background: R.ink, color: '#fff',
            border: 'none', cursor: 'pointer',
            padding: '11px 0', borderRadius: 12,
            ...T.headline,
            display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
          }}>
            <ChatGlyph size={16} /> 私信
          </button>
          <button onClick={() => setLiked((v) => !v)} aria-pressed={liked} style={{
            flex: 1,
            background: liked ? R.rooy : R.card,
            color: liked ? '#fff' : R.ink,
            /* border 始终在 — 颜色变 transparent 保 box-size 守恒，
               防止 like 切换时 1pt 跳动。 */
            border: `0.5px solid ${liked ? 'transparent' : R.hair2}`,
            cursor: 'pointer',
            padding: '11px 0', borderRadius: 12,
            ...T.headline,
            display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
            transition: 'background .15s, color .15s, border-color .15s'
          }}>
            {/* 固定宽容器 — 锁 icon + text 整体宽度按 "已喜欢" 3 字
                算（46pt 文字 + 16pt icon + 6pt gap = 68，留 4pt 余裕→72）；
                whiteSpace: nowrap 防 3 字溢出换行。flex 居中后 icon
                位置常驻不漂移，"喜欢" 2 字时右侧自然留 15pt 空白。 */}
            <span style={{
              display: 'inline-flex', alignItems: 'center', gap: 6,
              width: 72, justifyContent: 'flex-start',
              whiteSpace: 'nowrap',
            }}>
              <HeartGlyph size={16} filled={liked} />
              <span>{liked ? '已喜欢' : '喜欢'}</span>
            </span>
          </button>
        </div>

        {/* ── Highlights rail — IG-style story circles 作为 tab 切换。
             active 圈完整 ROOY ring + 文字 ink；inactive 降级 hairline 圈
             + 文字 ink3。只显示 active tab 的 section，整屏缩短。 ──── */}
        <HighlightsRail active={activeTab} onPick={onPickTab} />

        {/* ── 当前 tab 的 section ──────────────────────── */}
        {activeTab === 'resort' && (
          <ProfileSection title="动态">
            {RESORT_POSTS.map((p, i) => <ResortPost key={i} {...p} />)}
          </ProfileSection>
        )}

        {activeTab === 'likes' && (
          <ProfileSection title="喜好">
            {QUIRKS.map((q, i) => <QuirkCard key={i} {...q} />)}
          </ProfileSection>
        )}

        {activeTab === 'notes' && (
          <ProfileSection title="教学笔记">
            {JOURNAL_NOTES.map((n, i) => (
              <JournalEntry key={i} {...n}/>
            ))}
          </ProfileSection>
        )}

        {activeTab === 'data' && (
          <ProfileSection title="数据">
            {/* Stats card — 6 stat, 双行 3 列. 上行陪伴维度（一起 / 在线
                / 笔记），下行教学维度（反馈 / 节课 / 雪场）。中间一条 hair
                横向分组, divider prop 在每行的后两列加 0.5px 竖 hairline. */}
            <div style={{
              background: R.card, borderRadius: B.cardL,
              padding: '20px 0',
              boxShadow: Shadow.cardSoft,
            }}>
              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)' }}>
                <ProfileStat n="24" u="天" l="一起滑过" />
                <ProfileStat n="284" u="H" l="在线" divider />
                <ProfileStat n="17" l="笔记" divider />
              </div>
              <div style={{
                height: 0.5, background: R.hair,
                margin: '20px 20px',
              }} />
              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)' }}>
                <ProfileStat n="132" l="反馈" />
                <ProfileStat n="9" u="节" l="课程" divider />
                <ProfileStat n="5" l="雪场" divider />
              </div>
            </div>
          </ProfileSection>
        )}

        <div style={{ height: spacerH }} />
      </div>
    </div>);

}

/* ── ProfileStat — IG-style number-over-label cell.
   v4：stats 现在只在数据 tab 出现，作为 tab 的 hero block，所以默认值
   回到 26pt / weight 800（最初版本就是这个）。`big` prop 保留为 future
   hook—如果以后再有"次级 stats"位置可以 fontSize 22。 */
function ProfileStat({ n, u, l, divider }) {
  return (
    <div style={{
      padding: '0 8px', textAlign: 'center',
      borderLeft: divider ? `0.5px solid ${R.hair}` : 'none'
    }}>
      <div style={{ display: 'inline-flex', alignItems: 'baseline', gap: 2 }}>
        <span style={{
          ...N.hero, fontSize: 26, color: R.ink, lineHeight: 1
        }}>{n}</span>
        {u && <span style={{ ...T.caption1, color: R.ink3, fontWeight: 500 }}>{u}</span>}
      </div>
      <p style={{ ...T.caption1, color: R.ink3, marginTop: 7 }}>{l}</p>
    </div>);

}

/* ── HighlightsRail — IG Stories highlight pattern 作为 tab 切换 ───
   4 highlights ↔ 4 tabs. active 圈完整 ROOY conic ring；inactive
   降级为 hairline 圈 + glyph 半透明 + label ink3。
   颜色收敛：硬编码 hex 全部 token 化，4 个 bg 收敛到
   ink / ROOY tint / systemFill 三档（关于=ink、动态=ROOY、其余=灰）。 */
/* tab 排列：动态首位（最日常 + 内容最长 = 默认）/ 喜好 / 教学笔
   记 / 数据。4 个 disc 颜色统一到 systemFill 灰 —— active 状态完全
   靠 ROOY conic ring 区分，不让任何 disc 在 inactive 时抢眼。 */
const HIGHLIGHTS = [
  { l: '动态',     target: 'resort', bg: 'color-mix(in srgb, var(--r-rooy) 14%, #fff)', fg: 'var(--r-rooy)', glyph: <HLMountain /> },
  { l: '喜好',     target: 'likes',  bg: R.systemFill,  fg: R.ink2,          glyph: <HLHeart /> },
  { l: '教学笔记', target: 'notes',  bg: R.systemFill,  fg: R.ink2,          glyph: <HLPencil /> },
  { l: '数据',     target: 'data',   bg: R.systemFill,  fg: R.ink2,          glyph: <HLChart /> },
];


function HighlightsRail({ active, onPick }) {
  return (
    /* marginTop 32（原 26）— Stats 改扁平后，hero unit（avatar / name / status
       / stats / action row）整体上挪、上下都更紧凑；tab rail 需要额外 6pt
       breathing 拉开"人物信息" → "浏览选项"的章节断层。 */
    <div style={{ marginTop: 32 }}>
      <div style={{
        padding: '0 16px',
        display: 'flex', gap: 22, justifyContent: 'center'
      }}>
        {HIGHLIGHTS.map((it, i) => {
          const isActive = active === it.target;
          return (
            <button key={i} onClick={() => onPick && onPick(it.target)} aria-pressed={isActive} style={{
              flexShrink: 0,
              background: 'transparent', border: 'none', cursor: 'pointer', padding: 0,
              display: 'flex', flexDirection: 'column',
              alignItems: 'center', gap: 8,
              width: 64
            }}>
              <div style={{
                width: 64, height: 64, borderRadius: '50%',
                padding: isActive ? 2 : 0.5,
                background: isActive
                  ? `conic-gradient(from 200deg, var(--r-rooy), color-mix(in srgb, var(--r-rooy) 50%, #fff), var(--r-rooy))`
                  : R.hair2,
                transition: 'padding .18s ease',
              }}>
                <div style={{
                  width: '100%', height: '100%', borderRadius: '50%',
                  background: R.bg, padding: isActive ? 2.5 : 2,
                }}>
                  <div style={{
                    width: '100%', height: '100%', borderRadius: '50%',
                    background: it.bg, color: it.fg,
                    opacity: isActive ? 1 : 0.6,
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    overflow: 'hidden',
                    transition: 'opacity .18s ease',
                  }}>{it.glyph}</div>
                </div>
              </div>
              <span style={{
                ...T.caption1,
                color: isActive ? R.ink : R.ink3,
                fontWeight: isActive ? 600 : 400,
                textAlign: 'center',
                maxWidth: 64, overflow: 'hidden',
                textOverflow: 'ellipsis', whiteSpace: 'nowrap',
              }}>{it.l}</span>
            </button>
          );
        })}
      </div>
    </div>);

}

/* ── ProfileSection — layout wrapper only.
   v3：去掉 eyebrow title 渲染——tab label 已经声明了当前 section 名
   （"关于" / "动态" / "喜好" / "教学笔记"），再在 section 顶部 echo 一次
   eyebrow 是 redundant。title prop 保留作为可选 a11y 标记不影响视觉。 */
function ProfileSection({ title, children }) {
  return (
    <div
      role="region" aria-label={title}
      style={{ marginTop: 18, padding: '0 16px' }}>
      {children}
    </div>);

}

/* ── JournalEntry — 教学笔记日记条目 ──────────────────────
   每条是独立的卡（白卡 + B.cardL 圆角 + 阴影），不再 hairline 叠
   在一起，视觉像翻日记本一页页。卡内：
     · 顶部日期 header (eyebrow 13 ink3, 可选 pin 图标 + 周几)
     · 主 body T.para ink2
     · 可选 italic tail 右下（"（你睡了）"）
   不再有 borderLeft accent —— 保持纸感。 */
const JOURNAL_NOTES = [
  {
    date: '5 月 3 日',
    weekday: '周五',
    time: '凌晨 4 点',
    pinned: true,
    body: (<>今早 Furi 蓝道，你右弯入弯还在犹豫 0.15s。我没说，因为你状态在调，自己会感觉到。<br /><br />下午雪面化得快，注意立刃别太狠。</>),
    tail: '（你睡了）'
  },
  {
    date: '5 月 2 日',
    weekday: '周四',
    time: '晚 22:14',
    body: (<>今天三趟稳定 40°，但只是第一组的事。第二组下来左右弯开始打回原形。<br /><br />稳定不是靠"做"，是靠"不忘"。明天我们试试节奏拍子。</>)
  },
  {
    date: '4 月 30 日',
    weekday: '周一',
    time: '晚 21:08',
    body: '雪面偏硬，立刃普遍保守了 5°——这是对的。硬雪上太激进容易打滑，你今天判断比上周好。'
  }
];

function JournalEntry({ date, weekday, time, body, tail, pinned }) {
  return (
    <div style={{
      background: R.card, borderRadius: B.cardL,
      padding: '16px 20px',
      marginBottom: 10,
      boxShadow: Shadow.cardSoft
    }}>
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        marginBottom: 12, gap: 8,
      }}>
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 6, flex: 1, minWidth: 0 }}>
          <span style={{
            ...T.subhead, color: R.ink, fontWeight: 600,
          }}>{date}</span>
          {weekday && (
            <span style={{ ...T.caption1, color: R.ink3 }}>{weekday}</span>
          )}
          {time && (
            <span style={{ ...T.caption1, color: R.ink4 }}>· {time}</span>
          )}
        </div>
        {pinned && (
          <span style={{
            color: R.ink3, flexShrink: 0,
            display: 'inline-flex', alignItems: 'center', gap: 4,
          }}>
            <PinGlyph size={10}/>
            <span style={{ ...T.caption1, color: R.ink3 }}>置顶</span>
          </span>
        )}
      </div>
      <p style={{ ...T.para, color: R.ink2 }}>{body}</p>
      {tail && (
        <p style={{
          ...T.caption1, color: R.ink4, marginTop: 12,
          fontStyle: 'italic', textAlign: 'right'
        }}>{tail}</p>
      )}
    </div>
  );
}

/* ── ResortPost — 一个雪场对应一条 ROOY 帖子，带定位 tag ─────
   把 ROOY 在每个雪场的"故事 / 心情"按 social feed 形式展示，
   而不是干巴巴的列表。每条都是 ROOY-voice 的小段子。 */
const RESORT_POSTS = [
  {
    resort: 'Zermatt',
    when: '2 月 14 日 · 主场',
    body: '今天和你一起到了 Stockhorn 顶。3 532 m。第一脚踩下去那种感觉，每个人都该体验一次。',
    star: true,
  },
  {
    resort: 'Verbier',
    when: '1 月 8 日',
    body: '4 Vallées 雪域。下午起风但雪面给力，跟着 Sergio 学的硬粉日立刃，今天才真正用上。',
  },
  {
    resort: 'Saas-Fee',
    when: '7 月 22 日',
    body: '夏天的冰川。9 月还能开板 —— 瑞士给我的礼物。你想夏天滑吗？',
  },
  {
    resort: 'Furi',
    when: '今天 · 训练点',
    body: 'Furi 蓝道，你刚开始的地方。每次回来看你滑，节奏都不太一样。',
  },
];

function ResortPost({ resort, when, body, star }) {
  return (
    <div style={{
      background: R.card, borderRadius: B.cardL,
      padding: '16px 20px',
      marginBottom: 8,
      boxShadow: Shadow.cardSoft,
    }}>
      <div style={{
        display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10
      }}>
        <div style={{
          width: 32, height: 32, borderRadius: '50%',
          background: R.ink, color: '#fff', position: 'relative',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          flexShrink: 0
        }}>
          <span style={{ ...N.hero, fontSize: 13, color: '#fff', lineHeight: 1 }}>R</span>
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 4, flexWrap: 'wrap' }}>
            <span style={{ ...T.subhead, color: R.ink, fontWeight: 600 }}>ROOY</span>
            <VerifiedBadge size={11}/>
            <span style={{ ...T.caption1, color: R.ink3, marginLeft: 2 }}>在</span>
            <span style={{
              ...T.caption1, color: R.rooy, fontWeight: 500,
              display: 'inline-flex', alignItems: 'center', gap: 3
            }}>
              <PinLocationGlyph size={10}/>{resort}
            </span>
            {star && (
              <span style={{
                ...T.caption1, color: R.gold,
                display: 'inline-flex', alignItems: 'center', gap: 3, marginLeft: 2
              }}>
                <StarGlyph size={10}/>
              </span>
            )}
          </div>
          <p style={{ ...T.caption1, color: R.ink3, marginTop: 2 }}>{when}</p>
        </div>
      </div>
      <p style={{ ...T.para, color: R.ink2 }}>{body}</p>
    </div>
  );
}

/* ── QuirkCard — 喜好 row as a card with tinted accent disc ── */
/* QUIRKS 颜色收敛：5 disc 从 5 色 → 2 色二态。
   规则：和 ROOY 心情 / 看法直接相关的（早班、硬粉数据）= ROOY tint；
   中性观察 / 喜好声明（最爱山、缆车不说话、只教双板）= systemFill。
   硬编码 hex (#B07A1F #EAECEF #F1ECE5 #7E6346) 全删；gold / data 外来
   accent 也清掉。 */
const QUIRK_ROOY = { bg: 'color-mix(in srgb, var(--r-rooy) 14%, #fff)', fg: 'var(--r-rooy)' };
const QUIRK_GRAY = { bg: R.systemFill, fg: R.ink2 };
const QUIRKS = [
{
  glyph: <SunriseGlyph />,
  accent: QUIRK_ROOY.bg, accentFg: QUIRK_ROOY.fg,
  l: '早班的雪面最纯',
  d: '6 点开板那种。看你第一趟没人压过的雪，很有满足感。'
},
{
  glyph: <PeakGlyph />,
  accent: QUIRK_GRAY.bg, accentFg: QUIRK_GRAY.fg,
  l: '最爱 Stockhorn 顶',
  d: 'Zermatt 之外都还好。Stockhorn 顶第一脚踩下去那感觉无可替代。'
},
{
  glyph: <LiftGlyph />,
  accent: QUIRK_GRAY.bg, accentFg: QUIRK_GRAY.fg,
  l: '缆车上我不说话',
  d: '上山是给你休息的，不是给我教学的。除非你先开口。'
},
{
  glyph: <SnowGlyph />,
  accent: QUIRK_ROOY.bg, accentFg: QUIRK_ROOY.fg,
  l: '蓝天硬粉 > 暴风雪',
  d: '我知道粉雪日大家都嗨。但我看数据，硬粉里你滑得最稳。'
},
{
  glyph: <BoardGlyph />,
  accent: QUIRK_GRAY.bg, accentFg: QUIRK_GRAY.fg,
  l: '只教双板',
  d: '单板我也看得懂，但教不好。诚实点说。'
}];


function QuirkCard({ glyph, accent, accentFg, l, d }) {
  return (
    <div style={{
      background: R.card, borderRadius: B.cardL,
      padding: '16px 20px',
      marginBottom: 8,
      display: 'flex', gap: 14, alignItems: 'flex-start',
      boxShadow: Shadow.cardSoft,
    }}>
      <div style={{
        width: 40, height: 40, borderRadius: '50%',
        background: accent || R.systemFill,
        color: accentFg || R.ink2,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        flexShrink: 0
      }}>{glyph}</div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <p style={{ ...T.headline, color: R.ink }}>{l}</p>
        <p style={{ ...T.footnote, color: R.ink3, marginTop: 4 }}>{d}</p>
      </div>
    </div>);

}

/* ── Glyphs ───────────────────────────────────────────────── */

/* Verified — burst-star silhouette, ROOY-color fill, white check.
   Smaller than IG/X badge but unmistakable as the same pattern. */
function VerifiedBadge({ size = 16 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none"
    style={{ flexShrink: 0 }}>
      <path d="M12 1.6 L14.2 3.4 L17 3 L18.6 5.3 L21.2 6.4 L21 9.2 L22.6 11.6 L21 14 L21.2 16.8 L18.6 17.9 L17 20.2 L14.2 19.8 L12 21.6 L9.8 19.8 L7 20.2 L5.4 17.9 L2.8 16.8 L3 14 L1.4 11.6 L3 9.2 L2.8 6.4 L5.4 5.3 L7 3 L9.8 3.4 Z"
      fill="var(--r-rooy)" />
      <path d="M7.6 12.2 L10.4 14.8 L16.4 8.6"
      stroke="#fff" strokeWidth="2.2" fill="none"
      strokeLinecap="round" strokeLinejoin="round" />
    </svg>);

}

function ChatGlyph({ size = 16 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 20 20" fill="none">
      <path d="M3 5 a2 2 0 0 1 2 -2 H15 a2 2 0 0 1 2 2 V12 a2 2 0 0 1 -2 2 H8 l-3.5 3 v-3 a2 2 0 0 1 -1.5 -2 z"
      fill="currentColor" stroke="none" />
    </svg>);

}

function BellGlyph({ size = 18 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 20 20" fill="none">
      <path d="M5 14.5 H15 L13.5 12.5 V8.4 a3.5 3.5 0 1 0 -7 0 V12.5 L5 14.5 z"
      stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round" />
      <path d="M8.4 16.4 a1.6 1.6 0 0 0 3.2 0"
      stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinecap="round" />
    </svg>);

}

function HeartGlyph({ size = 16, filled }) {
  return (
    <svg width={size} height={size} viewBox="0 0 20 20" fill="none">
      <path d="M10 17 C 2.5 12 2.5 5.5 6.5 5.5 c 1.6 0 2.7 0.9 3.5 2 c 0.8 -1.1 1.9 -2 3.5 -2 c 4 0 4 6.5 -3.5 11.5 z"
        fill={filled ? 'currentColor' : 'none'}
        stroke="currentColor" strokeWidth={filled ? 0 : 1.7}
        strokeLinejoin="round"/>
    </svg>
  );
}

function StarGlyph({ size = 12 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 12 12" fill="currentColor">
      <path d="M6 0.8 L7.6 4.2 L11.3 4.7 L8.6 7.3 L9.3 11 L6 9.2 L2.7 11 L3.4 7.3 L0.7 4.7 L4.4 4.2 Z"/>
    </svg>
  );
}

function PinLocationGlyph({ size = 10 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 10 12" fill="none">
      <path d="M5 0.8 C 2.5 0.8 1 2.7 1 4.6 c 0 2.9 4 6.5 4 6.5 s 4 -3.6 4 -6.5 c 0 -1.9 -1.5 -3.8 -4 -3.8 z"
        fill="currentColor"/>
      <circle cx="5" cy="4.5" r="1.4" fill="#fff"/>
    </svg>
  );
}

function PinGlyph({ size = 11 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 14 14" fill="currentColor">
      <path d="M8.5 0.8 a0.5 0.5 0 0 1 0.7 0 l4 4 a0.5 0.5 0 0 1 0 0.7 l-1.3 1.3 -3 0.6 -1.4 3 0.6 0.6 a0.5 0.5 0 1 1 -0.7 0.7 l-2 -2 -3.4 1.5 1.5 -3.4 -2 -2 a0.5 0.5 0 1 1 0.7 -0.7 l0.6 0.6 3 -1.4 0.6 -3 z" />
    </svg>);

}

/* Quirk row glyphs (16×16) */
function SunriseGlyph() {
  return (
    <svg width="18" height="18" viewBox="0 0 20 20" fill="none">
      <circle cx="10" cy="13" r="3.4" stroke="currentColor" strokeWidth="1.5" />
      <path d="M2 17h16M5 13l1.4-1.4M10 6.5V4M14.6 11.6L16 13" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
    </svg>);

}
function LiftGlyph() {
  return (
    <svg width="18" height="18" viewBox="0 0 20 20" fill="none">
      <path d="M2 4l16 4M3 11h7v5H3z" stroke="currentColor" strokeWidth="1.5" fill="none" strokeLinejoin="round" />
      <path d="M6.5 11V8" stroke="currentColor" strokeWidth="1.5" />
    </svg>);

}
function SnowGlyph() {
  return (
    <svg width="18" height="18" viewBox="0 0 20 20" fill="none">
      <path d="M10 2v16M2.5 6l15 8M2.5 14l15-8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
    </svg>);

}
function BoardGlyph() {
  return (
    <svg width="18" height="18" viewBox="0 0 20 20" fill="none">
      <rect x="3" y="9" width="14" height="2.5" rx="1.2" stroke="currentColor" strokeWidth="1.5" fill="none"
      transform="rotate(-12 10 10)" />
    </svg>);

}
function PeakGlyph() {
  return (
    <svg width="18" height="18" viewBox="0 0 20 20" fill="none">
      <path d="M2 16 L7.5 6 L11 11 L14 5 L18 16 Z"
      stroke="currentColor" strokeWidth="1.5" fill="none" strokeLinejoin="round" />
    </svg>);

}

/* Highlights story-circle glyphs — bigger, more graphic. */
function HLLetterR() {
  return (
    <span style={{
      fontFamily: '"SF Pro Display", system-ui',
      fontSize: 30, fontWeight: 900, color: '#fff',
      letterSpacing: '-0.02em', lineHeight: 1
    }}>R</span>);

}
/* HLBio — 3 短横线 representing bio/about text. 替代之前的 HLLetterR，
   避免 active 关于 tab 的"黑 R 圈"和 hero avatar 的"黑 R 圈"视觉重复。
   3 行的最后一行短一段（像段落 ragged-right）让它读起来像"文本块"。 */
function HLBio() {
  return (
    <svg width="30" height="30" viewBox="0 0 32 32" fill="none">
      <rect x="6" y="9"  width="20" height="2.6" rx="1.3" fill="currentColor" />
      <rect x="6" y="14.5" width="20" height="2.6" rx="1.3" fill="currentColor" />
      <rect x="6" y="20" width="12" height="2.6" rx="1.3" fill="currentColor" />
    </svg>);

}
function HLMountain() {
  return (
    <svg width="34" height="34" viewBox="0 0 36 36" fill="none">
      <path d="M3 28 L11 13 L17 22 L23 9 L33 28 Z"
      fill="currentColor" />
      <circle cx="23" cy="7" r="1.6" fill="currentColor" />
    </svg>);

}
function HLBook() {
  return (
    <svg width="30" height="30" viewBox="0 0 32 32" fill="none">
      <path d="M5 7 L5 25 L15.5 22 L26 25 L26 7 L15.5 10 Z"
      stroke="currentColor" strokeWidth="2" fill="none" strokeLinejoin="round" />
      <path d="M15.5 10 L15.5 22"
      stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
    </svg>);

}
function HLHeart() {
  return (
    <svg width="28" height="28" viewBox="0 0 32 32" fill="none">
      <path d="M16 27 C 5 19 5 9 11 9 c 2.2 0 4 1.3 5 3 c 1 -1.7 2.8 -3 5 -3 c 6 0 6 10 -5 18 z"
      fill="currentColor" />
    </svg>);

}
function HLPencil() {
  return (
    <svg width="28" height="28" viewBox="0 0 32 32" fill="none">
      <path d="M22 4 l6 6 -17 17 -7 1 1 -7 z M19 7 l6 6"
      stroke="currentColor" strokeWidth="2" fill="none"
      strokeLinejoin="round" strokeLinecap="round" />
    </svg>);

}
function HLChart() {
  return (
    <svg width="32" height="32" viewBox="0 0 32 32" fill="none">
      <path d="M5 26 L5 18 M12 26 L12 13 M19 26 L19 8 M26 26 L26 16"
      stroke="currentColor" strokeWidth="2.6" fill="none" strokeLinecap="round" />
    </svg>);

}

Object.assign(window, {
  RooyProfileScreen, ProfileStat, ProfileSection,
  HighlightsRail, JournalEntry, QuirkCard,
  ResortPost,
  VerifiedBadge, ChatGlyph, BellGlyph, HeartGlyph, StarGlyph, PinGlyph, PinLocationGlyph,
  SunriseGlyph, LiftGlyph, SnowGlyph, BoardGlyph, PeakGlyph,
  HLLetterR, HLBio, HLMountain, HLBook, HLHeart, HLPencil, HLChart
});