// lvpai-core.jsx — 旅派 design system: tokens, icons, photo placeholders, primitives, WeChat chrome
// Exported to window for use by screen files.

// ── Design tokens ──────────────────────────────────────────────
const T = {
  paper: '#F4F2EC', // warm paper background
  card: '#FFFFFF',
  ink: '#1A1916', // near-black warm
  inkSoft: '#46443D',
  muted: '#6E6A5E', // secondary text (4.5:1+ on paper)
  faint: '#918C7E', // tertiary
  line: '#E7E2D8', // hairline
  lineSoft: '#F0EBE0',
  field: '#FAF8F2',
  serif: 'Georgia, "Times New Roman", "Songti SC", serif',
  sans: '"PingFang SC", "Noto Sans SC", "Hiragino Sans GB", -apple-system, system-ui, sans-serif'
};
// accent is injected at runtime via window.__LVACCENT (a Tweak). Default = ink.
const accent = () => window.__LVACCENT || T.ink;

// ── Photo tone gradients (refined, layered to feel photographic) ──
const TONES = {
  lake: 'radial-gradient(115% 75% at 28% 16%, rgba(255,255,255,.5), transparent 52%), linear-gradient(166deg,#e9f3ef 0%,#bfe0d6 28%,#79bcad 54%,#3d8e80 78%,#1f5a50 100%)',
  gold: 'radial-gradient(115% 75% at 30% 14%, rgba(255,255,255,.55), transparent 50%), linear-gradient(166deg,#f8efda 0%,#efd49a 30%,#e0a857 58%,#b9763a 82%,#774626 100%)',
  snow: 'radial-gradient(115% 75% at 26% 14%, rgba(255,255,255,.6), transparent 54%), linear-gradient(166deg,#eef3f7 0%,#cfdde9 30%,#9db8d0 58%,#647f9f 84%,#3a5169 100%)',
  dusk: 'radial-gradient(115% 75% at 30% 16%, rgba(255,255,255,.4), transparent 50%), linear-gradient(166deg,#ece6ef 0%,#bdb4d2 28%,#8d84b4 54%,#5b5485 80%,#2f2a4c 100%)',
  stone: 'radial-gradient(115% 75% at 28% 16%, rgba(255,255,255,.45), transparent 52%), linear-gradient(166deg,#efece4 0%,#dbd5c7 38%,#b6ad9a 70%,#827a66 100%)',
  pine: 'radial-gradient(115% 75% at 30% 16%, rgba(255,255,255,.35), transparent 50%), linear-gradient(166deg,#dde7e2 0%,#9cc0b5 32%,#5a8c7e 60%,#2f5d52 84%,#16332d 100%)'
};

// ── Icon set (line, 24×24, stroke currentColor) ──────────────────
const PATHS = {
  home: 'M3.2 11.4 12 4l8.8 7.4M5.4 9.6V20h13.2V9.6',
  image: 'M4 5.5h16v13H4zM4 16l4.5-4.5L12 15l3-3 5 5M8.5 10a1.4 1.4 0 1 0 0-2.8 1.4 1.4 0 0 0 0 2.8z',
  scan: 'M4 8.5V6a2 2 0 0 1 2-2h2.5M15.5 4H18a2 2 0 0 1 2 2v2.5M20 15.5V18a2 2 0 0 1-2 2h-2.5M8.5 20H6a2 2 0 0 1-2-2v-2.5M4 12h16',
  receipt: 'M6 3.2h12v17.6l-2.7-1.8-2.7 1.8-2.6-1.8L7 20.8 6 20.2zM9.2 8.2h5.6M9.2 12h5.6M9.2 15.6h3.4',
  user: 'M12 11.6a3.7 3.7 0 1 0 0-7.4 3.7 3.7 0 0 0 0 7.4zM4.6 20c.6-3.6 3.6-5.4 7.4-5.4S18.8 16.4 19.4 20',
  bell: 'M6.2 9.5a5.8 5.8 0 0 1 11.6 0c0 4.8 1.8 5.7 1.8 5.7H4.4s1.8-.9 1.8-5.7zM9.8 19.2a2.3 2.3 0 0 0 4.4 0',
  headset: 'M5 13.5v-1.5a7 7 0 0 1 14 0v1.5M5 13.2h1.2a1.4 1.4 0 0 1 1.4 1.4v2.4A1.4 1.4 0 0 1 6.2 18.4H5zM19 13.2h-1.2a1.4 1.4 0 0 0-1.4 1.4v2.4a1.4 1.4 0 0 0 1.4 1.4H18a1 1 0 0 0 1-1z',
  chevR: 'M9.5 5 16 11.5 9.5 18',
  chevL: 'M14.5 5 8 11.5 14.5 18',
  camera: 'M3.4 8.6a2 2 0 0 1 2-2H7l1.4-2h7L17 6.6h1.6a2 2 0 0 1 2 2v8.4a2 2 0 0 1-2 2H5.4a2 2 0 0 1-2-2zM12 16.4a3.4 3.4 0 1 0 0-6.8 3.4 3.4 0 0 0 0 6.8z',
  sparkle: 'M12 3.2l1.7 5.1 5.1 1.7-5.1 1.7L12 16.8l-1.7-5.1L5.2 10l5.1-1.7zM18.5 15.5l.7 2 2 .7-2 .7-.7 2-.7-2-2-.7 2-.7z',
  wand: 'M14.5 4.2l5.3 5.3L8.5 20.8l-5.3-5.3zM13 5.7l5.3 5.3M16.5 3l.6 1.7 1.7.6-1.7.6-.6 1.7-.6-1.7-1.7-.6 1.7-.6z',
  gift: 'M4.5 9.5h15v10.3h-15zM3.6 6.6h16.8v2.9H3.6zM12 6.6v13.2M12 6.6S10.8 3 8.8 3a2 2 0 1 0 0 3.6zM12 6.6S13.2 3 15.2 3a2 2 0 1 1 0 3.6z',
  pin: 'M12 20.8s6.4-5.4 6.4-10.4a6.4 6.4 0 1 0-12.8 0c0 5 6.4 10.4 6.4 10.4zM12 12.4a2.4 2.4 0 1 0 0-4.8 2.4 2.4 0 0 0 0 4.8z',
  clock: 'M12 20a8 8 0 1 0 0-16 8 8 0 0 0 0 16zM12 7.4v4.9l3.2 1.9',
  check: 'M5 12.5l4.6 4.5L19 6.6',
  checkSm: 'M5 12l4 4 10-10',
  plus: 'M12 5v14M5 12h14',
  keyboard: 'M4.4 6.6h15.2a1.5 1.5 0 0 1 1.5 1.5v7.8a1.5 1.5 0 0 1-1.5 1.5H4.4a1.5 1.5 0 0 1-1.5-1.5V8.1a1.5 1.5 0 0 1 1.5-1.5zM7 10h.01M11 10h.01M15 10h.01M8.5 14h7',
  search: 'M10.8 17.6a6.8 6.8 0 1 0 0-13.6 6.8 6.8 0 0 0 0 13.6zM20 20l-4.4-4.4',
  share: 'M17.5 8.2a2.6 2.6 0 1 0 0-5.2 2.6 2.6 0 0 0 0 5.2zM6.5 14.6a2.6 2.6 0 1 0 0-5.2 2.6 2.6 0 0 0 0 5.2zM17.5 21a2.6 2.6 0 1 0 0-5.2 2.6 2.6 0 0 0 0 5.2zM8.8 10.4l6.4-3.6M8.8 13.6l6.4 3.6',
  download: 'M12 4v11M8 11.2l4 4 4-4M5 19.4h14',
  shield: 'M12 3.2l7 3v5.6c0 4.4-3 7.4-7 9.2-4-1.8-7-4.8-7-9.2V6.2zM8.8 12l2.3 2.3L15.4 10',
  cardico: 'M3.4 6.4h17.2a1.5 1.5 0 0 1 1.5 1.5v8.2a1.5 1.5 0 0 1-1.5 1.5H3.4a1.5 1.5 0 0 1-1.5-1.5V7.9a1.5 1.5 0 0 1 1.5-1.5zM2 10h20M6 14.4h4',
  chat: 'M5 5h14a1.2 1.2 0 0 1 1.2 1.2v8.6A1.2 1.2 0 0 1 19 16H9.5L5.4 19.2A.5.5 0 0 1 4.6 18.8V6.2A1.2 1.2 0 0 1 5 5z',
  question: 'M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18zM9.4 9.2a2.7 2.7 0 0 1 4-2c1 .6 1.3 1.7.8 2.7-.5.9-1.6 1.1-1.9 2.1l-.1.6M12 16.3h.01',
  ticket: 'M4 7.2h16v3.1a2 2 0 0 0 0 3.4v3.1H4v-3.1a2 2 0 0 0 0-3.4zM14 7.2v9.7',
  close: 'M6 6l12 12M18 6L6 18',
  arrowR: 'M4.5 12h14.5M13 6l6 6-6 6',
  edit: 'M4 20h4.2L19 9.2 14.8 5 4 15.8zM13.4 6.4l4.2 4.2',
  refresh: 'M19.5 11a7.5 7.5 0 1 0-1.6 5.4M19.5 11V5.6M19.5 11h-5.4',
  copy: 'M8.5 8.5h9a1.5 1.5 0 0 1 1.5 1.5v9a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 7 19v-9a1.5 1.5 0 0 1 1.5-1.5zM4.5 15.5A1.5 1.5 0 0 1 3 14V4.5A1.5 1.5 0 0 1 4.5 3H14a1.5 1.5 0 0 1 1.5 1.5',
  sliders: 'M4 7h10M18 7h2M4 12h2M10 12h10M4 17h7M15 17h5M14 5.4v3.2M6 10.4v3.2M11 15.4v3.2',
  bag: 'M6 8h12l-.9 11.4a1.4 1.4 0 0 1-1.4 1.3H8.3a1.4 1.4 0 0 1-1.4-1.3zM9 8V6.4a3 3 0 0 1 6 0V8',
  lock: 'M6.6 10.6V8a5.4 5.4 0 0 1 10.8 0v2.6M5.6 10.6h12.8a1 1 0 0 1 1 1V20a1 1 0 0 1-1 1H5.6a1 1 0 0 1-1-1v-8.4a1 1 0 0 1 1-1z',
  scenery: 'M3 18.5l5.5-7 3.5 4.2L15 12l6 6.5zM8 8.2a1.6 1.6 0 1 0 0-3.2 1.6 1.6 0 0 0 0 3.2z',
  dot: 'M12 13.4a1.4 1.4 0 1 0 0-2.8 1.4 1.4 0 0 0 0 2.8z',
  key: 'M15 4.2a3.9 3.9 0 1 0-3.5 5.7l-6 6V19.5h3.4v-2h2v-2h1.6A3.9 3.9 0 0 0 15 4.2zM15.4 7.9h.01',
  magnet: 'M6.6 5h10.8a1.6 1.6 0 0 1 1.6 1.6v10.8a1.6 1.6 0 0 1-1.6 1.6H6.6A1.6 1.6 0 0 1 5 17.4V6.6A1.6 1.6 0 0 1 6.6 5zM9 9h6v6H9z',
  album: 'M6 4.5h11.5a1 1 0 0 1 1 1v13a1 1 0 0 0-1-1H6zM6 4.5A1.5 1.5 0 0 0 4.5 6v12A1.5 1.5 0 0 0 6 19.5M8.4 8.3h6.4M8.4 11.3h4.2',
  trophy: 'M8 4.5h8v3.2a4 4 0 0 1-8 0zM8 5.5H5.4v1.6a2.6 2.6 0 0 0 2.6 2.6M16 5.5h2.6v1.6a2.6 2.6 0 0 1-2.6 2.6M12 11.7v3.6M9 19.5h6l-.6-2.6a1 1 0 0 0-1-.8h-2.8a1 1 0 0 0-1 .8z',
  moreV: 'M12 5.5a1.1 1.1 0 1 0 0 2.2 1.1 1.1 0 0 0 0-2.2zM12 10.9a1.1 1.1 0 1 0 0 2.2 1.1 1.1 0 0 0 0-2.2zM12 16.3a1.1 1.1 0 1 0 0 2.2 1.1 1.1 0 0 0 0-2.2z',
  moreH: 'M5.5 12a1.1 1.1 0 1 0 2.2 0 1.1 1.1 0 0 0-2.2 0zM10.9 12a1.1 1.1 0 1 0 2.2 0 1.1 1.1 0 0 0-2.2 0zM16.3 12a1.1 1.1 0 1 0 2.2 0 1.1 1.1 0 0 0-2.2 0z',
  flag: 'M6.5 4v16M6.5 4.8h9.4l-2.4 3.4 2.4 3.4H6.5'
};

function Icon({ name, size = 22, color = 'currentColor', stroke = 1.6, fill, style }) {
  const d = PATHS[name];
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none"
    style={{ display: 'block', flexShrink: 0, ...style }}>
      <path d={d} stroke={color} strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round" fill={fill || 'none'} />
    </svg>);

}

// ── Real photo pool (玉龙雪山·蓝月谷 实拍) ──────────────────────────
// In standalone (bundled) builds, asset paths are remapped to inlined blob URLs via window.__resources.
window.lvAsset = function (p) { return (p && window.__resources && window.__resources[p]) || p; };
const LV_PHOTOS = ['assets/p1.jpg', 'assets/p2.jpg', 'assets/p3.jpg', 'assets/p4.jpg', 'assets/p5.jpg', 'assets/p6.jpg', 'assets/p7.jpg', 'assets/p8.jpg', 'assets/p9.jpg', 'assets/p10.jpg'];
function pickPhoto(seed) {
  const s = seed == null ? Math.random() : (seed % 1 + 1) % 1;
  return LV_PHOTOS[Math.floor(s * LV_PHOTOS.length) % LV_PHOTOS.length];
}
// stable 0..1 seed from any string/id → deterministic photo per item
function lvSeed(str) {let h = 0;str = '' + str;for (let i = 0; i < str.length; i++) h = h * 31 + str.charCodeAt(i) >>> 0;return h % 997 / 997;}

// ── Photo (real image, object-fit cover; src overrides the pooled pick) ──
function Photo({ tone = 'lake', src, pos = 'center 32%', label, tag, locked = false, selected = false, ratio = '1 / 1', radius = 0, seed, style, children, onClick }) {
  const autoSeed = React.useRef(Math.random());
  const s = seed != null ? seed : autoSeed.current;
  const img = window.lvAsset(src || pickPhoto(s));
  return (
    <div onClick={onClick} style={{
      position: 'relative', aspectRatio: ratio, width: '100%',
      borderRadius: radius, overflow: 'hidden', background: '#cfd9d3',
      boxShadow: 'inset 0 0 0 1px rgba(0,0,0,0.04)', cursor: onClick ? 'pointer' : 'default',
      ...style
    }}>
      <img src={img} alt="" loading="lazy" draggable="false" style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: pos, display: 'block' }} />
      <div className="lv-grain" style={{ position: 'absolute', inset: 0 }} />
      {/* gentle bottom vignette for photographic depth + legibility */}
      <div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to top, rgba(20,22,20,0.18), transparent 38%)' }} />
      {label &&
      <div style={{ position: 'absolute', left: 10, bottom: 9, color: 'rgba(255,255,255,0.95)',
        fontFamily: T.sans, fontSize: 11, letterSpacing: 0.3, fontWeight: 500, textShadow: '0 1px 4px rgba(0,0,0,0.3)' }}>{label}</div>
      }
      {tag &&
      <div style={{ position: 'absolute', left: 8, top: 8, ...tag.style,
        fontFamily: T.sans, fontSize: 10.5, letterSpacing: 0.4, fontWeight: 600,
        padding: '3px 7px', borderRadius: 4, background: tag.bg || 'rgba(255,255,255,0.92)', color: tag.color || T.ink }}>{tag.text}</div>
      }
      {locked &&
      <div style={{ position: 'absolute', right: 8, top: 8, display: 'flex', alignItems: 'center', gap: 3,
        padding: '3px 8px 3px 6px', borderRadius: 4, background: 'rgba(20,22,20,0.42)', color: '#fff',
        fontFamily: T.sans, fontSize: 10.5, fontWeight: 500, backdropFilter: 'blur(4px)' }}>
          <Icon name="lock" size={12} color="#fff" stroke={1.8} /> 未解锁
        </div>
      }
      {selected &&
      <>
          <div style={{ position: 'absolute', inset: 0, boxShadow: `inset 0 0 0 2.5px ${accent()}` }} />
          <div style={{ position: 'absolute', right: 8, top: 8, width: 22, height: 22, borderRadius: 999,
          background: accent(), display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            <Icon name="checkSm" size={14} color="#fff" stroke={2.4} />
          </div>
        </>
      }
      {children}
    </div>);

}

// ── Primitives ───────────────────────────────────────────────────
function Btn({ children, kind = 'primary', size = 'md', full, onClick, icon, disabled, style }) {
  const pad = size === 'lg' ? '15px 22px' : size === 'sm' ? '8px 14px' : '12px 18px';
  const fs = size === 'lg' ? 16 : size === 'sm' ? 13 : 15;
  const base = {
    display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 7,
    fontFamily: T.sans, fontSize: fs, fontWeight: 600, letterSpacing: 0.2,
    padding: pad, borderRadius: 7, border: 'none', cursor: disabled ? 'default' : 'pointer',
    width: full ? '100%' : undefined, boxSizing: 'border-box', lineHeight: 1.1,
    transition: 'transform .12s ease, opacity .12s ease', opacity: disabled ? 0.4 : 1,
    WebkitTapHighlightColor: 'transparent'
  };
  const kinds = {
    primary: { background: accent(), color: '#fff' },
    dark: { background: T.ink, color: '#fff' },
    ghost: { background: 'transparent', color: T.ink, boxShadow: `inset 0 0 0 1.4px ${T.line}` },
    soft: { background: T.field, color: T.ink, boxShadow: `inset 0 0 0 1px ${T.line}` },
    quiet: { background: 'transparent', color: T.muted }
  };
  return (
    <button onClick={disabled ? undefined : onClick} style={{ ...base, ...kinds[kind], ...style }}
    onMouseDown={(e) => !disabled && (e.currentTarget.style.transform = 'scale(0.975)')}
    onMouseUp={(e) => e.currentTarget.style.transform = 'scale(1)'}
    onMouseLeave={(e) => e.currentTarget.style.transform = 'scale(1)'}>
      {icon && <Icon name={icon} size={fs + 3} color={kinds[kind].color} stroke={1.8} />}
      {children}
    </button>);

}

function Card({ children, pad = 16, style, onClick }) {
  return (
    <div onClick={onClick} style={{
      background: T.card, borderRadius: 8, padding: pad,
      boxShadow: '0 1px 2px rgba(26,25,22,0.04), 0 8px 24px rgba(26,25,22,0.04)',
      cursor: onClick ? 'pointer' : 'default', ...style
    }}>{children}</div>);

}

// small uppercase editorial label
function Eyebrow({ children, color, style }) {
  return <div style={{ fontFamily: T.sans, fontSize: 11, fontWeight: 700, letterSpacing: 2, textTransform: 'uppercase', color: color || T.faint, ...style }}>{children}</div>;
}

// section header: chinese title + optional 更多
function SectionHead({ kicker, title, action, onAction, style }) {
  return (
    <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', padding: '0 2px', ...style }}>
      <div>
        {kicker && <Eyebrow style={{ marginBottom: 7 }}>{kicker}</Eyebrow>}
        <div style={{ fontFamily: T.sans, fontSize: 19, fontWeight: 700, color: T.ink, letterSpacing: 0.2, lineHeight: 1.2 }}>{title}</div>
      </div>
      {action &&
      <button onClick={onAction} style={{ display: 'flex', alignItems: 'center', gap: 2, background: 'none', border: 'none', cursor: 'pointer', color: T.muted, fontFamily: T.sans, fontSize: 13, padding: 0 }}>
          {action} <Icon name="chevR" size={14} color={T.muted} stroke={1.8} />
        </button>
      }
    </div>);

}

// price — serif numerals for an editorial premium feel
function Price({ value, size = 22, color, prefix = '¥', strike, style }) {
  if (strike) {
    return <span style={{ fontFamily: T.serif, fontSize: size * 0.8, color: T.faint, textDecoration: 'line-through', ...style }}>{prefix}{value}</span>;
  }
  return (
    <span style={{ fontFamily: T.serif, color: color || T.ink, display: 'inline-flex', alignItems: 'baseline', ...style }}>
      <span style={{ fontSize: size * 0.62, marginRight: 1 }}>{prefix}</span>
      <span style={{ fontSize: size, fontWeight: 500, letterSpacing: 0.3 }}>{value}</span>
    </span>);

}

function FreeTag({ children = '免费', style }) {
  return <span style={{ display: 'inline-flex', alignItems: 'center', fontFamily: T.serif, fontSize: 14, fontWeight: 500, color: accent(), letterSpacing: 0.5, ...style }}>{children}</span>;
}

function Pill({ children, active, onClick, style }) {
  return (
    <button onClick={onClick} style={{
      fontFamily: T.sans, fontSize: 13, fontWeight: 500, whiteSpace: 'nowrap',
      padding: '7px 14px', borderRadius: 4, border: 'none', cursor: 'pointer',
      background: active ? T.ink : T.card, color: active ? '#fff' : T.inkSoft,
      boxShadow: active ? 'none' : `inset 0 0 0 1px ${T.line}`, transition: 'all .15s', ...style
    }}>{children}</button>);

}

function Divider({ style }) {return <div style={{ height: 1, background: T.line, ...style }} />;}

// status dot + label
function StatusChip({ label, tone = 'wait', style }) {
  const map = {
    wait: { c: '#9A6A3C', b: 'rgba(154,106,60,0.10)' },
    going: { c: '#2F6F63', b: 'rgba(47,111,99,0.10)' },
    done: { c: T.muted, b: 'rgba(140,136,124,0.10)' },
    action: { c: '#fff', b: accent() }
  };
  const s = map[tone] || map.wait;
  return <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontFamily: T.sans, fontSize: 12, fontWeight: 600, color: s.c, background: s.b, padding: '4px 10px', borderRadius: 4, ...style }}>{label}</span>;
}

// ── WeChat capsule (system) ──────────────────────────────────────
function WeChatCapsule() {
  return (
    <div style={{ display: 'flex', alignItems: 'center', height: 32, borderRadius: 4,
      background: 'rgba(255,255,255,0.62)', boxShadow: 'inset 0 0 0 1px rgba(26,25,22,0.10)',
      backdropFilter: 'blur(8px)', WebkitBackdropFilter: 'blur(8px)' }}>
      <div style={{ width: 43, display: 'flex', justifyContent: 'center', alignItems: 'center', gap: 4 }}>
        <span style={{ width: 4, height: 4, borderRadius: 6, background: T.inkSoft }} />
        <span style={{ width: 4, height: 4, borderRadius: 6, background: T.inkSoft }} />
        <span style={{ width: 4, height: 4, borderRadius: 6, background: T.inkSoft }} />
      </div>
      <div style={{ width: 1, height: 17, background: 'rgba(26,25,22,0.14)' }} />
      <div style={{ width: 43, display: 'flex', justifyContent: 'center' }}>
        <span style={{ width: 14, height: 14, borderRadius: 6, boxShadow: `inset 0 0 0 1.6px ${T.inkSoft}` }} />
      </div>
    </div>);

}

// ── WeChat nav bar (sits below status bar) ───────────────────────
// statusBg: opaque color painted behind the OS status bar so scrolled
// content never shows through. Defaults to paper; pass T.ink on dark screens.
function WeChatNav({ title, left, rightIcons, onBack, transparent, statusBg }) {
  const sb = statusBg || (transparent ? 'transparent' : T.paper);
  return (
    <div style={{
      position: 'relative', zIndex: 8, flexShrink: 0,
      height: 104, display: 'flex', alignItems: 'center',
      padding: '60px 14px 0 18px',
      background: transparent ? 'transparent' : T.paper
    }}>
      {/* opaque status-bar backing — always covers the ~62px OS status bar */}
      <div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, background: sb, zIndex: -1 }} />
      {/* left */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, height: 44, flex: 1, minWidth: 0 }}>
        {onBack &&
        <button onClick={onBack} style={{ background: 'none', border: 'none', padding: 0, marginLeft: -4, marginRight: 2, cursor: 'pointer', display: 'flex' }}>
            <Icon name="chevL" size={26} color={T.ink} stroke={1.7} />
          </button>
        }
        {left ? left : title ?
        <span style={{ fontFamily: T.sans, fontSize: 17, fontWeight: 600, color: T.ink, letterSpacing: 0.3, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{title}</span> :
        null}
      </div>
      {/* right: page icons + capsule */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 14, height: 44 }}>
        {rightIcons && rightIcons.map((ic, i) =>
        <button key={i} onClick={ic.onClick} style={{ background: 'none', border: 'none', padding: 0, cursor: 'pointer', position: 'relative', display: 'flex' }}>
            <Icon name={ic.name} size={23} color={T.ink} stroke={1.6} />
            {ic.dot && <span style={{ position: 'absolute', top: -1, right: -1, width: 7, height: 7, borderRadius: 6, background: '#C0492F', boxShadow: `0 0 0 1.6px ${T.paper}` }} />}
          </button>
        )}
        <WeChatCapsule />
      </div>
    </div>);

}

// ── Bottom tab bar with center 绑卡 action ───────────────────────
function TabBar({ active, onTab, onBind }) {
  const tabs = [
  { key: 'home', label: '首页', icon: 'home' },
  { key: 'works', label: '影集', icon: 'image' },
  { key: 'orders', label: '订单', icon: 'receipt' },
  { key: 'mine', label: '我的', icon: 'user' }];

  const item = (t) => {
    const on = active === t.key;
    return (
      <button key={t.key} onClick={() => onTab(t.key)} style={{ flex: 1, background: 'none', border: 'none', cursor: 'pointer',
        display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4, padding: '9px 0 0', WebkitTapHighlightColor: 'transparent' }}>
        <Icon name={t.icon} size={24} color={on ? T.ink : T.faint} stroke={on ? 1.9 : 1.5} />
        <span style={{ fontFamily: T.sans, fontSize: 10.5, fontWeight: on ? 700 : 500, color: on ? T.ink : T.faint, letterSpacing: 0.5 }}>{t.label}</span>
      </button>);

  };
  return (
    <div style={{ position: 'relative', flexShrink: 0, background: T.card, paddingBottom: 22,
      boxShadow: '0 -0.5px 0 ' + T.line + ', 0 -8px 24px rgba(26,25,22,0.04)' }}>
      <div style={{ display: 'flex', alignItems: 'flex-start', position: 'relative' }}>
        {item(tabs[0])}
        {item(tabs[1])}
        <div style={{ width: 64, flexShrink: 0 }} />
        {item(tabs[2])}
        {item(tabs[3])}
      </div>
      {/* center bind button */}
      <button onClick={onBind} style={{ position: 'absolute', left: '50%', top: -20, transform: 'translateX(-50%)',
        width: 60, height: 60, borderRadius: 999, background: (window.LV_GOLD && window.LV_GOLD.grad) || '#D6AC54', border: '3px solid ' + T.card, cursor: 'pointer',
        display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 1,
        boxShadow: '0 6px 18px rgba(204,156,64,0.42)', WebkitTapHighlightColor: 'transparent' }}>
        <Icon name="scan" size={26} color={(window.LV_GOLD && window.LV_GOLD.text) || '#4A3209'} stroke={1.8} />
      </button>
      <span style={{ position: 'absolute', left: '50%', top: 43, transform: 'translateX(-50%)', fontFamily: T.sans, fontSize: 10.5, fontWeight: 700, color: T.ink, letterSpacing: 0.5, whiteSpace: 'nowrap' }}>绑卡</span>
    </div>);

}

// generic screen scaffold: nav + scrollable body (used by sub-screens)
function Screen({ nav, body, footer, bg = T.paper }) {
  return (
    <div style={{ height: '100%', display: 'flex', flexDirection: 'column', background: bg }}>
      {nav}
      <div className="lv-scroll" style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden' }}>{body}</div>
      {footer}
    </div>);

}

// bottom sheet (for payment / pickers)
function Sheet({ open, onClose, title, children, maxH = '80%' }) {
  if (!open) return null;
  return (
    <div style={{ position: 'absolute', inset: 0, zIndex: 200, display: 'flex', flexDirection: 'column', justifyContent: 'flex-end' }}>
      <div onClick={onClose} style={{ position: 'absolute', inset: 0, background: 'rgba(20,22,20,0.4)', animation: 'lvFade .2s ease' }} />
      <div style={{ position: 'relative', background: T.paper, borderRadius: '8px 8px 0 0', maxHeight: maxH, display: 'flex', flexDirection: 'column',
        animation: 'lvSlideUp .26s cubic-bezier(.2,.8,.2,1)', boxShadow: '0 -10px 40px rgba(0,0,0,0.2)' }}>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '16px 18px 10px' }}>
          <span style={{ fontFamily: T.sans, fontSize: 17, fontWeight: 700, color: T.ink }}>{title}</span>
          <button onClick={onClose} style={{ background: T.field, border: 'none', borderRadius: 999, width: 30, height: 30, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' }}>
            <Icon name="close" size={16} color={T.muted} stroke={2} />
          </button>
        </div>
        <div style={{ overflowY: 'auto', padding: '0 18px calc(22px + env(safe-area-inset-bottom))' }}>{children}</div>
      </div>
    </div>);

}

// ── Swipe-to-navigate hook（左右滑动看前/后一张） ──────────────────
// returns { dx, dragging, handlers }; spread handlers onto the image area,
// apply transform:translateX(dx) for live drag, snap back on release.
function lvUseSwipe({ onPrev, onNext, enabled = true, threshold = 42 }) {
  const st = React.useRef({ x: 0, y: 0, on: false, axis: null });
  const [dx, setDx] = React.useState(0);
  const [dragging, setDragging] = React.useState(false);
  const begin = (x, y) => { st.current = { x, y, on: true, axis: null }; };
  const move = (x, y) => {
    const s = st.current; if (!s.on) return;
    const ddx = x - s.x, ddy = y - s.y;
    if (!s.axis && (Math.abs(ddx) > 6 || Math.abs(ddy) > 6)) {
      s.axis = Math.abs(ddx) > Math.abs(ddy) ? 'x' : 'y';
      if (s.axis === 'x') setDragging(true);
    }
    if (s.axis === 'x') setDx(ddx);
  };
  const finish = (x) => {
    const s = st.current; if (!s.on) return;
    const ddx = x - s.x; s.on = false; setDx(0); setDragging(false);
    if (s.axis === 'x') { if (ddx <= -threshold) onNext && onNext(); else if (ddx >= threshold) onPrev && onPrev(); }
  };
  return {
    dx: enabled ? dx : 0, dragging,
    handlers: enabled ? {
      onTouchStart: (e) => begin(e.touches[0].clientX, e.touches[0].clientY),
      onTouchMove: (e) => move(e.touches[0].clientX, e.touches[0].clientY),
      onTouchEnd: (e) => finish(e.changedTouches[0].clientX),
      onMouseDown: (e) => begin(e.clientX, e.clientY),
      onMouseMove: (e) => { if (st.current.on) move(e.clientX, e.clientY); },
      onMouseUp: (e) => finish(e.clientX),
      onMouseLeave: (e) => { if (st.current.on) finish(e.clientX); }
    } : {}
  };
}

// 左右切换箭头（非触屏可点；到头时自动隐藏）
function lvNavArrow({ dir, onClick, show = true }) {
  if (!show) return null;
  const left = dir === 'prev';
  return (
    <button onClick={(e) => { e.stopPropagation(); onClick && onClick(); }}
      style={{ position: 'absolute', top: '50%', [left ? 'left' : 'right']: 8, transform: 'translateY(-50%)',
        width: 36, height: 36, borderRadius: 999, border: 'none', cursor: 'pointer', zIndex: 4,
        background: 'rgba(255,255,255,0.13)', backdropFilter: 'blur(4px)', WebkitBackdropFilter: 'blur(4px)',
        display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
      <Icon name={left ? 'chevL' : 'chevR'} size={19} color="#fff" stroke={2} />
    </button>);
}

Object.assign(window, {
  LV_T: T, lvAccent: accent, TONES, LV_PHOTOS, pickPhoto, lvSeed, lvUseSwipe, lvNavArrow,
  Icon, Photo, Btn, Card, Eyebrow, SectionHead, Price, FreeTag, Pill, Divider, StatusChip,
  WeChatCapsule, WeChatNav, TabBar, Screen, Sheet
});