// screens.jsx — all screens for the Nurse CNE app

const { useState: uS, useEffect: uE, useMemo: uM, useRef: uR, Fragment: F } = React;

// ─────────────────────────────────────────────────────────────────
// Humanize a raw schema field name (e.g. "venue_en", "registration_url")
// into a readable label for change-log diffs. Strips locale suffixes
// (_en/_zh), replaces underscores with spaces, fixes a few acronyms,
// and provides a curated map for fields that need a friendlier name.
// ─────────────────────────────────────────────────────────────────
const FIELD_LABELS = {
  en: {
    venue:                 'Venue',
    date:                  'Date',
    time_text:             'Time',
    fee_hkd:               'Fee',
    fee_per_session_hkd:   'Fee per session',
    cost:                  'Fee',
    cost_member:           'Member fee',
    cost_non_member:       'Non-member fee',
    cost_early_bird:       'Early bird fee',
    cne_points:            'CNE points',
    cne_per_session:       'CNE points per session',
    cne_points_note:       'CNE points note',
    cem_points:            'CEM points',
    points:                'Points',
    quota:                 'Capacity',
    spots:                 'Spots',
    online_platform:       'Online platform',
    online:                'Online',
    title:                 'Title',
    blurb:                 'Description',
    description_short:     'Short description',
    description_full:      'Description',
    objectives:            'Objectives',
    syllabus:              'Syllabus',
    target:                'Target audience',
    target_audience:       'Target audience',
    entry_requirement:     'Entry requirement',
    instructor:            'Instructor',
    instructors:           'Instructors',
    speakers:              'Speakers',
    topics:                'Topics',
    theme:                 'Theme',
    organizer:             'Organizer',
    provider:              'Provider',
    provider_full:         'Provider',
    provider_short:        'Provider',
    provider_code:         'Provider code',
    academy:               'Academy',
    specialty_area:        'Specialty',
    materials_language:    'Materials language',
    duration_text:         'Duration',
    sessions:              'Sessions',
    application_deadline:  'Application deadline',
    registration_deadline: 'Registration deadline',
    registration_url:      'Registration link',
    registration_method:   'Registration method',
    registration_contact:  'Registration contact',
    early_bird_registration_deadline: 'Early bird deadline',
    abstract_submission_deadline:     'Abstract deadline',
    assessment_required:        'Assessment required',
    assessment_format:          'Assessment format',
    assessment_passing_criteria:'Passing criteria',
    attendance_requirement:     'Attendance requirement',
    certificate_type:           'Certificate type',
    certificate_requirements:   'Certificate requirements',
    notes:                 'Notes',
    url:                   'Course page',
    source_url:            'Source',
  },
  zh: {
    venue: '地點', date: '日期', time_text: '時間',
    fee_hkd: '費用', fee_per_session_hkd: '每節費用',
    cost: '費用', cost_member: '會員費', cost_non_member: '非會員費', cost_early_bird: '早鳥費',
    cne_points: 'CNE 分數', cne_per_session: '每節分數', cne_points_note: '分數備註',
    cem_points: 'CEM 分數', points: '分數',
    quota: '名額', spots: '名額',
    online_platform: '網上平台', online: '網上課程',
    title: '課程名稱', blurb: '簡介',
    description_short: '簡介', description_full: '詳細介紹',
    objectives: '課程目標', syllabus: '課程大綱',
    target: '對象', target_audience: '對象',
    entry_requirement: '入學要求',
    instructor: '導師', instructors: '導師', speakers: '講者',
    topics: '研討主題', theme: '主題',
    organizer: '主辦', provider: '機構', provider_full: '機構', provider_short: '機構',
    provider_code: '機構編號', academy: '學院',
    specialty_area: '專科', materials_language: '教材語言',
    duration_text: '時數', sessions: '節數',
    application_deadline: '申請截止', registration_deadline: '報名截止',
    registration_url: '報名連結', registration_method: '報名方法', registration_contact: '聯絡',
    early_bird_registration_deadline: '早鳥截止',
    abstract_submission_deadline: '摘要截止',
    assessment_required: '需要評核', assessment_format: '評核方式',
    assessment_passing_criteria: '合格要求', attendance_requirement: '出席要求',
    certificate_type: '證書類型', certificate_requirements: '證書要求',
    notes: '備註', url: '課程頁', source_url: '原始資料',
  },
};
function humanField(raw, lang) {
  if (!raw) return '';
  // Strip locale suffixes so "venue_en" and "venue_zh" map to the same label.
  const key = String(raw).replace(/_(en|zh|hk|tc|sc)$/i, '');
  const dict = FIELD_LABELS[lang === 'zh' ? 'zh' : 'en'];
  if (dict[key]) return dict[key];
  // Fallback: titlecase the underscored name.
  return key
    .split('_')
    .map(w => w.length <= 3 ? w.toUpperCase() : (w[0].toUpperCase() + w.slice(1)))
    .join(' ');
}

// Map a Supabase profile row → the USER-shape the screens already render.
function userFromProfile(p, records, baseline) {
  if (!p) return null;
  const initials = ((p.name_en || p.name_zh || '?').trim().split(/\s+/).map(s => s[0]).join('').slice(0, 2) || '?').toUpperCase();
  const earned = (Number(p.baseline_points) || 0) + (records || []).reduce((s, r) => s + (Number(r.points) || 0), 0);
  return {
    name:      { en: p.name_en || '',     zh: p.name_zh || p.name_en || '' },
    shortName: { en: p.short_name_en || (p.name_en || '').split(' ')[0] || 'Nurse',
                 zh: p.short_name_zh || p.name_zh || '' },
    initials,
    license:  p.license_no || '—',
    hospital: { en: p.hospital_en || '—', zh: p.hospital_zh || p.hospital_en || '—' },
    role:     { en: p.role_en || 'Registered Nurse', zh: p.role_zh || '註冊護士' },
    cycleStart:  p.cycle_start,
    renewalDate: p.renewal_date || '2026-12-31',
    required: Number(p.required_points) || 60,
    earned,
  };
}

// ─────────────────────────────────────────────────────────────────────────────
// HOME / DASHBOARD
// ─────────────────────────────────────────────────────────────────────────────
function HomeScreen({ lang, setLang, onOpenCourse, onTab, onOpenSheet, onSignIn, savedIds, enrolledIds, session, profile, records = [], reminders = [], onRouteReminder }) {
  const isGuest = !session;

  // ── Guest variant ──────────────────────────────────────────────────────
  if (isGuest) {
    const recommended = COURSES.filter(c => isUpcomingCourse(c)).slice(0, 6);
    const hour = new Date().getHours();
    const greet = hour < 12 ? STR.goodMorning : hour < 18 ? STR.goodAfternoon : STR.goodEvening;

    return (
      <div className="scroll fade-in" style={{ paddingBottom: 32 }}>
        <div className="top">
          <div style={{ flex: 1 }}>
            <div className="h-greet">{L(greet, lang)}</div>
            <div className="h-screen" style={{ marginTop: 4 }}>
              {lang === 'zh' ? '探索 CNE 課程' : 'Explore CNE courses'}
            </div>
          </div>
          <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
            <LangPill lang={lang} setLang={setLang} />
          </div>
        </div>

        {/* Welcome / sign-in card */}
        <div style={{ padding: '14px 20px 0' }}>
          <div className="guest-card">
            <div className="gc-eyebrow">
              {lang === 'zh' ? '本港護士 CNE 助手' : 'Hong Kong nurse · CNE companion'}
            </div>
            <h2 className="gc-title">
              {lang === 'zh'
                ? '追蹤每一分 CNE,完整準備續期。'
                : 'Track every CNE point, ready your renewal.'}
            </h2>
            <p className="gc-sub">
              {lang === 'zh'
                ? '由 NCHK 認可的 13 間機構,118 個課程 — 隨時隨地瀏覽。建立帳戶後即可儲存、報名及記錄完成的課程。'
                : '118 NCHK-accredited courses from 13 providers. Browse freely — sign in to save, enrol, and log your record.'}
            </p>
            <div className="gc-cta">
              <button className="btn btn-primary" onClick={onSignIn}>
                <Icon name="user" size={16} stroke={1.9} />
                {lang === 'zh' ? '登入或建立帳戶' : 'Sign in or create account'}
              </button>
              <button className="btn btn-ghost" onClick={() => onTab('discover')}>
                {lang === 'zh' ? '先看看課程' : 'Browse courses first'}
                <Icon name="arrow-r" size={15} stroke={1.7} />
              </button>
            </div>
          </div>
        </div>

        {/* Recommended */}
        <Section title={L(STR.recommended, lang)} action={L(STR.viewAll, lang)} onAction={() => onTab('discover')}>
          <div className="scroll-x">
            {recommended.map(c => (
              <MiniCourseCard key={c.id} course={c} lang={lang} onClick={() => onOpenCourse(c.id)} />
            ))}
          </div>
        </Section>

        {/* How it works — three info cards */}
        <Section title={lang === 'zh' ? '如何運作' : 'How it works'}>
          <div className="how-it-works" style={{ padding: '0 20px', display: 'grid', gridTemplateColumns: '1fr', gap: 10 }}>
            <HowItem n="1"
              title={lang === 'zh' ? '瀏覽課程' : 'Browse courses'}
              body={lang === 'zh' ? '從 13 間 NCHK 認可機構搜尋及篩選課程。' : 'Search and filter across 13 NCHK-accredited providers.'} />
            <HowItem n="2"
              title={lang === 'zh' ? '報名或收藏' : 'Enrol or save'}
              body={lang === 'zh' ? '一鍵儲存喜歡的課程,或記下將要參加的課程。' : 'Bookmark courses you like, or mark the ones you’ll attend.'} />
            <HowItem n="3"
              title={lang === 'zh' ? '記錄完成' : 'Log completion'}
              body={lang === 'zh' ? '上傳證書,系統會自動計算週期內已修分數及距續期日子。' : 'Upload certificates and your dashboard tracks points + days to renewal.'} />
          </div>
        </Section>
      </div>
    );
  }

  // ── Signed-in variant ──────────────────────────────────────────────────
  const u = userFromProfile(profile, records) || USER;
  const earned = u.earned;
  const required = u.required;
  const pct = Math.round((earned / required) * 100);
  // category split from real records
  const splitRaw = (records || []).reduce((acc, r) => {
    acc[r.category || 'elective'] = (acc[r.category || 'elective'] || 0) + (Number(r.points) || 0);
    return acc;
  }, {});
  const split = { core: splitRaw.core || 0, elective: splitRaw.elective || 0, specialty: splitRaw.specialty || 0 };
  const segments = [
    { value: split.core,      total: required, color: CAT_COLORS.core },
    { value: split.elective,  total: required, color: CAT_COLORS.elective },
    { value: split.specialty, total: required, color: CAT_COLORS.specialty },
  ];

  const today = new Date().toISOString().slice(0,10);
  const days = Math.max(0, daysBetween(today, u.renewalDate));

  const upcoming = COURSES.filter(c => enrolledIds.has(c.id) && isUpcomingCourse(c));
  const recommended = COURSES.filter(c => !enrolledIds.has(c.id) && isUpcomingCourse(c)).slice(0, 5);
  const thisYear = new Date().getFullYear();
  const recentRecord = (records || []).filter(r => r.year === thisYear || r.year === thisYear - 1).slice(0, 3);

  const hour = new Date().getHours();
  const greet = hour < 12 ? STR.goodMorning : hour < 18 ? STR.goodAfternoon : STR.goodEvening;

  return (
    <div className="scroll fade-in" style={{ paddingBottom: 32 }}>
      <div className="top">
        <div style={{ flex: 1 }}>
          <div className="h-greet">{L(greet, lang)}</div>
          <div className="h-screen" style={{ marginTop: 4 }}>{L(u.shortName, lang)}</div>
        </div>
        <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
          <LangPill lang={lang} setLang={setLang} />
          <button className="icon-btn" onClick={() => onOpenSheet('reminders')} style={{ position: 'relative' }}>
            <Icon name="bell" size={18} />
            <span className="dot" style={{ position: 'absolute' }}></span>
          </button>
        </div>
      </div>

      {/* Wallet — clickable: opens Profile > Record so the user can see
          the courses that earned the points displayed here. */}
      <div style={{ padding: '14px 20px 0' }}>
        <M.div
          className="wallet wallet-clickable"
          role="button"
          tabIndex={0}
          aria-label={lang === 'zh' ? '查看已修課程記錄' : 'View earned record'}
          onClick={() => onTab && onTab('profile', 'record')}
          onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onTab && onTab('profile', 'record'); } }}
          whileHover={{ y: -1 }}
          whileTap={{ scale: 0.995 }}
          transition={{ duration: 0.16 }}
        >
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 10 }}>
            <div className="eyebrow">{L(STR.pointsWallet, lang)}</div>
            <span className="chip ghost" style={{ background: 'transparent', border: '0.5px solid var(--border)' }}>
              <Icon name="clock" size={11} stroke={1.7} />
              {days} {L(STR.daysToRenewal, lang)}
            </span>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 18, position: 'relative', zIndex: 1 }}>
            <div style={{ position: 'relative', width: 132, height: 132, flexShrink: 0 }}>
              <ProgressRing size={132} stroke={9} segments={segments} />
              <div style={{
                position: 'absolute', inset: 0,
                display: 'flex', flexDirection: 'column',
                alignItems: 'center', justifyContent: 'center',
              }}>
                <div className="ring-num">{earned}</div>
                <div className="mono muted-2" style={{ fontSize: 9.5, letterSpacing: '0.10em', textTransform: 'uppercase', marginTop: 2 }}>
                  / {required} {L(STR.pts, lang)}
                </div>
              </div>
            </div>
            <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 10 }}>
              <CatLegend label={L(STR.core, lang)}      cat="core"      val={split.core} required={20} />
              <CatLegend label={L(STR.elective, lang)}  cat="elective"  val={split.elective} required={20} />
              <CatLegend label={L(STR.specialty, lang)} cat="specialty" val={split.specialty} required={20} />
            </div>
          </div>
          <div className="wallet-cta-row">
            <span>{lang === 'zh' ? '查看已修課程' : 'View earned courses'}</span>
            <Icon name="arrow-r" size={13} stroke={1.7} />
          </div>
        </M.div>
      </div>

      {/* Upcoming */}
      {upcoming.length > 0 && (
        <Section title={L(STR.upcoming, lang)} action={L(STR.viewAll, lang)} onAction={() => onTab && onTab('profile', 'record')}>
          <div style={{ padding: '0 20px', display: 'flex', flexDirection: 'column', gap: 10 }}>
            {upcoming.map(c => (
              <UpcomingRow key={c.id} course={c} lang={lang} onClick={() => onOpenCourse(c.id)} />
            ))}
          </div>
        </Section>
      )}

      {/* Recommended (horizontal scroll) */}
      <Section title={L(STR.recommended, lang)} action={L(STR.viewAll, lang)} onAction={() => onTab && onTab('discover')}>
        <div className="scroll-x">
          {recommended.map(c => (
            <MiniCourseCard key={c.id} course={c} lang={lang} onClick={() => onOpenCourse(c.id)} />
          ))}
        </div>
      </Section>

      {/* Reminders — surfaced inline so the user sees what needs action
          right under the recommendation strip. Tapping a row routes to
          the relevant destination (course detail / record / overview). */}
      {reminders && reminders.length > 0 && (
        <Section
          title={lang === 'zh' ? '提醒' : 'Reminders'}
          action={reminders.length > 4 ? (lang === 'zh' ? '查看全部' : 'View all') : null}
          onAction={() => onOpenSheet && onOpenSheet('reminders')}>
          {typeof RemindersInlineList === 'function' && (
            <RemindersInlineList
              lang={lang}
              reminders={reminders}
              onRouteReminder={onRouteReminder}
              max={4}
            />
          )}
        </Section>
      )}

      {/* Recent activity */}
      <Section title={L(STR.recentActivity, lang)} action={L(STR.viewAll, lang)} onAction={() => onTab && onTab('profile', 'record')}>
        <div style={{ padding: '0 20px' }} className="card flush">
          {recentRecord.map((r, i) => (
            <div key={r.id} className="card-row" style={{ borderBottom: i === recentRecord.length - 1 ? 0 : undefined }}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
                  <CatDot cat={r.category} />
                  <div className="mono muted-2" style={{ fontSize: 10.5, letterSpacing: '0.10em', textTransform: 'uppercase' }}>
                    {fmtShort(r.date, lang)} · {L(r.provider, lang)}
                  </div>
                </div>
                <div className="h-row">{L(r.title, lang)}</div>
              </div>
              <div style={{ textAlign: 'right' }}>
                <div className="serif" style={{ fontSize: 22, lineHeight: 1, letterSpacing: '-0.02em' }}>+{r.points}</div>
                {r.certificate
                  ? <Icon name="check-circ" size={13} color="var(--success)" stroke={1.8} />
                  : <span style={{ fontSize: 9.5, color: 'var(--accent)' }}>!</span>}
              </div>
            </div>
          ))}
        </div>
      </Section>
    </div>
  );
}

function CatLegend({ label, cat, val, required }) {
  const pct = Math.min(100, Math.round((val / required) * 100));
  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 4 }}>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, color: 'var(--fg-2)', fontWeight: 500 }}>
          <CatDot cat={cat} size={6} />
          {label}
        </span>
        <span className="mono" style={{ fontSize: 11, color: 'var(--fg)', fontVariantNumeric: 'tabular-nums' }}>
          {val}/{required}
        </span>
      </div>
      <div className="progress-bar" style={{ height: 4 }}>
        <div style={{ width: pct + '%', background: CAT_COLORS[cat] }} />
      </div>
    </div>
  );
}

function UpcomingRow({ course, lang, onClick }) {
  const today = '2026-05-10';
  const d = daysBetween(today, course.date);
  return (
    <div className="card" style={{ padding: 14, cursor: 'pointer', display: 'flex', gap: 14, alignItems: 'center' }} onClick={onClick}>
      <div style={{
        width: 52, flexShrink: 0,
        display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
        gap: 0, padding: '6px 0',
        borderRight: '0.5px solid var(--border)',
        marginRight: 4,
      }}>
        <div className="mono" style={{ fontSize: 9.5, letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--fg-3)' }}>
          {new Date(course.date + 'T00:00:00').toLocaleString('en', { month: 'short' })}
        </div>
        <div className="serif" style={{ fontSize: 26, lineHeight: 1, letterSpacing: '-0.02em' }}>
          {new Date(course.date + 'T00:00:00').getDate()}
        </div>
      </div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ display: 'flex', gap: 6, marginBottom: 5, alignItems: 'center' }}>
          <TopicPill topic={course.topic} label={L(course.topicLabel, lang)} />
        </div>
        <div className="h-row" style={{ fontSize: 14 }}>{L(course.title, lang)}</div>
        <div className="muted" style={{ fontSize: 11.5, marginTop: 3 }}>
          {course.time} · {L(course.venue, lang).split(',')[0]}
        </div>
      </div>
      <div style={{ textAlign: 'right', flexShrink: 0 }}>
        <div className="chip solid" style={{ marginBottom: 4 }}>
          {d === 0 ? (lang === 'zh' ? '今日' : 'Today') : `${d}${lang === 'zh' ? '日' : 'd'}`}
        </div>
        <div className="mono muted-2" style={{ fontSize: 9.5, letterSpacing: '0.10em', textTransform: 'uppercase' }}>
          {course.points} {L(STR.pts, lang)}
        </div>
      </div>
    </div>
  );
}

function Section({ title, action, onAction, children }) {
  return (
    <div style={{ marginTop: 26 }}>
      <div style={{ padding: '0 20px 12px', display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
        <h3 className="h-section" style={{ margin: 0 }}>{title}</h3>
        {action && (
          <button onClick={onAction} style={{
            appearance: 'none', background: 'transparent', border: 0,
            color: 'var(--fg-2)', font: 'inherit', fontSize: 12.5,
            display: 'inline-flex', gap: 4, alignItems: 'center', cursor: 'pointer',
          }}>
            {action} <Icon name="chev-r" size={13} stroke={1.6} />
          </button>
        )}
      </div>
      {children}
    </div>
  );
}

function HowItem({ n, title, body }) {
  return (
    <div className="card" style={{ display: 'flex', gap: 14, alignItems: 'flex-start' }}>
      <div style={{
        width: 32, height: 32, borderRadius: '50%',
        background: 'var(--primary-soft)', color: 'var(--primary)',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        flexShrink: 0,
        fontFamily: 'var(--font-serif)', fontWeight: 500, fontSize: 14,
        letterSpacing: '-0.02em',
      }}>{n}</div>
      <div style={{ minWidth: 0 }}>
        <div className="h-row" style={{ fontSize: 14 }}>{title}</div>
        <div className="muted" style={{ fontSize: 12.5, marginTop: 3, lineHeight: 1.45 }}>{body}</div>
      </div>
    </div>
  );
}

function LangPill({ lang, setLang }) {
  return (
    <div className="lang-toggle">
      <button className={lang === 'en' ? 'on' : ''} onClick={() => setLang('en')}>EN</button>
      <button className={lang === 'zh' ? 'on' : ''} onClick={() => setLang('zh')}>繁</button>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// DISCOVER
// ─────────────────────────────────────────────────────────────────────────────
function DiscoverScreen({ lang, setLang, onOpenCourse, view, setView, initialFilters, onBrowseProviders }) {
  const [query, setQuery] = uS('');
  const [filter, setFilter] = uS('all');
  const [showExpired, setShowExpired] = uS(false);
  // "Only with seats" — hide courses whose remaining capacity is 0
  // (still shows unlimited / unknown-capacity rows; only excludes confirmed-full).
  const [onlyWithSeats, setOnlyWithSeats] = uS(false);

  // Advanced filter state — multi-facet, surfaced via <DiscoverFilterSheet>
  const [advFilters, setAdvFilters] = uS(() =>
    (window.DiscoverFilters?.emptyFilters?.() || {})
  );
  const [filterSheetOpen, setFilterSheetOpen] = uS(false);

  // Inbound provider filter (when arriving from ProvidersScreen)
  uE(() => {
    if (initialFilters?.provider_code) {
      setAdvFilters(f => ({ ...(f || {}), provider_code: [initialFilters.provider_code] }));
    }
  }, [initialFilters?.provider_code]);

  const activeAdvCount = (window.DiscoverFilters?.countActive?.(advFilters)) || 0;

  const filtered = uM(() => {
    // Always compute against today's real date so the filter never goes stale.
    const today = new Date().toISOString().slice(0, 10);
    let r = showExpired ? COURSES : COURSES.filter(c => isUpcomingCourse(c, today));
    if (filter === 'free')     r = r.filter(c => c.cost === 0);
    if (filter === 'online')   r = r.filter(c => c.online);
    if (filter === 'inperson') r = r.filter(c => !c.online);
    if (filter === 'workshop') r = r.filter(c => c.format === 'workshop');
    if (filter === 'lecture')  r = r.filter(c => c.format === 'lecture');
    if (filter === 'elearn')   r = r.filter(c => c.format === 'elearning');
    if (filter === 'expired')  r = r.filter(c => isPastCourse(c, today));
    if (onlyWithSeats) {
      r = r.filter(c => {
        const s = c.seats;
        // No info → keep (we can't prove it's full); confirmed full → drop.
        if (!s) return true;
        if (s.kind === 'full') return false;
        return true;
      });
    }
    if (query) {
      const q = query.toLowerCase();
      r = r.filter(c =>
        L(c.title, lang).toLowerCase().includes(q) ||
        L(c.providerShort, lang).toLowerCase().includes(q) ||
        L(c.topicLabel, lang).toLowerCase().includes(q));
    }
    if (activeAdvCount > 0 && window.DiscoverFilters?.applyFilters) {
      r = window.DiscoverFilters.applyFilters(r, advFilters);
    }
    return r;
  }, [filter, query, lang, showExpired, onlyWithSeats, advFilters, activeAdvCount]);

  const totalExpired = uM(() => {
    const today = new Date().toISOString().slice(0, 10);
    return COURSES.filter(c => isPastCourse(c, today)).length;
  }, []);

  // When user picks the "Expired" chip, auto-enable the toggle.
  // When user picks any other chip, auto-disable the toggle.
  const onPickFilter = (k) => {
    setFilter(k);
    setShowExpired(k === 'expired');
  };

  return (
    <div className="scroll fade-in" style={{ paddingBottom: 24 }}>
      <ScreenHeader
        title={L(STR.discover, lang)}
        eyebrow={lang === 'zh' ? '所有可報課程' : 'All available courses'}
        actions={<LangPill lang={lang} setLang={setLang} />}
      />

      {/* Sticky filter / search bar — stays pinned to the top of the
          scroll container so the controls follow the user down. */}
      <div className="discover-sticky">
      <div style={{ padding: '8px 20px 0' }}>
        <div className="search-bar">
          <Icon name="search" size={17} color="var(--fg-3)" stroke={1.6} />
          <input
            value={query}
            onChange={e => setQuery(e.target.value)}
            placeholder={L(STR.searchCourses, lang)}
          />
        </div>
        {/* Browse-by-provider entry point — only on mobile (side-nav covers tablet+) */}
        {onBrowseProviders && (
          <button type="button" className="discover-browse-providers" onClick={onBrowseProviders}>
            <Icon name="pin" size={14} stroke={1.6} color="var(--fg-3)" />
            <span>{L(STR.byProvider, lang)}</span>
            <Icon name="chev-r" size={14} stroke={1.7} color="var(--fg-3)" />
          </button>
        )}
      </div>

      <div className="scroll-x" style={{ marginTop: 12 }}>
        {[
          ['all', STR.all], ['free', STR.free], ['online', STR.online], ['inperson', STR.inPerson],
          ['workshop', STR.workshop], ['lecture', STR.lecture], ['elearn', STR.elearning],
        ].map(([k, s]) => (
          <button key={k}
            className={'filter-chip' + (filter === k ? ' on' : '')}
            onClick={() => onPickFilter(k)}>
            {L(s, lang)}
          </button>
        ))}
        <button
          className={'filter-chip' + (filter === 'expired' ? ' on' : '')}
          onClick={() => onPickFilter('expired')}>
          {lang === 'zh' ? '已過期' : 'Expired'}
          {totalExpired > 0 && (
            <span className="mono" style={{ marginLeft: 4, opacity: 0.65, fontSize: 10.5 }}>
              {totalExpired}
            </span>
          )}
        </button>
      </div>

      <div style={{ padding: '6px 20px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 10 }}>
        <div className="muted" style={{ fontSize: 12 }}>
          {filtered.length} {lang === 'zh' ? '個課程' : 'courses'}
        </div>

        <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
          {/* Advanced filters trigger — surfaces provider / specialty / language / mode / etc. */}
          <button
            type="button"
            className={'filters-trigger' + (activeAdvCount > 0 ? ' has-active' : '')}
            onClick={() => setFilterSheetOpen(true)}
            aria-label={L(STR.filters, lang)}>
            <Icon name="filter" size={14} stroke={1.7} />
            <span>{L(STR.filters, lang)}</span>
            {activeAdvCount > 0 && <span className="filters-trigger-count">{activeAdvCount}</span>}
          </button>

          {/* Inline toggle — "Only show with seats available" */}
          <label style={{ display: 'inline-flex', alignItems: 'center', gap: 8, cursor: 'pointer' }}>
            <span className="muted" style={{ fontSize: 12 }}>
              {lang === 'zh' ? '只顯示有名額' : 'Only with seats'}
            </span>
            <span
              role="switch"
              aria-checked={onlyWithSeats}
              tabIndex={0}
              onClick={() => setOnlyWithSeats(v => !v)}
              onKeyDown={(e) => { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); setOnlyWithSeats(v => !v); } }}
              style={{
                position: 'relative',
                width: 32, height: 18,
                borderRadius: 999,
                background: onlyWithSeats ? 'var(--primary)' : 'var(--border-strong)',
                transition: 'background 160ms',
                flexShrink: 0,
              }}>
              <span style={{
                position: 'absolute', top: 2, left: onlyWithSeats ? 16 : 2,
                width: 14, height: 14, borderRadius: '50%',
                background: '#fff',
                transition: 'left 160ms',
                boxShadow: '0 1px 2px rgba(0,0,0,0.2)',
              }} />
            </span>
          </label>

          {/* (Removed list/grid view switcher — list view is the standard
              presentation; the grid alternative was distracting and rarely
              used.) */}
        </div>
      </div>

      {/* Active filter chips bar — removable chips for every selected facet,
          so a glance tells the user what's narrowing their results. */}
      {activeAdvCount > 0 && (
        <div className="active-filters-bar">
          {(window.DiscoverFilters?.FACETS || []).flatMap(facet => {
            const sel = advFilters[facet.key] || [];
            return sel.map(value => (
              <span key={facet.key + ':' + value} className="active-filter-chip">
                <span>{window.DiscoverFilters?.zh && lang === 'zh' ? window.DiscoverFilters.zh(value) : value}</span>
                <button className="active-filter-chip-x"
                  aria-label={lang === 'zh' ? '移除' : 'Remove'}
                  onClick={() => setAdvFilters(f => ({ ...f, [facet.key]: (f[facet.key] || []).filter(v => v !== value) }))}>
                  <Icon name="close" size={11} stroke={2} />
                </button>
              </span>
            ));
          })}
          {advFilters.cost && (
            <span className="active-filter-chip">
              <span>{advFilters.cost === 'free' ? L(STR.free, lang) : (lang === 'zh' ? '付費' : 'Paid')}</span>
              <button className="active-filter-chip-x" onClick={() => setAdvFilters(f => ({ ...f, cost: null }))}>
                <Icon name="close" size={11} stroke={2} />
              </button>
            </span>
          )}
          {advFilters.deadlineDays != null && (
            <span className="active-filter-chip">
              <span>{lang === 'zh' ? `${advFilters.deadlineDays} 日內截止` : `Deadline ≤ ${advFilters.deadlineDays}d`}</span>
              <button className="active-filter-chip-x" onClick={() => setAdvFilters(f => ({ ...f, deadlineDays: null }))}>
                <Icon name="close" size={11} stroke={2} />
              </button>
            </span>
          )}
          {advFilters.points && (
            <span className="active-filter-chip">
              <span>{advFilters.points[0]}–{advFilters.points[1]} {L(STR.pts, lang)}</span>
              <button className="active-filter-chip-x" onClick={() => setAdvFilters(f => ({ ...f, points: null }))}>
                <Icon name="close" size={11} stroke={2} />
              </button>
            </span>
          )}
          <button className="active-filters-clear"
            onClick={() => setAdvFilters(window.DiscoverFilters?.emptyFilters?.() || {})}>
            {L(STR.filterClearAll, lang)}
          </button>
        </div>
      )}
      </div>{/* /.discover-sticky */}

      {/* Empty state when no courses match — high-quality fallback that
          tells the user EXACTLY which knob to turn (clear search vs.
          toggle "Show expired"). Beats a blank list. */}
      {filtered.length === 0 ? (
        <div className="vital-empty" style={{ marginTop: 12 }}>
          <div className="vital-empty-icon">
            <Icon name="search" size={22} stroke={1.7} />
          </div>
          <div className="vital-empty-title">
            {lang === 'zh' ? '找不到符合的課程' : 'No matching courses'}
          </div>
          <div className="vital-empty-sub">
            {query
              ? (lang === 'zh' ? `沒有結果符合「${query}」。試試清除搜尋或改變篩選。` : `Nothing matched "${query}". Clear the search or relax the filter.`)
              : (lang === 'zh' ? '試試切換篩選或關閉「只顯示有名額」。' : 'Try a different filter or turn off "Only with seats".')}
          </div>
          <div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
            {query && (
              <M.button className="btn btn-ghost" onClick={() => setQuery('')}
                whileTap={{ scale: 0.97 }} whileHover={{ y: -1 }}>
                {lang === 'zh' ? '清除搜尋' : 'Clear search'}
              </M.button>
            )}
            {filter !== 'all' && (
              <M.button className="btn btn-ghost" onClick={() => onPickFilter('all')}
                whileTap={{ scale: 0.97 }} whileHover={{ y: -1 }}>
                {lang === 'zh' ? '重設篩選' : 'Reset filters'}
              </M.button>
            )}
          </div>
        </div>
      ) : view === 'list' ? (
        <Stagger
          className=""
          delay={28}
          initialDelay={20}
          style={{ padding: '4px 20px', display: 'flex', flexDirection: 'column', gap: 10 }}>
          {filtered.map(c => (
            <CourseCard key={c.id} course={c} lang={lang} onClick={() => onOpenCourse(c.id)} />
          ))}
        </Stagger>
      ) : (
        <Stagger
          className="discover-grid"
          delay={22}
          initialDelay={20}
          style={{ padding: '4px 20px' }}>
          {filtered.map(c => (
            <GridCourseCard key={c.id} course={c} lang={lang} onClick={() => onOpenCourse(c.id)} />
          ))}
        </Stagger>
      )}

      {/* Advanced filter sheet — multi-facet picker (provider, specialty, language…) */}
      {typeof DiscoverFilterSheet === 'function' && (
        <DiscoverFilterSheet
          open={filterSheetOpen}
          onClose={() => setFilterSheetOpen(false)}
          filters={advFilters}
          onApply={setAdvFilters}
          lang={lang}
          courses={COURSES}
        />
      )}
    </div>
  );
}

function GridCourseCard({ course, lang, onClick }) {
  const ended = typeof isPastCourse === 'function' ? isPastCourse(course) : false;
  const onKey = (e) => {
    if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onClick && onClick(); }
  };
  return (
    <M.div
      role="button"
      tabIndex={0}
      aria-label={L(course.title, lang)}
      className={'course-card' + (ended ? ' is-ended' : '')}
      style={{ padding: 12, gap: 8 }}
      onClick={onClick}
      onKeyDown={onKey}
      whileHover={{ y: -2 }}
      whileTap={{ scale: 0.985 }}
      transition={{ duration: 0.18 }}
    >
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 6 }}>
        <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', flex: 1, minWidth: 0 }}>
          <TopicPill topic={course.topic} label={L(course.topicLabel, lang)} />
          {ended && (
            <span className="chip" style={{ background: 'var(--bg-2)', color: 'var(--fg-3)' }}>
              {lang === 'zh' ? '已過期' : 'Ended'}
            </span>
          )}
        </div>
        <div className="serif" style={{ fontSize: 22, lineHeight: 1, letterSpacing: '-0.02em', flexShrink: 0 }}>{course.points}</div>
      </div>
      <div className="h-row" style={{ fontSize: 13.5, lineHeight: 1.25, minHeight: 50 }}>
        {L(course.title, lang)}
      </div>
      <div className="mono muted-2" style={{ fontSize: 9.5, letterSpacing: '0.08em', textTransform: 'uppercase' }}>
        {fmtShort(course.date, lang)} · {L(course.providerShort, lang)}
      </div>
    </M.div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// COURSE DETAIL
// ─────────────────────────────────────────────────────────────────────────────
function CourseDetailScreen({ courseId, lang, onBack, savedIds, toggleSaved, enrolledIds, toggleEnrolled }) {
  const c = COURSES.find(x => x.id === courseId);
  if (!c) return null;
  const saved = savedIds.has(c.id);
  const enrolled = enrolledIds.has(c.id);
  const tint = TOPIC_TINT[c.topic];
  const ended = isPastCourse(c);

  // Three-dot menu state + scroll-aware chrome
  const [menuOpen, setMenuOpen] = uS(false);
  const [scrolled, setScrolled] = uS(false);
  const [registerDialog, setRegisterDialog] = uS(null);   // null | 'options' | 'external'
  const scrollRef = uR(null);
  const menuRef = uR(null);

  // Reset transient state when navigating between courses
  uE(() => {
    setRegisterDialog(null);
    setMenuOpen(false);
    // Scroll the detail layer back to the top when the user opens a
    // different course (e.g. via "related courses"). The overlay is a
    // separate scroll container from the underlying tab, so this only
    // affects detail-screen scroll — the tab's scrollTop is untouched.
    if (scrollRef.current) scrollRef.current.scrollTop = 0;
  }, [courseId]);

  uE(() => {
    const el = scrollRef.current;
    if (!el) return;
    const onScroll = () => setScrolled(el.scrollTop > 8);
    el.addEventListener('scroll', onScroll, { passive: true });
    return () => el.removeEventListener('scroll', onScroll);
  }, [courseId]);

  uE(() => {
    if (!menuOpen) return;
    const onDoc = (e) => { if (!menuRef.current?.contains(e.target)) setMenuOpen(false); };
    const onKey = (e) => { if (e.key === 'Escape') setMenuOpen(false); };
    document.addEventListener('mousedown', onDoc);
    document.addEventListener('keydown', onKey);
    return () => {
      document.removeEventListener('mousedown', onDoc);
      document.removeEventListener('keydown', onKey);
    };
  }, [menuOpen]);

  const providerUrl = c.raw?.url || null;
  const shareUrl = providerUrl || (typeof window !== 'undefined' ? window.location.href : '');

  const onCopyLink = async () => {
    setMenuOpen(false);
    try {
      await navigator.clipboard.writeText(shareUrl);
      // tiny inline confirm
      const t = document.createElement('div');
      t.className = 'vital-toast';
      t.textContent = lang === 'zh' ? '已複製連結' : 'Link copied';
      document.body.appendChild(t);
      setTimeout(() => t.remove(), 1400);
    } catch (e) {
      console.warn('clipboard failed', e);
    }
  };

  const onShare = async () => {
    setMenuOpen(false);
    if (navigator.share) {
      try {
        await navigator.share({ title: L(c.title, lang), text: L(c.title, lang), url: shareUrl });
      } catch (e) { /* user cancelled */ }
    } else {
      onCopyLink();
    }
  };

  const onOpenProvider = () => {
    setMenuOpen(false);
    if (providerUrl) window.open(providerUrl, '_blank', 'noopener,noreferrer');
  };

  const onReport = () => {
    setMenuOpen(false);
    alert(lang === 'zh' ? '已收到回報 — 多謝!' : "Thanks — we've received your report.");
  };

  return (
    <div className="detail-shell fade-in">
      {/* Floating top chrome — back + three-dot, always pinned above the scroll */}
      <div className={'detail-topbar' + (scrolled ? ' is-scrolled' : '')}>
        <button className="icon-btn" onClick={onBack} aria-label={lang === 'zh' ? '返回' : 'Back'}>
          <Icon name="chev-l" size={18} />
        </button>
        <div className="detail-topbar-title" aria-hidden={!scrolled}>
          {L(c.title, lang)}
        </div>
        <div className="detail-menu-wrap" ref={menuRef}>
          <button className="icon-btn" onClick={() => setMenuOpen(v => !v)}
            aria-label={lang === 'zh' ? '更多' : 'More'} aria-expanded={menuOpen}>
            <Icon name="dot-vert" size={18} />
          </button>
          {menuOpen && (
            <div className="detail-menu" role="menu">
              <button className="detail-menu-item" role="menuitem" onClick={onShare}>
                <Icon name="upload" size={16} stroke={1.7} />
                <span>{lang === 'zh' ? '分享課程' : 'Share course'}</span>
              </button>
              <button className="detail-menu-item" role="menuitem" onClick={onCopyLink}>
                <Icon name="globe" size={16} stroke={1.7} />
                <span>{lang === 'zh' ? '複製連結' : 'Copy link'}</span>
              </button>
              {providerUrl && (
                <button className="detail-menu-item" role="menuitem" onClick={onOpenProvider}>
                  <Icon name="arrow-r" size={16} stroke={1.7} />
                  <span>{lang === 'zh' ? '到主辦機構網站' : 'Open provider site'}</span>
                </button>
              )}
              <button className="detail-menu-item" role="menuitem" onClick={() => { setMenuOpen(false); toggleSaved(c.id); }}>
                <Icon name="bookmark" size={16} stroke={1.7} color={saved ? 'var(--primary)' : 'currentColor'} />
                <span>{saved ? (lang === 'zh' ? '取消收藏' : 'Remove from saved') : (lang === 'zh' ? '加入收藏' : 'Add to saved')}</span>
              </button>
              <div className="detail-menu-divider" />
              <button className="detail-menu-item danger" role="menuitem" onClick={onReport}>
                <Icon name="shield" size={16} stroke={1.7} />
                <span>{lang === 'zh' ? '回報資料錯誤' : 'Report incorrect info'}</span>
              </button>
            </div>
          )}
        </div>
      </div>

      {/* Hero — colored topic strip */}
      <div className="scroll" ref={scrollRef} style={{ paddingBottom: 88 }}>
        <div style={{
          background: tint.bg,
          padding: '64px 20px 24px',
          position: 'relative',
        }}>
          <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16 }}>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div className="topic-pill" style={{ background: 'rgba(255,255,255,0.6)', color: tint.fg, marginBottom: 14 }}>
                {L(c.topicLabel, lang)}
              </div>
              <h1 style={{
                margin: 0,
                fontFamily: 'var(--font-serif)',
                fontSize: 28, lineHeight: 1.12, letterSpacing: '-0.022em',
                fontWeight: 500, color: tint.fg,
              }}>
                {L(c.title, lang)}
              </h1>
              <div style={{ marginTop: 12, color: tint.fg, opacity: 0.75, fontSize: 13.5 }}>
                {L(c.provider, lang)}
              </div>
              {c.courseCode && (
                <div className="mono" style={{ marginTop: 6, color: tint.fg, opacity: 0.55, fontSize: 10.5, letterSpacing: '0.06em' }}>
                  {c.courseCode}
                </div>
              )}
            </div>
            <div style={{
              flexShrink: 0,
              background: 'rgba(255,255,255,0.85)',
              borderRadius: 14,
              padding: '14px 16px',
              textAlign: 'center',
              minWidth: 76,
            }}>
              <div className="serif" style={{ fontSize: 36, lineHeight: 0.9, letterSpacing: '-0.02em', color: tint.fg }}>
                {c.points}
              </div>
              <div className="mono" style={{ fontSize: 9, letterSpacing: '0.10em', textTransform: 'uppercase', color: tint.fg, opacity: 0.7, marginTop: 4 }}>
                CNE {L(STR.pts, lang)}
              </div>
            </div>
          </div>
          <div style={{ display: 'flex', gap: 6, marginTop: 16, flexWrap: 'wrap' }}>
            {(() => {
              const info = typeof enrolmentStatusInfo === 'function' ? enrolmentStatusInfo(c) : null;
              if (!info) return null;
              return (
                <span className={'cap-pill tone-' + info.tone} style={{ background: 'rgba(255,255,255,0.78)' }}>
                  {L(info.label, lang)}
                </span>
              );
            })()}
            {c.eventType && (
              <span className="chip" style={{ background: 'rgba(255,255,255,0.6)' }}>
                {L(c.eventType, lang)}
              </span>
            )}
            {c.online && <span className="chip" style={{ background: 'rgba(255,255,255,0.6)' }}><Icon name="globe" size={11} stroke={1.7} /> {L(STR.online, lang)}</span>}
            {c.cost === 0 && <span className="chip" style={{ background: 'rgba(255,255,255,0.6)' }}>{L(STR.free, lang)}</span>}
            <span className="chip" style={{ background: 'rgba(255,255,255,0.6)' }}>{L(c.formatLabel, lang)}</span>
          </div>
        </div>

        {/* Facts + About — 2-col at ≥1024 */}
        <div className="detail-cols" style={{ paddingTop: 18 }}>
          <div className="detail-body">
            <div style={{ padding: '0 20px', display: 'flex', flexDirection: 'column', gap: 22 }}>

              {/* About */}
              <div>
                <h3 className="h-section" style={{ margin: '0 0 10px', fontSize: 18 }}>{L(STR.about, lang)}</h3>
                <p style={{ margin: 0, fontSize: 14.5, lineHeight: 1.5, color: 'var(--fg-2)', textWrap: 'pretty' }}>
                  {L(c.blurb, lang)}
                </p>
              </div>

              {/* Audience */}
              {(c.raw?.target_en || c.raw?.target_zh) && (
                <div>
                  <h3 className="h-section" style={{ margin: '0 0 8px', fontSize: 16 }}>
                    {lang === 'zh' ? '對象' : 'Who this is for'}
                  </h3>
                  <p style={{ margin: 0, fontSize: 13.5, lineHeight: 1.5, color: 'var(--fg-2)' }}>
                    {lang === 'zh' ? (c.raw.target_zh || c.raw.target_en) : (c.raw.target_en || c.raw.target_zh)}
                  </p>
                </div>
              )}

              {/* Entry requirement */}
              {c.raw?.entry_requirement_en && (
                <div>
                  <h3 className="h-section" style={{ margin: '0 0 8px', fontSize: 16 }}>
                    {lang === 'zh' ? '報名要求' : 'Entry requirements'}
                  </h3>
                  <p style={{ margin: 0, fontSize: 13.5, lineHeight: 1.5, color: 'var(--fg-2)' }}>
                    {c.raw.entry_requirement_en}
                  </p>
                </div>
              )}

              {/* Speakers / instructor section removed by request — info is
                  still on the side facts pane and via the provider page. */}

              {/* Conference topics */}
              {(c.raw?.topics_en?.length || c.raw?.topics_zh?.length) && (
                <div>
                  <h3 className="h-section" style={{ margin: '0 0 10px', fontSize: 16 }}>
                    {lang === 'zh' ? '研討主題' : 'Topics covered'}
                  </h3>
                  <ul style={{ margin: 0, paddingLeft: 18, color: 'var(--fg-2)', fontSize: 13.5, lineHeight: 1.6 }}>
                    {(lang === 'zh' ? (c.raw.topics_zh || c.raw.topics_en) : (c.raw.topics_en || c.raw.topics_zh)).map((t, i) => (
                      <li key={i} style={{ marginBottom: 2 }}>{t}</li>
                    ))}
                  </ul>
                </div>
              )}

              {/* Conference theme */}
              {(c.raw?.theme_en || c.raw?.theme_zh) && (
                <div className="card" style={{ background: 'var(--primary-soft-2)', borderColor: 'var(--primary-soft)' }}>
                  <div className="eyebrow" style={{ color: 'var(--primary)', marginBottom: 6 }}>
                    {lang === 'zh' ? '會議主題' : 'Theme'}
                  </div>
                  <div style={{ fontFamily: 'var(--font-serif)', fontSize: 16, fontWeight: 500, color: 'var(--fg)', lineHeight: 1.3 }}>
                    {lang === 'zh' ? (c.raw.theme_zh || c.raw.theme_en) : (c.raw.theme_en || c.raw.theme_zh)}
                  </div>
                </div>
              )}

              {/* Organizer (when different from provider) */}
              {c.raw?.organizer && c.raw.organizer !== (c.raw.provider_full_en || c.raw.provider_full_zh) && (
                <div>
                  <div className="eyebrow" style={{ marginBottom: 6 }}>
                    {lang === 'zh' ? '主辦' : 'Organiser'}
                  </div>
                  <div style={{ fontSize: 13.5, color: 'var(--fg)' }}>{c.raw.organizer}</div>
                </div>
              )}

              {/* Objectives */}
              {(c.raw?.objectives_en || c.raw?.objectives_zh) && (
                <div>
                  <h3 className="h-section" style={{ margin: '0 0 8px', fontSize: 16 }}>
                    {lang === 'zh' ? '課程目標' : 'Objectives'}
                  </h3>
                  <p style={{ margin: 0, fontSize: 13.5, lineHeight: 1.55, color: 'var(--fg-2)', textWrap: 'pretty', whiteSpace: 'pre-line' }}>
                    {lang === 'zh' ? (c.raw.objectives_zh || c.raw.objectives_en) : (c.raw.objectives_en || c.raw.objectives_zh)}
                  </p>
                </div>
              )}

              {/* Syllabus */}
              {(c.raw?.syllabus_en || c.raw?.syllabus_zh) && (
                <div>
                  <h3 className="h-section" style={{ margin: '0 0 8px', fontSize: 16 }}>
                    {lang === 'zh' ? '課程大綱' : 'Syllabus'}
                  </h3>
                  <p style={{ margin: 0, fontSize: 13.5, lineHeight: 1.55, color: 'var(--fg-2)', textWrap: 'pretty', whiteSpace: 'pre-line' }}>
                    {lang === 'zh' ? (c.raw.syllabus_zh || c.raw.syllabus_en) : (c.raw.syllabus_en || c.raw.syllabus_zh)}
                  </p>
                </div>
              )}

              {/* Assessment */}
              {(c.raw?.assessment_required || c.raw?.assessment_format_en || c.raw?.assessment_passing_criteria_en || c.raw?.attendance_requirement) && (
                <div>
                  <h3 className="h-section" style={{ margin: '0 0 10px', fontSize: 16 }}>
                    {lang === 'zh' ? '評核' : 'Assessment'}
                  </h3>
                  <div className="card flush" style={{ padding: 0 }}>
                    {c.raw.assessment_required != null && (
                      <div className="fact-row" style={{ borderBottom: '0.5px solid var(--border)' }}>
                        <div className="k">{lang === 'zh' ? '需評核' : 'Required'}</div>
                        <div className="v">{c.raw.assessment_required ? (lang === 'zh' ? '是' : 'Yes') : (lang === 'zh' ? '否' : 'No')}</div>
                      </div>
                    )}
                    {c.raw.assessment_format_en && (
                      <div className="fact-row" style={{ borderBottom: '0.5px solid var(--border)' }}>
                        <div className="k">{lang === 'zh' ? '形式' : 'Format'}</div>
                        <div className="v">{lang === 'zh' ? (c.raw.assessment_format_zh || c.raw.assessment_format_en) : c.raw.assessment_format_en}</div>
                      </div>
                    )}
                    {c.raw.assessment_passing_criteria_en && (
                      <div className="fact-row" style={{ borderBottom: '0.5px solid var(--border)' }}>
                        <div className="k">{lang === 'zh' ? '合格準則' : 'Passing'}</div>
                        <div className="v">{c.raw.assessment_passing_criteria_en}</div>
                      </div>
                    )}
                    {c.raw.attendance_requirement && (
                      <div className="fact-row">
                        <div className="k">{lang === 'zh' ? '出席要求' : 'Attendance'}</div>
                        <div className="v">{c.raw.attendance_requirement}</div>
                      </div>
                    )}
                  </div>
                </div>
              )}

              {/* Certificate */}
              {(c.raw?.certificate_type_en || c.raw?.certificate_requirements_en) && (
                <div>
                  <h3 className="h-section" style={{ margin: '0 0 10px', fontSize: 16 }}>
                    {lang === 'zh' ? '證書' : 'Certificate'}
                  </h3>
                  <div className="card flush" style={{ padding: 0 }}>
                    {c.raw.certificate_type_en && (
                      <div className="fact-row" style={{ borderBottom: c.raw.certificate_requirements_en ? '0.5px solid var(--border)' : 'none' }}>
                        <div className="k">{lang === 'zh' ? '類型' : 'Type'}</div>
                        <div className="v">{lang === 'zh' ? (c.raw.certificate_type_zh || c.raw.certificate_type_en) : c.raw.certificate_type_en}</div>
                      </div>
                    )}
                    {c.raw.certificate_requirements_en && (
                      <div className="fact-row">
                        <div className="k">{lang === 'zh' ? '取得條件' : 'Requirements'}</div>
                        <div className="v">{c.raw.certificate_requirements_en}</div>
                      </div>
                    )}
                  </div>
                </div>
              )}

              {/* Notes */}
              {c.raw?.notes && (
                <div className="card" style={{ background: 'var(--accent-soft)', borderColor: 'var(--accent-soft)' }}>
                  <div className="eyebrow" style={{ color: 'var(--accent)', marginBottom: 6 }}>
                    {lang === 'zh' ? '備註' : 'Notes'}
                  </div>
                  <div style={{ fontSize: 13, color: 'var(--fg)', lineHeight: 1.5 }}>{c.raw.notes}</div>
                </div>
              )}

              {/* Provenance footer */}
              {(c.raw?.last_updated || c.raw?.first_seen) && (
                <div className="mono muted-2" style={{ fontSize: 10, letterSpacing: '0.10em', textTransform: 'uppercase', marginTop: 2 }}>
                  {c.raw.last_updated && <>{lang === 'zh' ? '最後更新' : 'Updated'} {c.raw.last_updated}</>}
                  {c.raw.first_seen && c.raw.first_seen !== c.raw.last_updated && (
                    <> · {lang === 'zh' ? '首次收錄' : 'First seen'} {c.raw.first_seen}</>
                  )}
                </div>
              )}

              {/* Change log (collapsible) — moved to the bottom; humanized timeline */}
              {Array.isArray(c.raw?.change_log) && c.raw.change_log.length > 0 && (
                <details className="changelog">
                  <summary className="changelog-summary">
                    <span className="changelog-summary-label">
                      <Icon name="clock" size={13} stroke={1.6} />
                      {lang === 'zh' ? '更新記錄' : 'Change history'}
                    </span>
                    <span className="changelog-summary-meta">
                      <span className="changelog-count">{c.raw.change_log.length}</span>
                      <Icon name="chev-d" size={14} stroke={1.7} />
                    </span>
                  </summary>
                  <ol className="changelog-list">
                    {c.raw.change_log.slice().reverse().map((entry, i) => {
                      const fields = Array.isArray(entry.fields_changed) ? entry.fields_changed : [];
                      return (
                        <li key={i} className="changelog-item">
                          <div className="changelog-bullet" aria-hidden="true" />
                          <div className="changelog-body">
                            <div className="changelog-date">{fmtDate(entry.date, lang)}</div>
                            {fields.length > 0 && (
                              <div className="changelog-changes">
                                {fields.map((f) => {
                                  const oldVal = entry.old_values?.[f];
                                  const newVal = entry.new_values?.[f];
                                  const hasDiff = oldVal != null && newVal != null;
                                  return (
                                    <div key={f} className="changelog-change">
                                      <div className="changelog-field">{humanField(f, lang)}</div>
                                      {hasDiff ? (
                                        <div className="changelog-diff">
                                          <div className="changelog-diff-row">
                                            <span className="changelog-diff-tag was">{lang === 'zh' ? '舊' : 'Was'}</span>
                                            <span className="changelog-diff-old">{String(oldVal)}</span>
                                          </div>
                                          <div className="changelog-diff-row">
                                            <span className="changelog-diff-tag now">{lang === 'zh' ? '新' : 'Now'}</span>
                                            <span className="changelog-diff-new">{String(newVal)}</span>
                                          </div>
                                        </div>
                                      ) : (
                                        <div className="changelog-diff-row">
                                          <span className="changelog-diff-tag now">{lang === 'zh' ? '更新' : 'Updated'}</span>
                                          <span className="changelog-diff-new">
                                            {newVal != null ? String(newVal) : (lang === 'zh' ? '已修改' : 'Changed')}
                                          </span>
                                        </div>
                                      )}
                                    </div>
                                  );
                                })}
                              </div>
                            )}
                          </div>
                        </li>
                      );
                    })}
                  </ol>
                </details>
              )}

            </div>
          </div>

          <div className="detail-side">
            <div className="detail-facts">

              <div className="fact-row">
                <div className="k">{L(STR.whenWhere, lang)}</div>
                <div className="v">
                  {fmtDate(c.date, lang)}
                  {c.endDate && c.endDate !== c.date && (
                    <span style={{ color: 'var(--fg-2)', fontWeight: 400 }}>
                      {' '}— {fmtDate(c.endDate, lang)}
                    </span>
                  )}
                  <div style={{ fontSize: 12.5, color: 'var(--fg-2)', fontWeight: 400, marginTop: 2 }}>
                    {c.time}
                    {(c.dayOfWeek?.en || c.dayOfWeek?.zh) && <> · {L(c.dayOfWeek, lang)}</>}
                  </div>
                  <div style={{ fontSize: 12.5, color: 'var(--fg-2)', fontWeight: 400, marginTop: 2 }}>
                    {L(c.venue, lang)}
                  </div>
                </div>
              </div>

              {(c.sessions > 1 || c.raw?.duration_text_en) && (
                <div className="fact-row">
                  <div className="k">{lang === 'zh' ? '節數' : 'Sessions'}</div>
                  <div className="v">
                    {c.sessions > 0 ? `${c.sessions} ${lang === 'zh' ? '節' : 'session' + (c.sessions === 1 ? '' : 's')}` : '—'}
                    {c.raw?.duration_text_en && (
                      <div style={{ fontSize: 12.5, color: 'var(--fg-2)', fontWeight: 400, marginTop: 2 }}>
                        {c.raw.duration_text_en}
                      </div>
                    )}
                  </div>
                </div>
              )}

              {c.raw?.application_deadline && (
                <div className="fact-row">
                  <div className="k">{lang === 'zh' ? '申請截止' : 'Apply by'}</div>
                  <div className="v">{fmtDate(c.raw.application_deadline, lang)}</div>
                </div>
              )}
              {c.raw?.registration_deadline && c.raw.registration_deadline !== c.raw.application_deadline && (
                <div className="fact-row">
                  <div className="k">{lang === 'zh' ? '報名截止' : 'Register by'}</div>
                  <div className="v">{fmtDate(c.raw.registration_deadline, lang)}</div>
                </div>
              )}
              {c.raw?.early_bird_registration_deadline && (
                <div className="fact-row">
                  <div className="k">{lang === 'zh' ? '早鳥截止' : 'Early bird by'}</div>
                  <div className="v" style={{ color: 'var(--accent)' }}>
                    {fmtDate(c.raw.early_bird_registration_deadline, lang)}
                  </div>
                </div>
              )}
              {c.raw?.abstract_submission_deadline && (
                <div className="fact-row">
                  <div className="k">{lang === 'zh' ? '摘要截止' : 'Abstract by'}</div>
                  <div className="v">{fmtDate(c.raw.abstract_submission_deadline, lang)}</div>
                </div>
              )}

              <div className="fact-row">
                <div className="k">{L(STR.format, lang)}</div>
                <div className="v">
                  {L(c.formatLabel, lang)}
                  {c.teachingMode && (
                    <div style={{ fontSize: 12.5, color: 'var(--fg-2)', fontWeight: 400, marginTop: 2 }}>
                      {L(c.teachingMode, lang)}
                    </div>
                  )}
                </div>
              </div>

              <div className="fact-row">
                <div className="k">{L(STR.language, lang)}</div>
                <div className="v">{L(c.language, lang)}</div>
              </div>

              <div className="fact-row">
                <div className="k">{L(STR.cost, lang)}</div>
                <div className="v">
                  {c.cost === 0 ? L(STR.free, lang) : `HKD $${c.cost.toLocaleString('en')}`}
                  {/* Member / non-member / early-bird breakdown */}
                  {(c.costMember !== null || c.costNonMember !== null || c.costEarlyBird !== null || c.raw?.fee_per_session_hkd) && (
                    <div style={{ marginTop: 6, display: 'flex', flexDirection: 'column', gap: 2 }}>
                      {c.costMember !== null && c.costNonMember !== null && c.costMember !== c.costNonMember && (
                        <>
                          <div className="fee-line">
                            <span>{lang === 'zh' ? '會員' : 'Member'}</span>
                            <span className="mono">${c.costMember.toLocaleString('en')}</span>
                          </div>
                          <div className="fee-line">
                            <span>{lang === 'zh' ? '非會員' : 'Non-member'}</span>
                            <span className="mono">${c.costNonMember.toLocaleString('en')}</span>
                          </div>
                        </>
                      )}
                      {c.costEarlyBird !== null && (
                        <div className="fee-line" style={{ color: 'var(--accent)' }}>
                          <span>{lang === 'zh' ? '早鳥' : 'Early bird'}</span>
                          <span className="mono">${c.costEarlyBird.toLocaleString('en')}</span>
                        </div>
                      )}
                      {c.raw?.fee_per_session_hkd && (
                        <div className="fee-line">
                          <span>{lang === 'zh' ? '每節' : 'Per session'}</span>
                          <span className="mono">${Number(c.raw.fee_per_session_hkd).toLocaleString('en')}</span>
                        </div>
                      )}
                    </div>
                  )}
                </div>
              </div>

              <div className="fact-row">
                <div className="k">{L(STR.points, lang)}</div>
                <div className="v">
                  {c.points ?? '—'} {L(STR.pts, lang)}
                  {c.raw?.cne_per_session && (
                    <div style={{ fontSize: 12.5, color: 'var(--fg-2)', fontWeight: 400, marginTop: 2 }}>
                      {c.raw.cne_per_session} {lang === 'zh' ? '分／節' : 'pts/session'}
                    </div>
                  )}
                  {c.raw?.cne_points_note && (
                    <div style={{ fontSize: 11.5, color: 'var(--accent)', fontWeight: 400, marginTop: 2, fontStyle: 'italic' }}>
                      {c.raw.cne_points_note}
                    </div>
                  )}
                  {c.raw?.cem_points > 0 && (
                    <div style={{ fontSize: 12.5, color: 'var(--fg-2)', fontWeight: 400, marginTop: 2 }}>
                      +{c.raw.cem_points} CEM
                    </div>
                  )}
                </div>
              </div>

              {/* Enrolment status — derived from enrolmentStatusInfo so the
                  badge always matches the CTA state. */}
              {(() => {
                const info = typeof enrolmentStatusInfo === 'function' ? enrolmentStatusInfo(c) : null;
                if (!info) return null;
                return (
                  <div className="fact-row">
                    <div className="k">{L(STR.filterEnrollment, lang)}</div>
                    <div className="v">
                      <span className={'cap-pill tone-' + info.tone}>{L(info.label, lang)}</span>
                    </div>
                  </div>
                );
              })()}

              {/* Capacity — three states: unlimited / full / numeric remaining */}
              {c.seats && (
                <div className="fact-row">
                  <div className="k">{lang === 'zh' ? '名額' : 'Capacity'}</div>
                  <div className="v">
                    {c.seats.kind === 'unlimited' ? (
                      <span style={{ display: 'inline-flex', gap: 6, alignItems: 'center' }}>
                        <Icon name="globe" size={13} stroke={1.7} color="var(--fg-3)" />
                        {L(STR.unlimited, lang)}
                      </span>
                    ) : c.seats.kind === 'full' ? (
                      <span style={{ color: 'var(--danger)', fontWeight: 500 }}>
                        {L(STR.full, lang)}
                        {Number.isFinite(c.seats.total) && c.seats.total > 0 && (
                          <span className="mono" style={{ marginLeft: 6, fontSize: 11, color: 'var(--fg-3)' }}>
                            0 / {c.seats.total}
                          </span>
                        )}
                      </span>
                    ) : (
                      <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                        <span style={{ color: c.seats.kind === 'limited' ? 'var(--accent)' : 'var(--fg)' }}>
                          <span className="mono" style={{ fontSize: 13 }}>
                            {c.seats.remaining} / {c.seats.total}
                          </span>
                          {' '}
                          <span style={{ fontSize: 11.5, color: 'var(--fg-2)' }}>
                            {L(STR.seatsRemaining, lang)}
                          </span>
                        </span>
                        {c.seats.total > 0 && (
                          <div className="progress-bar" style={{ flex: 1, maxWidth: 100 }}>
                            <div style={{
                              width: ((c.seats.total - c.seats.remaining) / c.seats.total * 100) + '%',
                              background: c.seats.kind === 'limited' ? 'var(--accent)' : 'var(--primary)'
                            }} />
                          </div>
                        )}
                      </div>
                    )}
                  </div>
                </div>
              )}

              {/* Registration deadline countdown — only when meaningful */}
              {c.registrationDeadline && (() => {
                const dLeft = typeof daysUntil === 'function' ? daysUntil(c.registrationDeadline) : null;
                return (
                  <div className="fact-row">
                    <div className="k">{L(STR.registerBy, lang)}</div>
                    <div className="v">
                      {fmtDate(c.registrationDeadline, lang)}
                      {dLeft != null && dLeft >= 0 && dLeft <= 30 && (
                        <span className={'deadline-pill' + (dLeft <= 3 ? ' urgent' : '')} style={{ marginLeft: 8 }}>
                          <Icon name="clock" size={11} stroke={1.7} />
                          {dLeft === 0
                            ? L(STR.closesToday, lang)
                            : `${dLeft} ${L(dLeft === 1 ? STR.dayLeft : STR.daysLeft, lang)}`}
                        </span>
                      )}
                    </div>
                  </div>
                );
              })()}

              {c.raw?.online_platform && !/zoom|online/i.test(L(c.venue, lang)) && (
                <div className="fact-row">
                  <div className="k">{lang === 'zh' ? '平台' : 'Platform'}</div>
                  <div className="v">{c.raw.online_platform}</div>
                </div>
              )}

              {(Array.isArray(c.raw?.target_audience_en) && c.raw.target_audience_en.length > 0) && (
                <div className="fact-row">
                  <div className="k">{lang === 'zh' ? '對象' : 'For'}</div>
                  <div className="v" style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                    {(lang === 'zh' && Array.isArray(c.raw.target_audience_zh) ? c.raw.target_audience_zh : c.raw.target_audience_en).map((t, i) => (
                      <span key={i} className="chip" style={{ background: 'var(--surface-2)', border: '0.5px solid var(--border)' }}>{t}</span>
                    ))}
                  </div>
                </div>
              )}

              {(Array.isArray(c.raw?.specialty_area_en) && c.raw.specialty_area_en.length > 0) && (
                <div className="fact-row">
                  <div className="k">{lang === 'zh' ? '專科' : 'Specialty'}</div>
                  <div className="v" style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                    {(lang === 'zh' && Array.isArray(c.raw.specialty_area_zh) ? c.raw.specialty_area_zh : c.raw.specialty_area_en).map((t, i) => (
                      <span key={i} className="chip" style={{ background: 'var(--surface-2)', border: '0.5px solid var(--border)' }}>{t}</span>
                    ))}
                  </div>
                </div>
              )}

              {c.raw?.materials_language_en && (
                <div className="fact-row">
                  <div className="k">{lang === 'zh' ? '教材語言' : 'Materials'}</div>
                  <div className="v">{lang === 'zh' ? (c.raw.materials_language_zh || c.raw.materials_language_en) : c.raw.materials_language_en}</div>
                </div>
              )}

              <div className="fact-row">
                <div className="k">{L(STR.provider, lang)}</div>
                <div className="v">
                  {L(c.provider, lang)}
                  <div style={{ fontSize: 11.5, color: 'var(--fg-2)', fontWeight: 400, marginTop: 2 }}>
                    {lang === 'zh' ? '已認可 · NCHK' : 'Accredited · NCHK'}
                    {c.raw?.provider_code && <> · {c.raw.provider_code}</>}
                  </div>
                </div>
              </div>

              {c.courseCode && (
                <div className="fact-row">
                  <div className="k">{lang === 'zh' ? '課程編號' : 'Course code'}</div>
                  <div className="v mono" style={{ fontSize: 12.5, letterSpacing: '0.04em' }}>{c.courseCode}</div>
                </div>
              )}

              {c.raw?.academy_en && (
                <div className="fact-row">
                  <div className="k">{lang === 'zh' ? '學院' : 'Academy'}</div>
                  <div className="v">{lang === 'zh' ? (c.raw.academy_zh || c.raw.academy_en) : c.raw.academy_en}</div>
                </div>
              )}
            </div>
          </div>
        </div>
      </div>

      {/* CTA */}
      <div className="cta-bar detail">
        <button className="btn btn-ghost" onClick={() => toggleSaved(c.id)} style={{ flex: 0 }}>
          <Icon name="bookmark" size={17} stroke={1.7} color={saved ? 'var(--primary)' : 'currentColor'} />
        </button>
        {ended ? (
          <button className="btn btn-ghost" style={{ flex: 1, opacity: 0.7, cursor: 'not-allowed' }} disabled>
            <Icon name="clock" size={16} stroke={1.8} />
            {lang === 'zh' ? '此課程已結束' : 'This course has ended'}
          </button>
        ) : (
          <button className={'btn ' + (enrolled ? 'btn-ghost' : 'btn-primary')} style={{ flex: 1 }}
                  onClick={() => {
                    // Already enrolled in our system — clicking should let them undo.
                    if (enrolled) toggleEnrolled(c.id);
                    else setRegisterDialog('options');
                  }}>
            {enrolled ? <><Icon name="check" size={16} stroke={2} /> {L(STR.enrolled, lang)}</> : L(STR.register, lang)}
          </button>
        )}
      </div>

      {/* Register dialog — two-step flow */}
      {registerDialog && (
        <RegisterDialog
          step={registerDialog}
          setStep={setRegisterDialog}
          lang={lang}
          course={c}
          onMarkEnrolled={() => {
            toggleEnrolled(c.id);
            setRegisterDialog(null);
            showVitalToast(lang === 'zh' ? '已加入「已報名」' : 'Marked as enrolled');
          }}
        />
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────
// Helper: ephemeral toast (called from CourseDetailScreen menu/dialog)
// ─────────────────────────────────────────────────────────────────
function showVitalToast(msg) {
  const t = document.createElement('div');
  t.className = 'vital-toast';
  t.textContent = msg;
  document.body.appendChild(t);
  setTimeout(() => t.remove(), 1600);
}

// ─────────────────────────────────────────────────────────────────
// Register dialog — two-step flow
//   1. options: pick "我已報名" or "前往報名"
//   2. external: confirm before leaving Vital
// ─────────────────────────────────────────────────────────────────
function RegisterDialog({ step, setStep, lang, course, onMarkEnrolled }) {
  const providerUrl = course.raw?.url || null;

  // Keyboard: Esc closes, Enter on external step opens the link
  uE(() => {
    const onKey = (e) => {
      if (e.key === 'Escape') setStep(null);
      if (e.key === 'Enter' && step === 'external' && providerUrl) {
        window.open(providerUrl, '_blank', 'noopener,noreferrer');
        setStep(null);
      }
    };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [step, providerUrl]);

  return (
    <div className="register-dialog-backdrop" onClick={(e) => { if (e.target === e.currentTarget) setStep(null); }}>
      <div className="register-dialog" role="dialog" aria-modal="true">
        <button className="register-dialog-close icon-btn" onClick={() => setStep(null)} aria-label={lang === 'zh' ? '關閉' : 'Close'}>
          <Icon name="close" size={16} />
        </button>

        {step === 'options' && (
          <>
            <div className="register-dialog-eyebrow mono">
              {lang === 'zh' ? '報名選項' : 'Registration'}
            </div>
            <h3 className="register-dialog-title">
              {L(course.title, lang)}
            </h3>
            <p className="register-dialog-sub">
              {lang === 'zh'
                ? '請告訴我們你的狀態:'
                : "Tell us where you are with this course:"}
            </p>

            <div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginTop: 14 }}>
              {/* Option 1: already registered (with provider) */}
              <M.button
                className="register-dialog-card"
                onClick={onMarkEnrolled}
                whileTap={{ scale: 0.985 }}
                whileHover={{ y: -1 }}
                transition={{ duration: 0.16 }}
              >
                <div className="rd-card-icon" style={{ background: 'var(--primary-soft)', color: 'var(--primary)' }}>
                  <Icon name="check-circ" size={20} stroke={1.7} />
                </div>
                <div className="rd-card-body">
                  <div className="rd-card-title">{lang === 'zh' ? '我已報名' : "I've already registered"}</div>
                  <div className="rd-card-sub">
                    {lang === 'zh' ? '把這個課程加入我的「已報名」' : 'Add this course to your enrolled list'}
                  </div>
                </div>
                <Icon name="chev-r" size={16} color="var(--fg-3)" stroke={1.7} />
              </M.button>

              {/* Option 2: go to provider */}
              <M.button
                className="register-dialog-card"
                onClick={() => setStep('external')}
                disabled={!providerUrl}
                whileTap={!providerUrl ? null : { scale: 0.985 }}
                whileHover={!providerUrl ? null : { y: -1 }}
                transition={{ duration: 0.16 }}
                style={!providerUrl ? { opacity: 0.55, cursor: 'not-allowed' } : undefined}>
                <div className="rd-card-icon" style={{ background: 'var(--accent-soft)', color: 'var(--accent)' }}>
                  <Icon name="arrow-r" size={20} stroke={1.8} />
                </div>
                <div className="rd-card-body">
                  <div className="rd-card-title">{lang === 'zh' ? '前往報名' : 'Go to registration page'}</div>
                  <div className="rd-card-sub">
                    {providerUrl
                      ? (lang === 'zh' ? '到主辦機構網站完成報名' : "Open the provider's registration page")
                      : (lang === 'zh' ? '本課程未提供報名連結' : 'No external link available')}
                  </div>
                </div>
                <Icon name="chev-r" size={16} color="var(--fg-3)" stroke={1.7} />
              </M.button>
            </div>
          </>
        )}

        {step === 'external' && (
          <>
            <div className="register-dialog-eyebrow mono" style={{ color: 'var(--accent)' }}>
              {lang === 'zh' ? '即將離開 Vital' : 'Leaving Vital'}
            </div>
            <h3 className="register-dialog-title">
              {lang === 'zh' ? '前往外部網站' : 'Opening external site'}
            </h3>
            <p className="register-dialog-sub">
              {lang === 'zh'
                ? '你將被帶到主辦機構的網站完成報名。Vital 對外部網站的內容並不負責。'
                : "You're about to leave Vital and go to the provider's site to finish registering. Vital isn't responsible for the content of external sites."}
            </p>

            <div className="register-dialog-url" title={providerUrl}>
              <Icon name="globe" size={14} stroke={1.7} color="var(--fg-3)" />
              <span>{providerUrl}</span>
            </div>

            <div className="register-dialog-actions">
              <button className="btn btn-ghost" onClick={() => setStep('options')}>
                {lang === 'zh' ? '取消' : 'Cancel'}
              </button>
              <button className="btn btn-primary" autoFocus
                onClick={() => {
                  window.open(providerUrl, '_blank', 'noopener,noreferrer');
                  setStep(null);
                }}>
                <Icon name="arrow-r" size={15} stroke={2} />
                {lang === 'zh' ? '繼續前往' : 'Continue'}
                <kbd className="register-dialog-kbd">↵</kbd>
              </button>
            </div>
          </>
        )}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// MY RECORD (timeline)
// ─────────────────────────────────────────────────────────────────────────────
function RecordScreen({ lang, setLang, onLog, onSignIn, session, profile, records = [], onUploadCert, onUpdateRecord, onDeleteRecord, onCreateRecord, onToggleEnrolled, enrolledIds, onOpenCourse, embedded = false }) {
  // ── Guest variant: intro + sample preview + sign-in CTA ───────────────
  // Goal: when a guest taps 記錄, they should understand WHAT this area
  // does for them before signing in — feature highlights + a faded preview
  // of the real timeline (using clearly-labelled sample data).
  if (!session) {
    const sampleRows = [
      { date: '2025-09-12',
        provider: lang === 'zh' ? '香港護士管理局' : 'Nursing Council HK',
        title:    lang === 'zh' ? '感染控制基礎課程'   : 'Infection Control Fundamentals',
        category: 'core',     points: 6, cert: true },
      { date: '2025-06-04',
        provider: lang === 'zh' ? '醫院管理局' : 'Hospital Authority',
        title:    lang === 'zh' ? '臨終護理溝通技巧' : 'End-of-Life Care Communication',
        category: 'elective', points: 6, cert: true },
      { date: '2025-03-21',
        provider: lang === 'zh' ? '香港大學護理學院' : 'HKU School of Nursing',
        title:    lang === 'zh' ? '糖尿病管理工作坊' : 'Diabetes Management Workshop',
        category: 'core',     points: 6, cert: false },
    ];
    const features = [
      { icon: 'book',
        title: lang === 'zh' ? '完成紀錄'     : 'Completion log',
        sub:   lang === 'zh' ? '按年份儲存所有完成的 CNE 課程,計算週期學分。'
                              : 'Year-by-year log of every CNE course you finish, with cycle points tallied.' },
      { icon: 'upload',
        title: lang === 'zh' ? '證書上載'     : 'Certificate upload',
        sub:   lang === 'zh' ? '直接附加 PDF 或相片,稽核時隨時取用。'
                              : 'Attach PDFs or photos to each record — ready for audit anytime.' },
      { icon: 'bell',
        title: lang === 'zh' ? '續期提醒'     : 'Renewal reminders',
        sub:   lang === 'zh' ? '在週期結束前提醒你還欠多少學分。'
                              : 'Get a nudge before your cycle deadline so you never miss renewal.' },
    ];

    return (
      <div className="scroll fade-in" style={{ paddingBottom: 32 }}>
        {!embedded && (
          <ScreenHeader
            title={L(STR.myRecord, lang)}
            eyebrow={lang === 'zh' ? '個人 CNE 紀錄' : 'Your CNE record'}
            actions={<LangPill lang={lang} setLang={setLang} />}
          />
        )}

        {/* Intro card */}
        <div style={{ padding: '8px 20px 0' }}>
          <div className="guest-card">
            <div className="gc-eyebrow">
              {lang === 'zh' ? '紀錄區' : 'Record'}
            </div>
            <h2 className="gc-title">
              {lang === 'zh' ? '把你的 CNE 進度集中在一個地方' : 'Your CNE journey, all in one place'}
            </h2>
            <p className="gc-sub">
              {lang === 'zh'
                ? '記錄已完成的課程、上傳證書、追蹤本週期所得學分,並設定續期提醒。所有資料均加密儲存於你的帳戶。'
                : 'Log completed courses, upload certificates, track cycle points, and set renewal reminders. Everything stays securely in your account.'}
            </p>
            <div className="gc-cta">
              <button className="btn btn-primary" onClick={onSignIn}>
                <Icon name="user" size={16} stroke={1.9} />
                {lang === 'zh' ? '登入' : 'Sign in'}
              </button>
              <button className="btn btn-ghost" onClick={onSignIn}>
                {lang === 'zh' ? '建立帳戶' : 'Create account'}
              </button>
            </div>
          </div>
        </div>

        {/* What this area helps with */}
        <div style={{ padding: '24px 20px 0' }}>
          <div className="eyebrow" style={{ marginBottom: 10 }}>
            {lang === 'zh' ? '此區域可協助你' : 'What this area helps with'}
          </div>
          <div style={{ display: 'grid', gap: 10 }}>
            {features.map((f, i) => (
              <div key={i} className="card" style={{ display: 'flex', gap: 12, alignItems: 'center', padding: '14px 16px' }}>
                <div style={{
                  width: 38, height: 38, borderRadius: 11,
                  background: 'color-mix(in oklab, var(--accent) 12%, transparent)',
                  color: 'var(--accent)',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  flexShrink: 0,
                }}>
                  <Icon name={f.icon} size={18} stroke={1.7} />
                </div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div className="h-row" style={{ fontSize: 14 }}>{f.title}</div>
                  <div className="muted" style={{ fontSize: 12.5, marginTop: 2, lineHeight: 1.45 }}>{f.sub}</div>
                </div>
              </div>
            ))}
          </div>
        </div>

        {/* Sample preview — clearly labelled, non-interactive */}
        <div style={{ padding: '26px 20px 0' }}>
          <div className="eyebrow" style={{ marginBottom: 10, display: 'flex', alignItems: 'center', gap: 8 }}>
            <span>{lang === 'zh' ? '預覽' : 'Preview'}</span>
            <span className="mono muted-2" style={{
              fontSize: 9.5, letterSpacing: '0.14em', textTransform: 'uppercase',
              padding: '2px 6px', border: '0.5px solid var(--border)', borderRadius: 4,
            }}>
              {lang === 'zh' ? '範例' : 'Sample'}
            </span>
          </div>

          <div
            aria-hidden="true"
            style={{
              pointerEvents: 'none',
              userSelect: 'none',
              opacity: 0.78,
              filter: 'saturate(0.92)',
            }}>
            {/* Year summary mock */}
            <div className="card" style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
              <div>
                <div className="mono muted-2" style={{ fontSize: 9.5, letterSpacing: '0.10em', textTransform: 'uppercase' }}>
                  2025 {L(STR.points, lang)}
                </div>
                <div className="serif" style={{ fontSize: 44, lineHeight: 0.95, letterSpacing: '-0.02em', marginTop: 4 }}>
                  18
                </div>
              </div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div className="muted" style={{ fontSize: 12, marginBottom: 6 }}>
                  3 {lang === 'zh' ? '項已完成' : 'completed'}
                </div>
                <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
                  <span className="chip" style={{ background: 'transparent', border: '0.5px solid var(--border)' }}>
                    <CatDot cat="core" size={6} />
                    {L(STR.core, lang)} <span className="mono" style={{ marginLeft: 3 }}>12</span>
                  </span>
                  <span className="chip" style={{ background: 'transparent', border: '0.5px solid var(--border)' }}>
                    <CatDot cat="elective" size={6} />
                    {L(STR.elective, lang)} <span className="mono" style={{ marginLeft: 3 }}>6</span>
                  </span>
                </div>
              </div>
              <div className="icon-btn" style={{ opacity: 0.5 }}>
                <Icon name="download" size={17} stroke={1.7} />
              </div>
            </div>

            {/* Sample timeline */}
            <div className="timeline" style={{ marginTop: 14 }}>
              {sampleRows.map((r, i) => (
                <div key={i} className="tl-item done" style={{ paddingBottom: i === sampleRows.length - 1 ? 0 : 18 }}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 10 }}>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div className="mono muted-2" style={{ fontSize: 10, letterSpacing: '0.10em', textTransform: 'uppercase', marginBottom: 4 }}>
                        {fmtShort(r.date, lang)} · {r.provider}
                      </div>
                      <div className="h-row" style={{ fontSize: 14.5 }}>{r.title}</div>
                      <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 6 }}>
                        <span className="chip">
                          <CatDot cat={r.category} size={6} />
                          {L(STR[r.category], lang)}
                        </span>
                        {r.cert ? (
                          <span className="chip" style={{ color: 'var(--success)' }}>
                            <Icon name="check" size={11} stroke={2} /> {L(STR.cert, lang)}
                          </span>
                        ) : (
                          <span className="chip" style={{
                            color: 'var(--accent)',
                            border: '0.5px dashed color-mix(in oklab, var(--accent) 40%, transparent)',
                            background: 'color-mix(in oklab, var(--accent) 8%, var(--surface))',
                          }}>
                            <Icon name="upload" size={11} stroke={2} /> {L(STR.uploadCert, lang)}
                          </span>
                        )}
                      </div>
                    </div>
                    <div style={{ textAlign: 'right', flexShrink: 0 }}>
                      <div className="serif" style={{ fontSize: 26, lineHeight: 0.95, letterSpacing: '-0.02em' }}>
                        +{r.points}
                      </div>
                      <div className="mono muted-2" style={{ fontSize: 9, letterSpacing: '0.10em', textTransform: 'uppercase' }}>
                        {L(STR.pts, lang)}
                      </div>
                    </div>
                  </div>
                </div>
              ))}
            </div>
          </div>
        </div>

        {/* Bottom CTA */}
        <div style={{ padding: '24px 20px 0', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}>
          <button className="btn btn-primary" onClick={onSignIn} style={{ minWidth: 220 }}>
            <Icon name="user" size={16} stroke={1.9} />
            {lang === 'zh' ? '登入以開始記錄' : 'Sign in to start your record'}
          </button>
          <div className="muted-2" style={{ fontSize: 11.5 }}>
            {lang === 'zh' ? '免費 · 數分鐘即可建立帳戶' : 'Free · takes under a minute'}
          </div>
        </div>
      </div>
    );
  }

  const u = userFromProfile(profile, records) || USER;
  const [year, setYear] = uS(new Date().getFullYear());
  const [filter, setFilter] = uS('all');
  // Sub-section within Record: 'active' (in-progress + pending) / 'history' (timeline)
  const [recordView, setRecordView] = uS('active');
  // Currently-edited record (opens RecordDetailSheet)
  const [editingRecord, setEditingRecord] = uS(null);
  // Per-record upload state: which record is currently uploading + last-err
  const [uploadingId, setUploadingId] = uS(null);
  const [uploadErr, setUploadErr] = uS(null);
  const fileInputRef = uR(null);
  const pendingRecordRef = uR(null);

  // File picker — uses a single hidden <input>, retargeted per record.
  const handleUploadClick = (recordId) => {
    if (!onUploadCert) {
      // No upload handler wired → tell the user
      setUploadErr({ id: recordId, msg: lang === 'zh' ? '上傳功能未啟用' : 'Upload not available' });
      return;
    }
    setUploadErr(null);
    pendingRecordRef.current = recordId;
    if (fileInputRef.current) fileInputRef.current.click();
  };

  const onFileChosen = async (e) => {
    const file = e.target.files?.[0];
    e.target.value = ''; // reset so the same file can be re-selected
    const recordId = pendingRecordRef.current;
    pendingRecordRef.current = null;
    if (!file || !recordId) return;
    // Soft client-side limit — Supabase storage allows much more but the
    // UI should fail fast if the user picks a 50MB scan.
    if (file.size > 8 * 1024 * 1024) {
      setUploadErr({ id: recordId, msg: lang === 'zh' ? '檔案太大（>8MB）' : 'File too large (>8MB)' });
      return;
    }
    setUploadingId(recordId);
    try {
      const res = await onUploadCert(recordId, file);
      if (res?.error) {
        setUploadErr({ id: recordId, msg: res.error.message || String(res.error) });
      } else {
        // success — tiny inline toast
        const t = document.createElement('div');
        t.className = 'vital-toast';
        t.textContent = lang === 'zh' ? '證書已上傳' : 'Certificate uploaded';
        document.body.appendChild(t);
        setTimeout(() => t.remove(), 1600);
      }
    } catch (e) {
      setUploadErr({ id: recordId, msg: e.message || String(e) });
    } finally {
      setUploadingId(null);
    }
  };

  // Records split by completion: anything with an end date in the future
  // is treated as "in progress" and surfaces in the Active view; everything
  // else (today or earlier) goes to the History timeline.
  const TODAY_ISO = (typeof todayLocalIso === 'function') ? todayLocalIso() : new Date().toISOString().slice(0, 10);
  // A record is "in progress" only when its end date is still in the future
  // AND the user hasn't explicitly marked it complete. The explicit-complete
  // flag (`COMPLETED:1` in the notes blob) lets a user finish a course early
  // — all sessions ticked — without us having to overwrite the real end date.
  const _isDone = (r) => (typeof isRecordCompleted === 'function') && isRecordCompleted(r);
  const inProgressRecords = uM(
    () => (records || []).filter(x => x.date && x.date > TODAY_ISO && !_isDone(x)),
    [records, TODAY_ISO]
  );

  const yearRecord = uM(() => {
    // Completed timeline = records whose end date has passed OR that the user
    // has explicitly marked complete (early-finish via 標記為已完成).
    let r = (records || []).filter(x => x.date && (x.date <= TODAY_ISO || _isDone(x)));
    r = r.filter(x => x.year === year);
    if (filter !== 'all') r = r.filter(x => x.category === filter);
    return r;
  }, [year, filter, records, TODAY_ISO]);

  const totalPts = yearRecord.reduce((s, r) => s + r.points, 0);

  return (
    <div className="scroll fade-in" style={{ paddingBottom: 24 }}>
      {!embedded && (
        <ScreenHeader
          title={L(STR.myRecord, lang)}
          eyebrow={lang === 'zh' ? `週期 2024–2026` : 'Cycle 2024–2026'}
          actions={<LangPill lang={lang} setLang={setLang} />}
        />
      )}

      {/* Hidden file input — single instance, retargeted per record via ref */}
      <input
        ref={fileInputRef}
        type="file"
        accept="image/*,application/pdf"
        style={{ display: 'none' }}
        onChange={onFileChosen}
      />

      {/* View switcher — "Active" (in-progress + pending verification) vs. "History" */}
      <div className="record-viewbar">
        <button
          className={'record-viewbar-tab' + (recordView === 'active' ? ' on' : '')}
          onClick={() => setRecordView('active')}>
          <Icon name="calendar" size={14} stroke={1.7} />
          <span>{lang === 'zh' ? '進行中' : 'In progress'}</span>
          {(() => {
            const n = (enrolledIds ? enrolledIds.size : 0) + inProgressRecords.length;
            return n > 0 ? <span className="record-viewbar-badge">{n}</span> : null;
          })()}
        </button>
        <button
          className={'record-viewbar-tab' + (recordView === 'history' ? ' on' : '')}
          onClick={() => setRecordView('history')}>
          <Icon name="book" size={14} stroke={1.7} />
          <span>{lang === 'zh' ? '已完成紀錄' : 'History'}</span>
          {(() => {
            const n = (records || []).filter(x => x.date && x.date <= TODAY_ISO).length;
            return n > 0 ? <span className="record-viewbar-badge">{n}</span> : null;
          })()}
        </button>
      </div>

      {recordView === 'active' ? (
        <div style={{ padding: '4px 20px 0', display: 'flex', flexDirection: 'column', gap: 22 }}>
          {/* Pending verification — sits at the top because it needs action */}
          {typeof PendingVerificationSection === 'function' && (
            <PendingBlock
              lang={lang} enrolledIds={enrolledIds} records={records}
              onUploadCert={onUploadCert} />
          )}

          {/* In-progress courses — catalog enrolments + manually-logged
              in-progress records, merged into a single list by
              InProgressSection. Tapping a card opens either the catalog
              detail action picker or the record editor directly. */}
          <div>
            <div className="eyebrow" style={{ marginBottom: 10 }}>
              {lang === 'zh' ? '正在參與的課程' : 'Active courses'}
            </div>
            {typeof InProgressSection === 'function' && (
              <InProgressSection
                lang={lang}
                enrolledIds={enrolledIds}
                inProgressRecords={inProgressRecords}
                records={records}
                onOpenCourse={onOpenCourse}
                onEditRecord={(record) => setEditingRecord(record)}
                onLogCourse={() => onLog && onLog()}
                onMarkComplete={async (course, resolvedSessions) => {
                  // Mark a course as finished WITHOUT overwriting the dates
                  // the user originally set on the course — the completed
                  // record's "course start / end" stay aligned with what
                  // the user planned, even if they finish early.
                  //
                  //   Manual record   → in-place update: add COMPLETED:1
                  //                     to the notes blob, keep date/notes
                  //                     start as-is.
                  //   Catalog course  → create a new record sized to the
                  //                     course's session range (first → last
                  //                     session iso, or course.date as the
                  //                     single-session fallback), then auto-
                  //                     unenrol so it stops showing in 進行中.
                  const todayIso = (typeof todayLocalIso === 'function') ? todayLocalIso() : new Date().toISOString().slice(0, 10);
                  const _parse  = (typeof parseNoteBlob === 'function') ? parseNoteBlob : null;
                  const _build  = (typeof buildNoteBlob === 'function') ? buildNoteBlob : null;

                  if (course.isManual && course._record) {
                    if (!onUpdateRecord) return;
                    const rec = course._record;
                    const parsed = _parse ? _parse(rec.notes) : { startDate: null, imagePaths: [], text: '' };
                    const newNotes = _build
                      ? _build({ ...parsed, completed: true })
                      : ((rec.notes ? rec.notes + '\n' : '') + 'COMPLETED:1');
                    await onUpdateRecord(rec.id, { notes: newNotes });
                    return;
                  }
                  // Catalog course — prefer the course's own start_date /
                  // end_date (the canonical schedule the provider published).
                  // Only fall back to resolved sessions when those originals
                  // are missing — user-edited session schedules shouldn't
                  // overwrite the course's official dates on the record.
                  if (!onCreateRecord) return;
                  const titleObj    = course.title    || { en: '', zh: '' };
                  const providerObj = course.provider || { en: '', zh: '' };
                  const sessList = Array.isArray(resolvedSessions) && resolvedSessions.length
                    ? resolvedSessions
                    : ((typeof parseSessions === 'function') ? parseSessions(course) : []);
                  const startIso = course.date    || sessList[0]?.iso                       || todayIso;
                  const endIso   = course.endDate || sessList[sessList.length - 1]?.iso     || course.date || todayIso;
                  const notesBlob = _build
                    ? _build({ startDate: startIso, completed: true })
                    : `START:${startIso}\nCOMPLETED:1`;
                  await onCreateRecord({
                    courseId:    course.id,
                    title:       L(titleObj, lang),
                    title_en:    titleObj.en || '',
                    title_zh:    titleObj.zh || null,
                    provider:    L(providerObj, lang),
                    provider_en: providerObj.en || '',
                    provider_zh: providerObj.zh || null,
                    date:        endIso,
                    points:      Number(course.points) || 0,
                    category:    course.category || 'elective',
                    notes:       notesBlob,
                  });
                  // Pull the course out of the enrolled list so it doesn't
                  // keep appearing in 進行中 alongside its new record.
                  if (onToggleEnrolled && enrolledIds && enrolledIds.has(course.id)) {
                    onToggleEnrolled(course.id);
                  }
                }} />
            )}
          </div>

          {/* Quick log CTA */}
          <button
            type="button"
            className="record-log-cta"
            onClick={() => onLog && onLog()}>
            <Icon name="plus" size={16} stroke={2} />
            <span>{lang === 'zh' ? '手動記錄課程' : 'Manually log a course'}</span>
          </button>
        </div>
      ) : (
      <F>
      {/* Year selector */}
      <div style={{ padding: '10px 20px 0', display: 'flex', gap: 6 }}>
        {[2026, 2025, 2024].map(y => (
          <button key={y}
            className={'filter-chip' + (year === y ? ' on' : '')}
            onClick={() => setYear(y)}>
            {y}
          </button>
        ))}
      </div>

      {/* Year summary card */}
      <div style={{ padding: '14px 20px 0' }}>
        <div className="card" style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
          <div>
            <div className="mono muted-2" style={{ fontSize: 9.5, letterSpacing: '0.10em', textTransform: 'uppercase' }}>
              {year} {L(STR.points, lang)}
            </div>
            <div className="serif" style={{ fontSize: 44, lineHeight: 0.95, letterSpacing: '-0.02em', marginTop: 4 }}>
              {totalPts}
            </div>
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div className="muted" style={{ fontSize: 12, marginBottom: 6 }}>
              {yearRecord.length} {lang === 'zh' ? '項已完成' : 'completed'}
            </div>
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
              {['core','elective','specialty'].map(cat => {
                const n = yearRecord.filter(r => r.category === cat).reduce((s,r) => s + r.points, 0);
                if (n === 0) return null;
                return (
                  <span key={cat} className="chip" style={{ background: 'transparent', border: '0.5px solid var(--border)' }}>
                    <CatDot cat={cat} size={6} />
                    {L(STR[cat], lang)} <span className="mono" style={{ marginLeft: 3 }}>{n}</span>
                  </span>
                );
              })}
            </div>
          </div>
          <button className="icon-btn" title={L(STR.exportPdf, lang)}>
            <Icon name="download" size={17} stroke={1.7} />
          </button>
        </div>
      </div>

      {/* Filter */}
      <div className="scroll-x" style={{ marginTop: 14 }}>
        {[
          ['all', STR.all], ['core', STR.core], ['elective', STR.elective], ['specialty', STR.specialty],
        ].map(([k, s]) => (
          <button key={k}
            className={'filter-chip' + (filter === k ? ' on' : '')}
            onClick={() => setFilter(k)}>
            {k !== 'all' && <CatDot cat={k} size={6} />}
            {L(s, lang)}
          </button>
        ))}
      </div>

      {/* Timeline */}
      <div style={{ padding: '10px 20px 0' }}>
        <div className="timeline">
          {yearRecord.map((r, i) => (
            <div key={r.id} className="tl-item done is-clickable"
                 role="button"
                 tabIndex={0}
                 onClick={() => setEditingRecord(r)}
                 onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setEditingRecord(r); } }}
                 style={{ paddingBottom: i === yearRecord.length - 1 ? 0 : 18, cursor: 'pointer' }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 10 }}>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div className="mono muted-2" style={{ fontSize: 10, letterSpacing: '0.10em', textTransform: 'uppercase', marginBottom: 4 }}>
                    {fmtShort(r.date, lang)} · {L(r.provider, lang)}
                  </div>
                  <div className="h-row" style={{ fontSize: 14.5 }}>
                    {L(r.title, lang)}
                  </div>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 6 }}>
                    <span className="chip">
                      <CatDot cat={r.category} size={6} />
                      {L(STR[r.category], lang)}
                    </span>
                    {r.certificate ? (
                      <span className="chip" style={{ color: 'var(--success)' }}>
                        <Icon name="check" size={11} stroke={2} /> {L(STR.cert, lang)}
                      </span>
                    ) : (
                      <button
                        type="button"
                        className="chip cert-upload-chip"
                        onClick={(e) => { e.stopPropagation(); handleUploadClick(r.id); }}
                        disabled={uploadingId === r.id}
                        style={{
                          color: 'var(--accent)',
                          appearance: 'none',
                          border: '0.5px dashed color-mix(in oklab, var(--accent) 40%, transparent)',
                          background: 'color-mix(in oklab, var(--accent) 8%, var(--surface))',
                          cursor: uploadingId === r.id ? 'wait' : 'pointer',
                        }}
                        aria-label={L(STR.uploadCert, lang)}>
                        {uploadingId === r.id ? (
                          <>
                            <span className="cert-upload-spinner" aria-hidden="true" />
                            {lang === 'zh' ? '上傳中…' : 'Uploading…'}
                          </>
                        ) : (
                          <>
                            <Icon name="upload" size={11} stroke={2} /> {L(STR.uploadCert, lang)}
                          </>
                        )}
                      </button>
                    )}
                  </div>
                  {uploadErr && uploadErr.id === r.id && (
                    <div style={{ fontSize: 11.5, color: 'var(--danger)', marginTop: 6 }}>
                      {uploadErr.msg}
                    </div>
                  )}
                </div>
                <div style={{ textAlign: 'right', flexShrink: 0 }}>
                  <div className="serif" style={{ fontSize: 26, lineHeight: 0.95, letterSpacing: '-0.02em' }}>
                    +{r.points}
                  </div>
                  <div className="mono muted-2" style={{ fontSize: 9, letterSpacing: '0.10em', textTransform: 'uppercase' }}>
                    {L(STR.pts, lang)}
                  </div>
                </div>
              </div>
            </div>
          ))}
          {yearRecord.length === 0 && (
            <div className="muted" style={{ textAlign: 'center', padding: 40 }}>
              {lang === 'zh' ? '此週期未有記錄' : 'No records this period'}
            </div>
          )}
        </div>
      </div>
      </F>
      )}

      {/* Record detail / edit sheet */}
      {editingRecord && typeof RecordDetailSheet === 'function' && (
        <RecordDetailSheet
          record={editingRecord}
          lang={lang}
          onClose={() => setEditingRecord(null)}
          onUploadCert={onUploadCert}
          onSave={onUpdateRecord}
          onDelete={onDeleteRecord} />
      )}
    </div>
  );
}

// PendingBlock — wraps PendingVerificationSection with a header that
// only renders when there's something pending (the section itself
// returns null in the empty case).
function PendingBlock({ lang, enrolledIds, records, onUploadCert }) {
  const today = new Date().toISOString().slice(0, 10);
  const pendingExists = uM(() => {
    if (!enrolledIds || !enrolledIds.size) return false;
    const all = (typeof COURSES !== 'undefined' ? COURSES : []);
    const loggedIds = new Set((records || []).map(r => r.courseId).filter(Boolean));
    return all.some(c => {
      if (!enrolledIds.has(c.id)) return false;
      if (loggedIds.has(c.id))    return false;
      const ses = (typeof parseSessions === 'function') ? parseSessions(c) : [];
      const last = ses[ses.length - 1];
      return last && last.iso < today;
    });
  }, [enrolledIds, records]);
  if (!pendingExists) return null;
  return (
    <div>
      <div className="eyebrow" style={{ marginBottom: 10, color: 'var(--accent)' }}>
        {lang === 'zh' ? '⚠ 待上傳證書' : 'Awaiting verification'}
      </div>
      <PendingVerificationSection
        lang={lang}
        enrolledIds={enrolledIds}
        records={records}
        onUploadCert={onUploadCert}
      />
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// SAVED (sheet)
// ─────────────────────────────────────────────────────────────────────────────
function SavedSheet({ lang, savedIds, onOpenCourse, onClose }) {
  const saved = COURSES.filter(c => savedIds.has(c.id));
  return (
    <Sheet open={true} onClose={onClose} title={L(STR.saved, lang)}>
      <div style={{ padding: '0 20px 20px', display: 'flex', flexDirection: 'column', gap: 10 }}>
        {saved.length === 0
          ? <div className="muted" style={{ textAlign: 'center', padding: 40 }}>
              {lang === 'zh' ? '尚未收藏任何課程' : 'You haven\u2019t saved any courses yet'}
            </div>
          : saved.map(c => (
              <CourseCard key={c.id} course={c} lang={lang} onClick={() => { onClose(); onOpenCourse(c.id); }} />
            ))}
      </div>
    </Sheet>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// REMINDERS sheet
// ─────────────────────────────────────────────────────────────────────────────
function RemindersSheet({ lang, reminders = [], onClose, onOpenCourse, onRouteReminder }) {
  const iconFor = (kind) => kind === 'enrolled' ? 'calendar' : kind === 'cert' ? 'upload' : 'clock';
  const list = reminders.length ? reminders : REMINDERS;
  const route = (rm) => {
    onClose && onClose();
    if (onRouteReminder) return onRouteReminder(rm);
    // Fallback if no router supplied: open course detail if we have it.
    if (rm.courseId && onOpenCourse) onOpenCourse(rm.courseId);
  };
  return (
    <Sheet open={true} onClose={onClose} title={lang === 'zh' ? '提醒' : 'Reminders'}>
      <div style={{ padding: '0 20px 20px', display: 'flex', flexDirection: 'column', gap: 10 }}>
        {list.length === 0 && (
          <div className="muted" style={{ textAlign: 'center', padding: 30 }}>
            {lang === 'zh' ? '暫無提醒' : 'No reminders yet'}
          </div>
        )}
        {list.map(rm => (
          <M.button key={rm.id}
            type="button"
            onClick={() => route(rm)}
            className="card reminder-row"
            whileTap={{ scale: 0.985 }}
            whileHover={{ x: 2 }}
            transition={{ duration: 0.16 }}
            style={{
              display: 'flex', alignItems: 'center', gap: 14,
              appearance: 'none', font: 'inherit', color: 'inherit',
              textAlign: 'left', cursor: 'pointer', width: '100%',
            }}>
            <div style={{
              width: 38, height: 38, borderRadius: 12,
              background: rm.kind === 'renewal' ? 'var(--accent-soft)' : 'var(--primary-soft)',
              color: rm.kind === 'renewal' ? 'var(--accent)' : 'var(--primary)',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              flexShrink: 0,
            }}>
              <Icon name={iconFor(rm.kind)} size={18} stroke={1.6} />
            </div>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div className="h-row" style={{ fontSize: 14 }}>{L(rm.title, lang)}</div>
              <div className="muted" style={{ fontSize: 12, marginTop: 2 }}>{L(rm.sub, lang)}</div>
            </div>
            <Icon name="chev-r" size={16} color="var(--fg-3)" stroke={1.6} />
          </M.button>
        ))}
      </div>
    </Sheet>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// <RemindersInlineList> — same row visual as RemindersSheet but inline,
// for placing inside Home under 為你推薦. Returns null when the list
// is empty so the host can decide whether to render a header.
// ─────────────────────────────────────────────────────────────────────────────
function RemindersInlineList({ lang, reminders = [], onRouteReminder, max = 4 }) {
  const iconFor = (kind) => kind === 'enrolled' ? 'calendar' : kind === 'cert' ? 'upload' : 'clock';
  if (!reminders || reminders.length === 0) return null;
  const list = reminders.slice(0, max);
  return (
    <div style={{ padding: '0 20px', display: 'flex', flexDirection: 'column', gap: 8 }}>
      {list.map(rm => (
        <M.button key={rm.id}
          type="button"
          onClick={() => onRouteReminder && onRouteReminder(rm)}
          className="card reminder-row"
          whileTap={{ scale: 0.985 }}
          whileHover={{ x: 2 }}
          transition={{ duration: 0.16 }}
          style={{
            display: 'flex', alignItems: 'center', gap: 12,
            padding: '12px 14px',
            appearance: 'none', font: 'inherit', color: 'inherit',
            textAlign: 'left', cursor: 'pointer', width: '100%',
          }}>
          <div style={{
            width: 34, height: 34, borderRadius: 11,
            background: rm.kind === 'renewal' ? 'var(--accent-soft)' : 'var(--primary-soft)',
            color: rm.kind === 'renewal' ? 'var(--accent)' : 'var(--primary)',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            flexShrink: 0,
          }}>
            <Icon name={iconFor(rm.kind)} size={16} stroke={1.7} />
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div className="h-row" style={{ fontSize: 13.5 }}>{L(rm.title, lang)}</div>
            <div className="muted" style={{ fontSize: 11.5, marginTop: 2 }}>{L(rm.sub, lang)}</div>
          </div>
          <Icon name="chev-r" size={14} color="var(--fg-3)" stroke={1.7} />
        </M.button>
      ))}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// LOG sheet (add a completed course)
// ─────────────────────────────────────────────────────────────────────────────
function LogSheet({ lang, onClose, onSave }) {
  const [step, setStep] = uS('chooser');
  const [form, setForm] = uS({
    title: '', title_en: '', title_zh: '',
    provider: '', provider_en: '', provider_zh: '',
    startDate: '', endDate: '', points: '', category: 'core',
    courseId: null,
  });
  const [certFile, setCertFile] = uS(null);
  const [busy, setBusy] = uS(false);
  const [search, setSearch] = uS('');
  const fileRef = uR(null);

  const fmtSize = (b) => b < 1024 ? `${b} B` : b < 1024*1024 ? `${(b/1024).toFixed(1)} KB` : `${(b/1024/1024).toFixed(1)} MB`;

  // Prefill the manual form from a catalog course and jump to manual step.
  const pickCourse = (c) => {
    const courseDate = c.date || new Date().toISOString().slice(0, 10);
    setForm({
      title:    L(c.title, lang),
      title_en: c.title?.en || '',
      title_zh: c.title?.zh || '',
      provider:    L(c.provider, lang),
      provider_en: c.provider?.en || '',
      provider_zh: c.provider?.zh || '',
      // For catalog courses we don't know a separate start date — use the
      // single course date for both fields; the user can adjust before saving.
      startDate: courseDate,
      endDate:   courseDate,
      points:   String(c.points ?? ''),
      category: c.category || 'elective',
      courseId: c.id,
    });
    setStep('manual');
  };

  const catalog = uM(() => {
    const q = search.trim().toLowerCase();
    // Log-from-catalog = past courses you attended → past + today's date.
    let r = (typeof COURSES !== 'undefined' ? COURSES : []).filter(c => isPastCourse(c));
    if (q) {
      r = r.filter(c =>
        L(c.title, lang).toLowerCase().includes(q) ||
        L(c.providerShort, lang).toLowerCase().includes(q) ||
        L(c.topicLabel, lang).toLowerCase().includes(q));
    }
    // Most recent past courses first.
    return r.sort((a, b) => (a.date || '0000') > (b.date || '0000') ? -1 : 1).slice(0, 12);
  }, [search, lang]);

  const handleSave = async () => {
    if (busy) return;
    setBusy(true);
    try {
      if (onSave) {
        // End date determines status:
        //   • endDate > today  → in-progress  (shows on 進行中)
        //   • endDate ≤ today  → completed    (shows on 已完成紀錄)
        // We persist the completion date via `date` (maps to completed_date
        // in the DB). startDate has no dedicated column so we tuck it into
        // the freeform notes field as `START:YYYY-MM-DD` — RecordScreen
        // can parse it back when displaying the entry.
        const noteParts = [];
        if (form.startDate) noteParts.push(`START:${form.startDate}`);
        await onSave({
          courseId:    form.courseId,
          title:       form.title,
          title_en:    form.title_en || form.title,
          title_zh:    form.title_zh || null,
          provider:    form.provider,
          provider_en: form.provider_en || form.provider,
          provider_zh: form.provider_zh || null,
          date:        form.endDate,
          startDate:   form.startDate,
          points:      Number(form.points) || 0,
          category:    form.category,
          notes:       noteParts.length ? noteParts.join('\n') : null,
          certificateFile: certFile,
        });
      }
      onClose();
    } finally {
      setBusy(false);
    }
  };

  return (
    <Sheet open={true} onClose={onClose} title={L(STR.logCourse, lang)}>
      <div style={{ padding: '0 20px 24px' }}>
        {step === 'chooser' && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            <button className="card" style={{ display: 'flex', gap: 14, alignItems: 'center', cursor: 'pointer', textAlign: 'left', font: 'inherit', color: 'inherit', border: '0.5px solid var(--border)' }}
                    onClick={() => setStep('catalog')}>
              <div style={{ width: 38, height: 38, borderRadius: 12, background: 'var(--primary-soft)', color: 'var(--primary)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                <Icon name="search" size={18} stroke={1.7} />
              </div>
              <div style={{ flex: 1 }}>
                <div className="h-row">{L(STR.fromCatalog, lang)}</div>
                <div className="muted" style={{ fontSize: 12, marginTop: 2 }}>
                  {lang === 'zh' ? '從已認可課程中選取' : 'Pick from accredited courses'}
                </div>
              </div>
              <Icon name="chev-r" size={16} color="var(--fg-3)" stroke={1.7} />
            </button>
            <button className="card" style={{ display: 'flex', gap: 14, alignItems: 'center', cursor: 'pointer', textAlign: 'left', font: 'inherit', color: 'inherit', border: '0.5px solid var(--border)' }}
                    onClick={() => setStep('manual')}>
              <div style={{ width: 38, height: 38, borderRadius: 12, background: 'var(--accent-soft)', color: 'var(--accent)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                <Icon name="plus" size={18} stroke={2} />
              </div>
              <div style={{ flex: 1 }}>
                <div className="h-row">{L(STR.manualEntry, lang)}</div>
                <div className="muted" style={{ fontSize: 12, marginTop: 2 }}>
                  {lang === 'zh' ? '輸入自訂課程詳情' : 'Enter your own course details'}
                </div>
              </div>
              <Icon name="chev-r" size={16} color="var(--fg-3)" stroke={1.7} />
            </button>
          </div>
        )}

        {step === 'catalog' && (
          <div>
            <button className="filter-chip" onClick={() => setStep('chooser')} style={{ marginBottom: 12 }}>
              <Icon name="chev-l" size={13} stroke={1.7} /> {L(STR.back, lang)}
            </button>
            <div className="search-bar" style={{ marginBottom: 12 }}>
              <Icon name="search" size={17} color="var(--fg-3)" stroke={1.6} />
              <input
                value={search}
                onChange={e => setSearch(e.target.value)}
                placeholder={L(STR.searchCourses, lang)}
                autoFocus
              />
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
              {catalog.length === 0 && (
                <div className="muted" style={{ textAlign: 'center', padding: '20px 0', fontSize: 13 }}>
                  {lang === 'zh' ? '找不到符合的課程' : 'No matching courses'}
                </div>
              )}
              {catalog.map(c => (
                <CourseCard key={c.id} course={c} lang={lang}
                  onClick={() => pickCourse(c)} compact />
              ))}
            </div>
            <div className="muted-2" style={{ fontSize: 11, textAlign: 'center', marginTop: 14 }}>
              {lang === 'zh' ? '選擇課程後可調整日期及上傳證書' : 'Pick a course — you can adjust the date and upload a certificate next'}
            </div>
          </div>
        )}

        {step === 'manual' && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
            <button className="filter-chip" onClick={() => setStep(form.courseId ? 'catalog' : 'chooser')}>
              <Icon name="chev-l" size={13} stroke={1.7} /> {L(STR.back, lang)}
            </button>
            {form.courseId && (
              <div className="card" style={{ display: 'flex', gap: 10, alignItems: 'center', background: 'var(--primary-soft-2)', borderColor: 'var(--primary-soft)' }}>
                <Icon name="check-circ" size={18} stroke={1.7} color="var(--primary)" />
                <div style={{ flex: 1, fontSize: 12.5, color: 'var(--fg-2)' }}>
                  {lang === 'zh' ? '從目錄選取的課程詳情已自動填入' : 'Course details prefilled from the catalog'}
                </div>
              </div>
            )}
            <div className="field">
              <div className="field-label">{L(STR.courseTitle, lang)}</div>
              <input className="field-input" value={form.title}
                     onChange={e => setForm({...form, title: e.target.value, title_en: e.target.value, title_zh: ''})}
                     placeholder={lang === 'zh' ? '例：壓瘡預防工作坊' : 'e.g. Pressure Injury Workshop'} />
            </div>
            <div className="field">
              <div className="field-label">{L(STR.provider, lang)}</div>
              <input className="field-input" value={form.provider}
                     onChange={e => setForm({...form, provider: e.target.value, provider_en: e.target.value, provider_zh: ''})}
                     placeholder={lang === 'zh' ? '主辦機構' : 'Organising body'} />
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
              <div className="field">
                <div className="field-label">{L(STR.startDate, lang)}</div>
                <input className="field-input" type="date" value={form.startDate}
                       max={form.endDate || undefined}
                       onChange={e => setForm({...form, startDate: e.target.value})} />
              </div>
              <div className="field">
                <div className="field-label">{L(STR.endDate, lang)}</div>
                <input className="field-input" type="date" value={form.endDate}
                       min={form.startDate || undefined}
                       onChange={e => setForm({...form, endDate: e.target.value})} />
              </div>
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
              <div className="field">
                <div className="field-label">{L(STR.points, lang)}</div>
                <input className="field-input" type="number" min="0" step="0.5"
                       value={form.points} onChange={e => setForm({...form, points: e.target.value})} placeholder="0" />
              </div>
              <div className="field" style={{ display: 'flex', flexDirection: 'column', justifyContent: 'flex-end' }}>
                {/* Status hint — mirrors how RecordScreen will route this entry */}
                {form.endDate && (
                  <div className="muted-2" style={{
                    fontSize: 11, padding: '8px 10px',
                    border: '0.5px solid var(--border)',
                    borderRadius: 8,
                    background: 'var(--surface)',
                    display: 'flex', alignItems: 'center', gap: 6,
                  }}>
                    {(() => {
                      const today = new Date().toISOString().slice(0, 10);
                      const inProgress = form.endDate > today;
                      return (
                        <>
                          <span style={{
                            width: 6, height: 6, borderRadius: '50%',
                            background: inProgress ? 'var(--accent)' : 'var(--success)',
                          }} />
                          {inProgress
                            ? (lang === 'zh' ? '將加入「進行中」' : 'Will appear in “In progress”')
                            : (lang === 'zh' ? '將加入「已完成」' : 'Will appear in “Completed”')}
                        </>
                      );
                    })()}
                  </div>
                )}
              </div>
            </div>
            <div className="field">
              <div className="field-label">{L(STR.category, lang)}</div>
              <div style={{ display: 'flex', gap: 8 }}>
                {['core','elective','specialty'].map(cat => (
                  <button key={cat}
                    className={'filter-chip' + (form.category === cat ? ' on' : '')}
                    style={{ flex: 1, justifyContent: 'center' }}
                    onClick={() => setForm({...form, category: cat})}>
                    <CatDot cat={cat} size={6} /> {L(STR[cat], lang)}
                  </button>
                ))}
              </div>
            </div>
            <div className="field">
              <div className="field-label">
                {L(STR.uploadCert, lang)} <span className="muted-2">({L(STR.optional, lang)})</span>
              </div>
              <input
                ref={fileRef}
                type="file"
                accept=".pdf,.png,.jpg,.jpeg,application/pdf,image/png,image/jpeg"
                style={{ display: 'none' }}
                onChange={(e) => {
                  const f = e.target.files?.[0];
                  if (!f) return;
                  if (f.size > 8 * 1024 * 1024) {
                    alert(lang === 'zh' ? '檔案不能超過 8 MB' : 'File must be 8 MB or smaller');
                    e.target.value = '';
                    return;
                  }
                  setCertFile(f);
                }}
              />
              <div style={{ display: 'flex', gap: 8 }}>
                <button type="button" className="btn btn-ghost"
                  style={{ flex: 1, justifyContent: 'flex-start', padding: 14, minWidth: 0 }}
                  onClick={() => fileRef.current?.click()}>
                  <Icon
                    name={certFile ? 'check-circ' : 'upload'}
                    size={16} stroke={1.7}
                    color={certFile ? 'var(--success)' : 'currentColor'} />
                  <span style={{ flex: 1, textAlign: 'left', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                    {certFile
                      ? `${certFile.name} · ${fmtSize(certFile.size)}`
                      : (lang === 'zh' ? '選擇 PDF / 圖片' : 'Choose PDF or image')}
                  </span>
                </button>
                {certFile && (
                  <button type="button" className="btn btn-ghost"
                    style={{ padding: '0 14px', flex: '0 0 auto' }}
                    onClick={() => { setCertFile(null); if (fileRef.current) fileRef.current.value = ''; }}
                    aria-label="Remove file">
                    <Icon name="close" size={16} stroke={1.7} />
                  </button>
                )}
              </div>
              <div className="muted-2" style={{ fontSize: 10.5, marginTop: 4, letterSpacing: '0.02em' }}>
                {lang === 'zh'
                  ? '可選 — PDF / JPG / PNG · 最大 8 MB'
                  : 'Optional — PDF / JPG / PNG · 8 MB max'}
              </div>
            </div>
            {/* LoadingButton centralises debounce: while `busy` is true
                onClick is short-circuited AND a spinner replaces the
                icon. The form-invalid case stays separately gated by
                disabled so it never spuriously shows "Saving…". */}
            <LoadingButton
              className="btn btn-primary btn-fill"
              style={{ marginTop: 6 }}
              isLoading={busy}
              disabled={!form.title || !form.startDate || !form.endDate || form.endDate < form.startDate || !form.points}
              loadingLabel={lang === 'zh' ? '儲存中…' : 'Saving…'}
              leftIcon={<Icon name="check" size={16} stroke={2.2} />}
              onClick={handleSave}>
              {L(STR.saveRecord, lang)}
            </LoadingButton>
          </div>
        )}
      </div>
    </Sheet>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// PROFILE
// ─────────────────────────────────────────────────────────────────────────────
function ProfileScreen({ lang, setLang, theme, setTheme, onOpenSheet, onSignIn, profile, session, records = [], onSignOut, initialSection, savedIds, enrolledIds, onOpenCourse, onUploadCert, onUpdateRecord, onDeleteRecord }) {
  const isGuest = !session;
  const u = !isGuest ? (userFromProfile(profile, records) || USER) : null;

  // Sub-section state (signed-in only) — Overview / Record / Saved are now
  // tabs inside Profile instead of separate top-level destinations.
  const [section, setSection] = uS(initialSection || 'overview');
  uE(() => { if (initialSection) setSection(initialSection); }, [initialSection]);

  // Reusable appearance + preferences blocks
  const appearanceBlock = (
    <ListSection title={L(STR.appearance, lang)} lang={lang}>
      <Row icon="moon" label={L(STR.darkMode, lang)}
           sub={theme === 'dark' ? (lang === 'zh' ? '已開啟' : 'On') : (theme === 'auto' ? (lang === 'zh' ? '跟隨系統' : 'Follow system') : (lang === 'zh' ? '已關閉' : 'Off'))}
           trailing={
             <button
               onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
               aria-label="Dark mode"
               style={{
                 appearance: 'none', border: 0, cursor: 'pointer',
                 width: 44, height: 26, borderRadius: 999,
                 background: theme === 'dark' ? 'var(--accent)' : 'var(--border-strong)',
                 position: 'relative', transition: 'background .2s',
               }}>
               <span style={{
                 position: 'absolute', top: 3, left: theme === 'dark' ? 21 : 3,
                 width: 20, height: 20, borderRadius: '50%',
                 background: '#fff', transition: 'left .2s',
                 boxShadow: '0 1px 3px rgba(0,0,0,.2)',
               }} />
             </button>
           } />
      <Row icon="leaf" label={lang === 'zh' ? '主題' : 'Theme'}
           trailing={
             <div style={{ display: 'flex', gap: 6 }}>
               {[
                 { id: 'light', label: L(STR.light, lang) },
                 { id: 'dark',  label: L(STR.dark, lang) },
                 { id: 'auto',  label: L(STR.auto, lang) },
               ].map(o => (
                 <button key={o.id}
                   className={'filter-chip' + (theme === o.id ? ' on' : '')}
                   style={{ padding: '4px 10px', fontSize: 11 }}
                   onClick={() => setTheme(o.id)}>{o.label}</button>
               ))}
             </div>
           } />
    </ListSection>
  );

  const preferencesBlock = (
    <ListSection title={L(STR.preferences, lang)} lang={lang}>
      <Row icon="globe" label={L(STR.appLanguage, lang)}
           trailing={<LangPill lang={lang} setLang={setLang} />} />
      <Row icon="bell" label={L(STR.notifications, lang)}
           value={isGuest ? (lang === 'zh' ? '登入後啟用' : 'Sign in to enable') : (lang === 'zh' ? '已開啟' : 'On')} />
    </ListSection>
  );

  // ── Guest variant ──────────────────────────────────────────────────────
  if (isGuest) {
    return (
      <div className="scroll fade-in" style={{ paddingBottom: 24 }}>
        <ScreenHeader title={L(STR.profile, lang)} actions={<LangPill lang={lang} setLang={setLang} />} />

        {/* Sign-in card */}
        <div style={{ padding: '8px 20px 0' }}>
          <div className="guest-card">
            <div className="gc-eyebrow">
              {lang === 'zh' ? '帳戶' : 'Account'}
            </div>
            <h2 className="gc-title">
              {lang === 'zh' ? '登入以追蹤你的 CNE 進度' : 'Sign in to track your CNE journey'}
            </h2>
            <p className="gc-sub">
              {lang === 'zh'
                ? '建立免費帳戶後即可儲存、報名及記錄完成的課程,並讓系統提示你距離續期的日子。'
                : 'Create a free account to save courses, log your record, upload certificates, and get renewal reminders.'}
            </p>
            <div className="gc-cta">
              <button className="btn btn-primary" onClick={onSignIn}>
                <Icon name="user" size={16} stroke={1.9} />
                {lang === 'zh' ? '登入' : 'Sign in'}
              </button>
              <button className="btn btn-ghost" onClick={onSignIn}>
                {lang === 'zh' ? '建立帳戶' : 'Create account'}
              </button>
            </div>
          </div>
        </div>

        {appearanceBlock}
        {preferencesBlock}

        <div style={{ padding: '24px 20px 0', textAlign: 'center' }}>
          <div className="mono muted-2" style={{ fontSize: 9.5, letterSpacing: '0.18em', textTransform: 'uppercase' }}>
            Vital
          </div>
          <div className="muted" style={{ fontSize: 11.5, marginTop: 4 }}>
            {lang === 'zh' ? 'CNE · 香港 · v1.0' : 'CNE companion · Hong Kong · v1.0'}
          </div>
        </div>
      </div>
    );
  }

  // ── Signed-in variant ──────────────────────────────────────────────────
  // Tab strip: Overview / 收藏 — Record is now its own top-level destination
  // (driven by the side-nav / TabBar), so it no longer lives in here.
  const sections = [
    { id: 'overview', label: lang === 'zh' ? '概覽' : 'Overview' },
    { id: 'saved',    label: lang === 'zh' ? '收藏' : 'Saved'    },
  ];

  // Defensive: if a stale initialSection of 'record' arrives (e.g. from
  // an older reminder click), fall back to Overview rather than rendering
  // an empty body.
  const safeSection = (section === 'record') ? 'overview' : section;

  return (
    <div className="scroll fade-in" style={{ paddingBottom: 24 }}>
      <ScreenHeader title={L(STR.profile, lang)} actions={<LangPill lang={lang} setLang={setLang} />} />

      {/* Sub-tab strip */}
      <div className="profile-tabs" role="tablist">
        {sections.map(s => (
          <button key={s.id}
            role="tab"
            aria-selected={section === s.id}
            className={'profile-tab' + (section === s.id ? ' on' : '')}
            onClick={() => setSection(s.id)}>
            {s.label}
          </button>
        ))}
      </div>

      {safeSection === 'overview' && <ProfileOverview u={u} session={session} lang={lang}
        appearanceBlock={appearanceBlock} preferencesBlock={preferencesBlock}
        onOpenSheet={onOpenSheet} onSignOut={onSignOut}
        onGoTo={setSection} />}

      {safeSection === 'saved' && (
        <ProfileSaved lang={lang} savedIds={savedIds} onOpenCourse={onOpenCourse} />
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────
// Profile · Overview sub-section (extracted so the tab branches stay tidy)
// ─────────────────────────────────────────────────────────────────
function ProfileOverview({ u, session, lang, appearanceBlock, preferencesBlock, onOpenSheet, onSignOut, onGoTo }) {
  return (
    <F>
      {/* Identity card */}
      <div style={{ padding: '8px 20px 0' }}>
        <div className="card" style={{ display: 'flex', gap: 14, alignItems: 'center' }}>
          <div className="avatar" style={{ width: 56, height: 56, fontSize: 18 }}>{u.initials}</div>
          <div style={{ flex: 1 }}>
            <div className="h-row" style={{ fontSize: 16 }}>{L(u.name, lang)}</div>
            <div className="muted" style={{ fontSize: 12.5, marginTop: 2 }}>{L(u.role, lang)}</div>
            <div className="mono muted-2" style={{ fontSize: 10.5, letterSpacing: '0.06em', marginTop: 4 }}>
              {u.license}{session?.user?.email ? ' · ' + session.user.email : ''}
            </div>
          </div>
        </div>
      </div>

      {/* Cycle stats */}
      <div style={{ padding: '12px 20px 0' }}>
        <div className="card" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
          <Stat
            k={lang === 'zh' ? '本週期已修' : 'Cycle earned'}
            v={u.earned}
            sub={`/ ${u.required} ${L(STR.pts, lang)}`}
          />
          <Stat
            k={L(STR.cycleEnds, lang)}
            v={'2026'}
            sub={'31 Dec'}
          />
        </div>
      </div>

      {/* Account list */}
      <ListSection title={L(STR.account, lang)} lang={lang}>
        <Row icon="user" label={L(u.hospital, lang)} sub={L(u.role, lang)} />
        <Row icon="cert" label={L(STR.licenseNo, lang)} value={u.license} />
        <Row icon="calendar" label={L(STR.renewBy, lang)} value={fmtDate(u.renewalDate, lang)} />
      </ListSection>

      {appearanceBlock}
      {preferencesBlock}

      <ListSection title={lang === 'zh' ? '紀錄' : 'Record'} lang={lang}>
        <Row icon="bookmark" label={L(STR.saved, lang)} onClick={() => onGoTo('saved')} />
        <Row icon="bell" label={lang === 'zh' ? '提醒' : 'Reminders'} onClick={() => onOpenSheet && onOpenSheet('reminders')} />
        <Row icon="download" label={L(STR.exportPdf, lang)} value={lang === 'zh' ? '本週期' : 'This cycle'} />
        <Row icon="shield" label={lang === 'zh' ? '私隱與資料' : 'Privacy & data'} />
      </ListSection>

      <div style={{ padding: '20px 20px 0' }}>
        <button className="btn btn-ghost btn-fill" style={{ color: 'var(--danger)' }} onClick={onSignOut}>
          {L(STR.signOut, lang)}
        </button>
      </div>
    </F>
  );
}

// ─────────────────────────────────────────────────────────────────
// Profile · Saved sub-section — embedded (non-sheet) version of SavedSheet
// ─────────────────────────────────────────────────────────────────
function ProfileSaved({ lang, savedIds, onOpenCourse }) {
  const saved = (typeof COURSES !== 'undefined' ? COURSES : [])
    .filter(c => savedIds && savedIds.has && savedIds.has(c.id));
  if (saved.length === 0) {
    return (
      <div className="vital-empty" style={{ marginTop: 8 }}>
        <div className="vital-empty-icon"><Icon name="bookmark" size={22} stroke={1.7} /></div>
        <div className="vital-empty-title">
          {lang === 'zh' ? '尚未收藏任何課程' : 'No saved courses yet'}
        </div>
        <div className="vital-empty-sub">
          {lang === 'zh' ? '在課程詳情頁面按書籤圖示即可收藏。' : 'Tap the bookmark icon on a course detail to save it here.'}
        </div>
      </div>
    );
  }
  return (
    <Stagger
      delay={24}
      initialDelay={20}
      style={{ padding: '12px 20px 0', display: 'flex', flexDirection: 'column', gap: 10 }}>
      {saved.map(c => (
        <CourseCard key={c.id} course={c} lang={lang} onClick={() => onOpenCourse && onOpenCourse(c.id)} />
      ))}
    </Stagger>
  );
}

function Stat({ k, v, sub }) {
  return (
    <div>
      <div className="mono muted-2" style={{ fontSize: 9.5, letterSpacing: '0.10em', textTransform: 'uppercase', marginBottom: 6 }}>
        {k}
      </div>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 6 }}>
        <span className="serif" style={{ fontSize: 28, lineHeight: 1, letterSpacing: '-0.02em' }}>{v}</span>
        <span className="muted-2" style={{ fontSize: 11 }}>{sub}</span>
      </div>
    </div>
  );
}

function ListSection({ title, children, lang }) {
  return (
    <div style={{ marginTop: 22 }}>
      <div className="eyebrow" style={{ padding: '0 20px 8px' }}>{title}</div>
      <div style={{ padding: '0 20px' }}>
        <div className="card flush" style={{ overflow: 'hidden' }}>
          {children}
        </div>
      </div>
    </div>
  );
}

function Row({ icon, label, sub, value, trailing, onClick }) {
  const isInteractive = !!onClick;
  const Tag = isInteractive ? 'button' : 'div';
  return (
    <Tag onClick={onClick} style={{
      display: 'flex', alignItems: 'center', gap: 12,
      padding: '14px 16px',
      width: '100%',
      borderBottom: '0.5px solid var(--border)',
      background: 'transparent',
      border: 0,
      borderBottomWidth: 0.5,
      borderBottomColor: 'var(--border)',
      borderBottomStyle: 'solid',
      font: 'inherit', color: 'inherit', textAlign: 'left',
      cursor: isInteractive ? 'pointer' : 'default',
    }}>
      <div style={{
        width: 32, height: 32, borderRadius: 9,
        background: 'var(--primary-soft-2)',
        color: 'var(--primary)',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        flexShrink: 0,
      }}>
        <Icon name={icon} size={16} stroke={1.7} />
      </div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div className="h-row" style={{ fontSize: 14 }}>{label}</div>
        {sub && <div className="muted" style={{ fontSize: 12, marginTop: 2 }}>{sub}</div>}
      </div>
      {value && <div className="muted" style={{ fontSize: 13 }}>{value}</div>}
      {trailing}
      {isInteractive && <Icon name="chev-r" size={15} color="var(--fg-3)" stroke={1.6} />}
    </Tag>
  );
}

Object.assign(window, {
  HomeScreen, DiscoverScreen, CourseDetailScreen, RecordScreen,
  SavedSheet, RemindersSheet, RemindersInlineList, LogSheet, ProfileScreen,
});
