// Shared — icons, Thai content, helpers

const mkIcon = (d) => ({ size = 18, stroke = 1.6, ...p } = {}) => (
  <svg viewBox="0 0 24 24" width={size} height={size} fill="none" stroke="currentColor"
       strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round" {...p}>{d}</svg>
);

const Ic = {
  search:  mkIcon(<><circle cx="11" cy="11" r="6.5"/><path d="M20 20l-4.6-4.6"/></>),
  user:    mkIcon(<><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0116 0"/></>),
  bell:    mkIcon(<><path d="M6 17l-1 2h14l-1-2V11a6 6 0 10-12 0v6z"/><path d="M10 19a2 2 0 004 0"/></>),
  cal:     mkIcon(<><rect x="3" y="4" width="18" height="17" rx="2"/><path d="M3 9h18M8 2v4M16 2v4"/></>),
  book:    mkIcon(<><path d="M4 4h14a2 2 0 012 2v14H6a2 2 0 01-2-2V4z"/><path d="M4 4v14a2 2 0 002 2"/></>),
  play:    mkIcon(<><path d="M7 4l13 8L7 20z"/></>),
  arrow:   mkIcon(<><path d="M5 12h14M13 6l6 6-6 6"/></>),
  arrowL:  mkIcon(<><path d="M19 12H5M11 6l-6 6 6 6"/></>),
  chev:    mkIcon(<><path d="M9 6l6 6-6 6"/></>),
  chevD:   mkIcon(<><path d="M6 9l6 6 6-6"/></>),
  close:   mkIcon(<><path d="M6 6l12 12M18 6L6 18"/></>),
  plus:    mkIcon(<><path d="M12 5v14M5 12h14"/></>),
  clock:   mkIcon(<><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></>),
  star:    mkIcon(<><path d="M12 3l2.7 5.6L21 9.4l-4.6 4.4L17.5 20 12 17l-5.5 3 1.1-6.2L3 9.4l6.3-.8z"/></>),
  check:   mkIcon(<><path d="M5 12l5 5L20 7"/></>),
  doc:     mkIcon(<><path d="M14 3H6a2 2 0 00-2 2v14a2 2 0 002 2h12a2 2 0 002-2V9z"/><path d="M14 3v6h6"/></>),
  download:mkIcon(<><path d="M12 4v12M7 11l5 5 5-5"/><path d="M5 20h14"/></>),
  globe:   mkIcon(<><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c2.5 2.7 4 6 4 9s-1.5 6.3-4 9c-2.5-2.7-4-6-4-9s1.5-6.3 4-9z"/></>),
  group:   mkIcon(<><circle cx="9" cy="8" r="3.5"/><path d="M3 21a6 6 0 0112 0"/><circle cx="17" cy="9" r="3"/><path d="M15 21a5 5 0 016-5"/></>),
  award:   mkIcon(<><circle cx="12" cy="9" r="6"/><path d="M9 13l-2 8 5-3 5 3-2-8"/></>),
  filter:  mkIcon(<><path d="M3 5h18M6 12h12M10 19h4"/></>),
  bookmk:  mkIcon(<><path d="M6 3h12v18l-6-4-6 4z"/></>),
  pause:   mkIcon(<><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></>),
  vol:     mkIcon(<><path d="M4 9v6h4l5 4V5L8 9H4z"/><path d="M16 8a5 5 0 010 8M18 5a8 8 0 010 14"/></>),
};

// ── Faculties / คณะ ────────────────────────────────────────────
const FACULTIES = [
  { code:'ENG',  name:'คณะวิศวกรรมศาสตร์',     color:'#3d5074' },
  { code:'SCI',  name:'คณะวิทยาศาสตร์',          color:'#6e9b3d' },
  { code:'LET',  name:'คณะอักษรศาสตร์',          color:'#b88532' },
  { code:'BUS',  name:'คณะบริหารธุรกิจ',         color:'#7d2a2a' },
  { code:'LAW',  name:'คณะนิติศาสตร์',           color:'#4a3d5c' },
  { code:'MED',  name:'คณะแพทยศาสตร์',           color:'#306060' },
  { code:'EDU',  name:'คณะศึกษาศาสตร์',          color:'#b06030' },
  { code:'ARC',  name:'คณะสถาปัตยกรรมศาสตร์',    color:'#5a5a5a' },
];

// ── Courses / รายวิชา ──────────────────────────────────────────
const COURSES = [
  { id:'cs101', code:'CS-101', name:'การเขียนโปรแกรมเชิงวัตถุ',
    fac:'ENG', level:'ปริญญาตรี ปี 1', credits:3, lang:'ไทย',
    inst:'รศ.ดร. ณัฐวุฒิ จันทร์ปราง', students:1842, rating:4.7, hours:42,
    lectures:24, weeks:14, term:'ภาคต้น 2569',
    desc:'พื้นฐานการคิดเชิงวัตถุ การออกแบบคลาส วงจรชีวิตของออบเจ็กต์ และการประยุกต์ใช้ Java เพื่อแก้ปัญหาในชีวิตจริง โดยใช้กรณีศึกษาจากระบบจริง.' },
  { id:'math101', code:'MATH-101', name:'แคลคูลัส 1',
    fac:'SCI', level:'ปริญญาตรี ปี 1', credits:3, lang:'ไทย',
    inst:'ผศ.ดร. ปิยะดา รัตนเดชา', students:2310, rating:4.4, hours:48,
    lectures:30, weeks:15, term:'ภาคต้น 2569',
    desc:'ลิมิตและความต่อเนื่อง อนุพันธ์ของฟังก์ชันค่าเดียว การประยุกต์อนุพันธ์ อินทิกรัลไม่จำกัดเขตและจำกัดเขต ทฤษฎีบทพื้นฐานของแคลคูลัส.' },
  { id:'th101', code:'TH-101', name:'ภาษาไทยเพื่อการสื่อสาร',
    fac:'LET', level:'พื้นฐานทั่วไป', credits:2, lang:'ไทย',
    inst:'อาจารย์ ดร. สุพรรณี วงศ์อนันต์', students:3104, rating:4.8, hours:32,
    lectures:16, weeks:15, term:'ภาคต้น 2569',
    desc:'ทักษะการฟัง พูด อ่าน เขียน เพื่อการสื่อสารในชีวิตประจำวันและในวิชาชีพ การวิเคราะห์สาร การเขียนเชิงสร้างสรรค์.' },
  { id:'hist201', code:'HIST-201', name:'ประวัติศาสตร์ไทยร่วมสมัย',
    fac:'LET', level:'ปริญญาตรี ปี 2', credits:3, lang:'ไทย',
    inst:'รศ. วิชญะ พัฒนเดชา', students:986, rating:4.6, hours:42,
    lectures:21, weeks:14, term:'ภาคต้น 2569',
    desc:'พัฒนาการสังคม การเมือง เศรษฐกิจ และวัฒนธรรมของประเทศไทยตั้งแต่หลังการเปลี่ยนแปลงการปกครอง พ.ศ. 2475 ถึงปัจจุบัน.' },
  { id:'econ101', code:'ECON-101', name:'เศรษฐศาสตร์จุลภาคเบื้องต้น',
    fac:'BUS', level:'ปริญญาตรี ปี 1', credits:3, lang:'ไทย',
    inst:'ผศ. ภัทรพล สิรินิติพงศ์', students:1520, rating:4.3, hours:42,
    lectures:21, weeks:14, term:'ภาคต้น 2569',
    desc:'อุปสงค์ อุปทาน ดุลยภาพตลาด พฤติกรรมผู้บริโภคและผู้ผลิต ตลาดในรูปแบบต่าง ๆ และการล้มเหลวของตลาด.' },
  { id:'cs301', code:'CS-301', name:'ปัญญาประดิษฐ์เบื้องต้น',
    fac:'ENG', level:'ปริญญาตรี ปี 3', credits:3, lang:'ไทย / EN',
    inst:'รศ.ดร. ธีรพงศ์ ภัทรกุลธาดา', students:642, rating:4.9, hours:45,
    lectures:24, weeks:15, term:'ภาคต้น 2569',
    desc:'การค้นหาในปริภูมิสถานะ ปัญหาตัวแปรเชิงเงื่อนไข การวางแผน ความรู้และการให้เหตุผล การเรียนรู้ของเครื่องเบื้องต้น พร้อมกรณีศึกษาประยุกต์.' },
  { id:'phil101', code:'PHIL-101', name:'ปรัชญาเบื้องต้น',
    fac:'LET', level:'พื้นฐานทั่วไป', credits:3, lang:'ไทย',
    inst:'อาจารย์ ดร. นที กุลวิวัฒน์', students:752, rating:4.5, hours:36,
    lectures:18, weeks:14, term:'ภาคต้น 2569',
    desc:'สำรวจประเด็นพื้นฐานของปรัชญาตะวันตกและตะวันออก ญาณวิทยา จริยศาสตร์ และอภิปรัชญา ผ่านการอ่านบทคัดสรรและการอภิปรายเชิงวิเคราะห์.' },
  { id:'chem201', code:'CHEM-201', name:'เคมีอินทรีย์ 1',
    fac:'SCI', level:'ปริญญาตรี ปี 2', credits:4, lang:'ไทย',
    inst:'รศ.ดร. กฤษณา อมรเศรษฐกุล', students:534, rating:4.2, hours:60,
    lectures:30, weeks:15, term:'ภาคต้น 2569',
    desc:'โครงสร้างและสมบัติของสารประกอบอินทรีย์ กลไกของปฏิกิริยา การสังเคราะห์เบื้องต้น และการประยุกต์ในเภสัชภัณฑ์และวัสดุ.' },
  { id:'law101', code:'LAW-101', name:'หลักกฎหมายทั่วไป',
    fac:'LAW', level:'ปริญญาตรี ปี 1', credits:3, lang:'ไทย',
    inst:'รศ. อภิชาติ พรหมเมศวร', students:1208, rating:4.4, hours:42,
    lectures:21, weeks:14, term:'ภาคต้น 2569',
    desc:'ระบบกฎหมาย ที่มาและบ่อเกิดของกฎหมาย การตีความ และโครงสร้างกฎหมายไทย พร้อมตัวอย่างคดีจริงเพื่อการวิเคราะห์.' },
  { id:'med201', code:'MED-201', name:'กายวิภาคศาสตร์มนุษย์',
    fac:'MED', level:'ปริญญาตรี ปี 2', credits:4, lang:'ไทย',
    inst:'ผศ.พญ. ณิชาพัฒน์ สิริธนากร', students:284, rating:4.8, hours:72,
    lectures:36, weeks:16, term:'ภาคต้น 2569',
    desc:'โครงสร้างของร่างกายมนุษย์เชิงระบบ ระบบประสาท ระบบไหลเวียนเลือด ระบบกระดูกและกล้ามเนื้อ การประยุกต์ทางคลินิก.' },
  { id:'edu201', code:'EDU-201', name:'จิตวิทยาการศึกษา',
    fac:'EDU', level:'ปริญญาตรี ปี 2', credits:3, lang:'ไทย',
    inst:'ผศ.ดร. รัชนีกร เสาวภาคย์', students:612, rating:4.6, hours:42,
    lectures:21, weeks:14, term:'ภาคต้น 2569',
    desc:'ทฤษฎีพัฒนาการ ทฤษฎีการเรียนรู้ การสร้างแรงจูงใจ และการประยุกต์ในห้องเรียน.' },
  { id:'arc101', code:'ARC-101', name:'การออกแบบสถาปัตยกรรม 1',
    fac:'ARC', level:'ปริญญาตรี ปี 1', credits:4, lang:'ไทย',
    inst:'อาจารย์ ธนภพ วงศ์อมรา', students:198, rating:4.7, hours:80,
    lectures:20, weeks:16, term:'ภาคต้น 2569',
    desc:'หลักการพื้นฐานของการออกแบบ องค์ประกอบของพื้นที่ การร่าง การสร้างหุ่นจำลอง และการนำเสนอแนวคิดการออกแบบ.' },
];

// ── Lectures inside a course ───────────────────────────────────
const LECTURES = [
  { id:'L1', wk:1, name:'แนวคิดและประวัติของการเขียนโปรแกรมเชิงวัตถุ', dur:'42:18', kind:'video', done:true },
  { id:'L2', wk:1, name:'การติดตั้ง JDK และเครื่องมือพัฒนา',         dur:'18:42', kind:'video', done:true },
  { id:'L3', wk:2, name:'คลาส วัตถุ และตัวสร้าง (Constructors)',     dur:'56:04', kind:'video', done:true },
  { id:'L4', wk:2, name:'การมองเห็น (Access modifiers) และห่อหุ้ม', dur:'34:21', kind:'video', done:false, current:true },
  { id:'L5', wk:3, name:'การสืบทอด (Inheritance) เบื้องต้น',          dur:'48:10', kind:'video', done:false },
  { id:'L6', wk:3, name:'การพหุสัณฐาน (Polymorphism)',                dur:'38:56', kind:'video', done:false },
  { id:'L7', wk:4, name:'แบบฝึกหัด: ออกแบบคลาส Bank Account',          dur:'—',    kind:'quiz',  done:false },
  { id:'L8', wk:4, name:'อ่านเพิ่มเติม: บทที่ 3-4 ของ Bloch',          dur:'—',    kind:'read',  done:false },
  { id:'L9', wk:5, name:'อินเตอร์เฟซและคลาสนามธรรม (Abstract)',        dur:'52:30', kind:'video', done:false },
  { id:'L10',wk:5, name:'การจัดการข้อผิดพลาด (Exception handling)',    dur:'40:14', kind:'video', done:false },
  { id:'L11',wk:6, name:'การทดสอบหน่วย (Unit testing) ด้วย JUnit',     dur:'46:22', kind:'video', done:false },
  { id:'L12',wk:6, name:'การส่งงานครั้งที่ 1: โปรเจ็กต์ห้องสมุดดิจิทัล', dur:'—',  kind:'assn',  done:false },
];

// ── Announcements / ข่าวประกาศ ─────────────────────────────────
const NEWS = [
  { id:'n1',  date:'13', month:'พ.ค.', cat:'ทุนการศึกษา', title:'เปิดรับสมัครทุนพัฒนานักวิจัย ประจำปีการศึกษา 2569',
    dek:'นักศึกษาระดับปริญญาโทและเอก สามารถยื่นขอรับทุนได้ตั้งแต่บัดนี้จนถึง 30 มิถุนายน รายละเอียดและเอกสารดูได้ที่หน้าทุนการศึกษา.' },
  { id:'n2',  date:'10', month:'พ.ค.', cat:'การลงทะเบียน', title:'กำหนดการลงทะเบียนเรียน ภาคต้น ปีการศึกษา 2569',
    dek:'นักศึกษาทุกชั้นปีลงทะเบียนเรียนผ่านระบบ ระหว่างวันที่ 20 พฤษภาคม - 5 มิถุนายน 2569 ตรวจสอบรายวิชาในตารางที่ปรึกษา.' },
  { id:'n3',  date:'08', month:'พ.ค.', cat:'กิจกรรม',       title:'งานเสวนา “เทคโนโลยีและสังคมไทยในทศวรรษหน้า”',
    dek:'พบกับ 5 วิทยากรชั้นแนวหน้าของวงการเทคโนโลยี วันเสาร์ที่ 25 พฤษภาคม ณ หอประชุมใหญ่ เริ่มลงทะเบียน 13:00 น.' },
  { id:'n4',  date:'06', month:'พ.ค.', cat:'ระบบ',          title:'ปรับปรุงระบบ e-learning ในวันอาทิตย์ 19 พ.ค.',
    dek:'ระบบจะปิดให้บริการเพื่อปรับปรุงและบำรุงรักษา ระหว่างเวลา 02:00 - 06:00 น. ขออภัยในความไม่สะดวก.' },
  { id:'n5',  date:'02', month:'พ.ค.', cat:'รางวัล',        title:'คณะวิศวกรรมศาสตร์คว้ารางวัลชนะเลิศ Robocon ระดับชาติ',
    dek:'ทีมหุ่นยนต์ม.อ.ภ. คว้าถ้วยพระราชทาน พร้อมเป็นตัวแทนประเทศไทยไปแข่งขันระดับเอเชีย-แปซิฟิก.' },
];

// ── Helpers ────────────────────────────────────────────────────
function thNum(n) {
  return Number(n).toLocaleString('th-TH');
}

function facColor(code) {
  return (FACULTIES.find(f => f.code === code) || {}).color || 'var(--ink-3)';
}
function facName(code) {
  return (FACULTIES.find(f => f.code === code) || {}).name || code;
}

function escape(s='') {
  return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');
}

function Placeholder({ label, num, variant = 'wide', dark = false, style, seed, src, video, children }) {
  const isReal = !!(src || seed || video);
  const imgUrl = src || (seed ? `/assets/img/picsum/${seed}_800x500.jpg` : null);
  return (
    <div className={`ph ${variant} ${dark?'dark':''} ${isReal?'is-real':''}`} data-label={isReal?'':label} data-num={isReal?'':num} style={style}>
      {video && (
        <video src={video} poster={src || (seed?`/assets/img/picsum/${seed}_720x400.jpg`:undefined)}
          controls playsInline preload="metadata"
          style={{width:'100%', height:'100%', objectFit:'cover', display:'block', background:'#000'}}/>
      )}
      {!video && imgUrl && (
        <img src={imgUrl} alt={label||''} loading="lazy"/>
      )}
      {children}
    </div>
  );
}

function CourseCard({ c, onOpen }) {
  return (
    <div className="cc" onClick={()=>onOpen(c.id)}>
      <div style={{position:'relative'}}>
        <Placeholder seed={c.id} label={c.code} variant="wide"/>
        <span className="tag" style={{position:'absolute', top:10, left:10, background:facColor(c.fac), color:'#fff', borderColor:'transparent', boxShadow:'0 4px 12px rgba(0,0,0,.3)'}}>
          {facName(c.fac)}
        </span>
      </div>
      <div className="body">
        <div className="code">{c.code} · {c.term}</div>
        <h3>{c.name}</h3>
        <div className="row" style={{gap:6, color:'var(--ink-3)', fontSize:12.5}}>
          <span>{c.inst}</span>
        </div>
        <div className="meta">
          <span className="row" style={{gap:5}}><Ic.star size={13} stroke={1.5}/> {c.rating}</span>
          <span className="dot"/>
          <span className="row" style={{gap:5}}><Ic.group size={13} stroke={1.5}/> {thNum(c.students)} คน</span>
          <span className="dot"/>
          <span className="row" style={{gap:5}}><Ic.clock size={13} stroke={1.5}/> {c.hours} ชม.</span>
        </div>
      </div>
    </div>
  );
}

// ── API helpers ─────────────────────────────────────────────────
async function apiReq(method, url, body) {
  const opts = { method, credentials: 'same-origin', headers: {} };
  if (body !== undefined) {
    opts.headers['Content-Type'] = 'application/json';
    opts.body = JSON.stringify(body);
  }
  const res = await fetch(url, opts);
  const ct = res.headers.get('content-type') || '';
  const data = ct.includes('application/json') ? await res.json() : await res.text();
  if (!res.ok) throw Object.assign(new Error('api ' + res.status), { status: res.status, data });
  return data;
}
const api = {
  get:    (u)    => apiReq('GET', u),
  post:   (u, b) => apiReq('POST', u, b ?? {}),
  del:    (u)    => apiReq('DELETE', u),
  me:           () => apiReq('GET', '/api/auth/me').catch(() => null),
  login:        (studentId, password) => apiReq('POST', '/api/auth/login', { studentId, password }),
  register:     (payload) => apiReq('POST', '/api/auth/register', payload),
  logout:       () => apiReq('POST', '/api/auth/logout'),
  faculties:    () => apiReq('GET', '/api/faculties'),
  courses:      (params = {}) => {
    const qs = new URLSearchParams(Object.entries(params).filter(([, v]) => v != null && v !== '')).toString();
    return apiReq('GET', '/api/courses' + (qs ? '?' + qs : ''));
  },
  course:       (slug) => apiReq('GET', '/api/courses/' + encodeURIComponent(slug)),
  lectures:     (slug) => apiReq('GET', '/api/courses/' + encodeURIComponent(slug) + '/lectures'),
  news:         ()     => apiReq('GET', '/api/news'),
  enrollments:  ()     => apiReq('GET', '/api/enrollments'),
  enroll:       (slug) => apiReq('POST', '/api/enrollments', { course: slug }),
  unenroll:     (slug) => apiReq('DELETE', '/api/enrollments/' + encodeURIComponent(slug)),
  progress:     (course)        => apiReq('GET', '/api/progress' + (course ? '?course=' + encodeURIComponent(course) : '')),
  setProgress:  (course, lecture, done) => apiReq('POST', '/api/progress', { course, lecture, done }),
  posts:        (slug)          => apiReq('GET', '/api/courses/' + encodeURIComponent(slug) + '/posts'),
  addPost:      (slug, body)    => apiReq('POST', '/api/courses/' + encodeURIComponent(slug) + '/posts', { body }),
  comments:     (slug)          => apiReq('GET', '/api/news/' + encodeURIComponent(slug) + '/comments'),
  addComment:   (slug, author, body) => apiReq('POST', '/api/news/' + encodeURIComponent(slug) + '/comments', { author, body }),
  notes:        (course, lecture) => apiReq('GET', '/api/notes?course=' + encodeURIComponent(course) + '&lecture=' + encodeURIComponent(lecture)),
  addNote:      (course, lecture, ts, body) => apiReq('POST', '/api/notes', { course, lecture, ts, body }),
};

// Relative time in Thai, e.g. "2 ชม. ที่แล้ว"
function timeAgo(iso) {
  const then = new Date(iso).getTime();
  if (!then) return '';
  const s = Math.max(0, Math.floor((Date.now() - then) / 1000));
  if (s < 60)    return 'เพิ่งโพสต์';
  const m = Math.floor(s / 60);
  if (m < 60)    return m + ' นาที ที่แล้ว';
  const h = Math.floor(m / 60);
  if (h < 24)    return h + ' ชม. ที่แล้ว';
  const d = Math.floor(h / 24);
  if (d < 30)    return d + ' วัน ที่แล้ว';
  return new Date(iso).toLocaleDateString('th-TH');
}

function normalizeCourse(c)  { return { ...c, id: c.slug }; }
function normalizeLecture(l) { return { ...l, id: l.lid, done: !!l.done }; }
function normalizeNews(n)    { return { ...n, id: n.slug }; }

async function hydrate() {
  try {
    const [facs, crs, nws] = await Promise.all([api.faculties(), api.courses(), api.news()]);
    FACULTIES.length = 0; FACULTIES.push(...facs);
    COURSES.length = 0;   COURSES.push(...crs.map(normalizeCourse));
    NEWS.length = 0;      NEWS.push(...nws.map(normalizeNews));
  } catch (err) {
    console.warn('[hydrate] using offline fallback:', err);
  }
}

window.Ic = Ic;
window.FACULTIES = FACULTIES;
window.COURSES = COURSES;
window.LECTURES = LECTURES;
window.NEWS = NEWS;
window.thNum = thNum;
window.facColor = facColor;
window.facName = facName;
window.escape = escape;
window.Placeholder = Placeholder;
window.CourseCard = CourseCard;
// ── URL routing: route/params <-> clean /path ───────────────────
function routeToPath(route, p = {}) {
  switch (route) {
    case 'courses': {
      const qs = new URLSearchParams();
      if (p.fac   && p.fac   !== 'all') qs.set('fac', p.fac);
      if (p.level && p.level !== 'all') qs.set('level', p.level);
      if (p.sort) qs.set('sort', p.sort);
      const s = qs.toString();
      return '/courses' + (s ? '?' + s : '');
    }
    case 'course':    return '/courses/' + encodeURIComponent(p.id || '');
    case 'lecture':   return '/learn/' + encodeURIComponent(p.id || '') +
                             (p.lec ? '/' + encodeURIComponent(p.lec) : '');
    case 'dashboard': return '/dashboard';
    case 'login':     return '/login';
    case 'search':    return '/search' + (p.q ? '?q=' + encodeURIComponent(p.q) : '');
    case 'news':      return '/news' + (p.id ? '/' + encodeURIComponent(p.id) : '');
    case 'home':
    default:          return '/';
  }
}

function pathToRoute(pathname, search) {
  const qs = new URLSearchParams(search || '');
  const segs = (pathname || '/').split('/').filter(Boolean).map(decodeURIComponent);
  const head = segs[0];
  if (!head) return { route: 'home', params: {} };
  if (head === 'courses') {
    if (segs[1]) return { route: 'course', params: { id: segs[1] } };
    return { route: 'courses', params: {
      fac:   qs.get('fac')   || undefined,
      level: qs.get('level') || undefined,
      sort:  qs.get('sort')  || undefined,
    } };
  }
  if (head === 'learn' && segs[1]) {
    return { route: 'lecture', params: { id: segs[1], lec: segs[2] || undefined } };
  }
  if (head === 'dashboard') return { route: 'dashboard', params: {} };
  if (head === 'login')     return { route: 'login', params: {} };
  if (head === 'search')    return { route: 'search', params: { q: qs.get('q') || '' } };
  if (head === 'news')      return { route: 'news', params: segs[1] ? { id: segs[1] } : {} };
  return { route: 'home', params: {} };  // unknown path → home
}

window.api = api;
window.hydrate = hydrate;
window.normalizeLecture = normalizeLecture;
window.timeAgo = timeAgo;
window.routeToPath = routeToPath;
window.pathToRoute = pathToRoute;
