// tietkhi.jsx — Vòng Tiết Khí theo CHUẨN THIÊN VĂN: 24 tiết = 24 cung 15° hoàng đạo (dlSunLong), 12 chi, 4 mùa.
// Sheet mở khi chạm tên tiết ở Đạo Tràng. Có hoạt cảnh Trái Đất quanh Mặt Trời: đường Đất→Trời kéo dài chạm vòng = tiết đương thời.
// Exports to window: TietKhiSheet, dlGiaoTietMoc
const TK_HAN = ['春分','清明','穀雨','立夏','小滿','芒種','夏至','小暑','大暑','立秋','處暑','白露','秋分','寒露','霜降','立冬','小雪','大雪','冬至','小寒','大寒','立春','雨水','驚蟄'];
const TK_CHI_HAN = ['子','丑','寅','卯','辰','巳','午','未','申','酉','戌','亥'];
const TK_CHI_VIET = ['Tý','Sửu','Dần','Mão','Thìn','Tỵ','Ngọ','Mùi','Thân','Dậu','Tuất','Hợi'];
const TK_MUA = [{ ten: 'Xuân', han: '春季', lon: 0, hanh: 'Mộc' }, { ten: 'Hạ', han: '夏季', lon: 90, hanh: 'Hỏa' }, { ten: 'Thu', han: '秋季', lon: 180, hanh: 'Kim' }, { ten: 'Đông', han: '冬季', lon: 270, hanh: 'Thủy' }];
const TK_LAP = [{ s: 21, hanh: 'Mộc' }, { s: 3, hanh: 'Hỏa' }, { s: 9, hanh: 'Kim' }, { s: 15, hanh: 'Thủy' }]; // 4 mốc Lập — vạch màu hành

// kinh độ hoàng đạo → toạ độ màn: Đông Chí (270°) trên đỉnh, Xuân Phân (0°) bên trái, Hạ Chí dưới, Thu Phân phải — đi ngược kim đồng hồ như đồ cổ
function tkXY(lon, r, c = 180) { const th = (lon - 270) * Math.PI / 180; return [c - r * Math.sin(th), c - r * Math.cos(th)]; }
function tkArc(l0, l1, r0, r1, c = 180) {
  const [ax, ay] = tkXY(l0, r1, c), [bx, by] = tkXY(l1, r1, c), [cx2, cy2] = tkXY(l1, r0, c), [dx, dy] = tkXY(l0, r0, c);
  return `M${ax} ${ay} A${r1} ${r1} 0 0 0 ${bx} ${by} L${cx2} ${cy2} A${r0} ${r0} 0 0 1 ${dx} ${dy} Z`;
}
// góc xoay để chữ đứng theo bán kính; nửa dưới lật 180° cho dễ đọc
function tkRot(lon) { const a = -(lon - 270); const norm = ((a % 360) + 360) % 360; return { rot: a, flip: norm > 90 && norm < 270 }; }

// thời điểm chính xác mặt trời chạm mốc 15°·s gần ngày d (tìm nhị phân tới phút) — "giao tiết" theo thiên văn
function dlGiaoTietMoc(s, d) {
  const target = (s * 15) % 360;
  const diff = (t) => { let x = (dlSunLong(new Date(t)) - target + 540) % 360 - 180; return x; }; // <0 trước mốc, >0 sau mốc
  let lo = d.getTime() - 20 * 86400000, hi = d.getTime() + 20 * 86400000;
  // thu hẹp về khoảng 1 ngày có đổi dấu
  let t = lo; while (t < hi && diff(t) < 0) t += 86400000;
  lo = t - 86400000; hi = t;
  for (let i = 0; i < 30; i++) { const mid = (lo + hi) / 2; if (diff(mid) < 0) lo = mid; else hi = mid; }
  return new Date(Math.round(hi / 60000) * 60000);
}
function tkFmtMoc(d) { return `${d.getDate()}/${d.getMonth() + 1} · ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`; }

// Nhân Nguyên Tư Lệnh Ca — nguyên bài theo DLVN (user cung cấp), chia câu theo tiết; khớp bảng DL_NGUYET_LENH
const TT_TU_LENH_CA = {
  'Lập Xuân': ['Lập xuân Mậu thổ ngũ triều vinh,', 'Thập nhật Bính hỏa kiến sơ sinh.'],
  'Vũ Thủy': ['Vũ thủy chi trung Giáp mộc vượng.'],
  'Kinh Trập': ['Kinh trập dương mộc cập trung minh,'],
  'Xuân Phân': ['Xuân phân Ất mộc manh nha địa.'],
  'Thanh Minh': ['Thanh minh Ất mộc thất nhật năng,', 'Bát nhật Quý thủy quy thìn gốc,'],
  'Cốc Vũ': ['Cốc vũ tiền tam Mậu thổ tình (thịnh).'],
  'Lập Hạ': ['Lập hạ Mậu thổ quy lộc ngũ,', 'Thập nhật Canh kim tại kỷ sinh.'],
  'Tiểu Mãn': ['Tiểu mãn đản minh Bính hỏa thịnh.'],
  'Mang Chủng': ['Mang chủng Kỷ thổ lộc quy đình.'],
  'Hạ Chí': ['Hạ chí âm sinh Đinh hỏa cục.'],
  'Tiểu Thử': ['Tiểu thử Đinh hỏa thất nhật vương,', 'Bát nhật Ất mộc vị tương đồng.'],
  'Đại Thử': ['Đại thử thời lai Kỷ thổ chính.'],
  'Lập Thu': ['Lập thu Mậu thổ ngũ nhật nhân,', 'Tự hữu Nhâm thủy thất nhật sinh.'],
  'Xử Thử': ['Xử thử Canh kim đương lộc vượng.'],
  'Bạch Lộ': ['Bạch lộ Canh kim thập nhật cư,'],
  'Thu Phân': ['Thu phân Tân kim nhị thập vinh.'],
  'Hàn Lộ': ['Hàn lộ Tân kim thất nhật đa,', 'Bát nhật Đinh hỏa quy tuất khố.'],
  'Sương Giáng': ['Sương giáng Mậu thổ thập ngũ thiên.'],
  'Lập Đông': ['Lập đông Mậu thổ ngũ nhật vinh,', 'Giáp mộc ngũ nhật kiến sơ sinh.'],
  'Tiểu Tuyết': ['Tiểu tuyết Nhâm thủy nhị thập thăng.'],
  'Đại Tuyết': ['Đại tuyết Nhâm thủy thập nhật vương,'],
  'Đông Chí': ['Đông chí Quý thủy nhị thập phong.'],
  'Tiểu Hàn': ['Tiểu hàn Quý thủy thất nhật quy,', 'Bát nhật Tân kim sửu mộ bách.'],
  'Đại Hàn': ['Đại hàn Kỷ thổ thập ngũ đương.'],
};
const TT_CAN_RE = /(Giáp|Ất|Bính|Đinh|Mậu|Kỷ|Canh|Tân|Nhâm|Quý) (mộc|hỏa|thổ|kim|thủy)/g;
// tô màu can trong câu ca theo LICH_CAN_MAU; can đang nắm lệnh sáng hơn
function TuLenhCau({ line, canLenh, dim }) {
  const out = []; let last = 0, m;
  while ((m = TT_CAN_RE.exec(line))) {
    out.push(line.slice(last, m.index));
    const cur = m[1] === canLenh;
    out.push(<span key={m.index} style={{ color: LICH_CAN_MAU[m[1]], fontWeight: cur ? 700 : 500, textShadow: cur ? `0 0 10px ${LICH_CAN_MAU[m[1]]}66` : 'none' }}>{m[0]}</span>);
    last = m.index + m[0].length;
  }
  out.push(line.slice(last));
  return <div style={{ fontFamily: "'Spectral', serif", fontStyle: 'italic', fontSize: dim ? 13 : 14.5, lineHeight: 1.6, color: dim ? INK.dim : INK.text, opacity: dim ? 0.75 : 1 }}>{out}</div>;
}

// Học dần theo tiết — nông vụ (禾) và dưỡng sinh (身). Nông lịch gốc phương Bắc Á Đông, xứ khác đối chiếu khí hậu địa phương.
const TT_TIET_HOC = {
  'Lập Xuân': { nong: 'Dương khí nhen lên, băng bắt đầu tan; dọn đất, ủ giống, chuẩn bị vụ xuân.', than: 'Xuân dưỡng Can: ngủ sớm dậy sớm, vươn vai đi bộ; ăn vị cay ngọt nhẹ (hành, hẹ), bớt chua gắt.' },
  'Vũ Thủy': { nong: 'Mưa phùn ẩm, gieo mạ chiêm, trồng cây đầu năm.', than: 'Trời ẩm, kiện Tỳ trừ thấp; giữ ấm bụng lưng — "xuân ô" chớ cởi áo sớm.' },
  'Kinh Trập': { nong: 'Sấm đầu mùa, sâu bọ tỉnh giấc; cày bừa, làm cỏ, phòng sâu.', than: 'Can khí vượng dễ cáu gắt; dậy sớm vận động nhẹ, ăn lê nhuận táo, bớt rượu.' },
  'Xuân Phân': { nong: 'Ngày đêm bằng nhau; mạ vươn, bón thúc, tỉa cành.', than: 'Âm dương cân bằng — điều hòa cảm xúc, ngủ đủ; mùa dị ứng phấn hoa, phòng phong tà.' },
  'Thanh Minh': { nong: '"Thanh minh tiền hậu, chủng qua điểm đậu" — gieo trồng rộ; tảo mộ, đạp thanh.', than: 'Đi bộ ngoài trời, hít thở sâu dưỡng Can; ăn thanh đạm, tránh gió lạnh sớm chiều.' },
  'Cốc Vũ': { nong: 'Mưa rào dưỡng lúa, cấy xong vụ xuân; hái trà Cốc Vũ.', than: 'Thấp khí nặng, dễ đau khớp; kiện Tỳ, ít đồ sống lạnh, ăn ý dĩ, đậu đỏ.' },
  'Lập Hạ': { nong: 'Vào hè, lúa làm đòng; chống hạn, phòng sâu đục thân.', than: 'Hạ dưỡng Tâm: ngủ trưa ngắn, giữ tâm bình; vị đắng thanh nhiệt (khổ qua, trà sen).' },
  'Tiểu Mãn': { nong: 'Hạt mẩy chưa chín — "tiểu mãn bất mãn"; tưới nước, giữ ẩm.', than: 'Thấp nhiệt, dễ nổi mẩn; ăn đậu xanh, ý dĩ lợi thấp; tránh cay nóng, dầu mỡ.' },
  'Mang Chủng': { nong: 'Lúa có râu chín gặt, gieo kê vụ mùa — bận rộn nhất năm.', than: 'Mưa dầm oi bức, người uể oải; uống đủ nước, ngủ trưa vừa phải, tắm nước ấm.' },
  'Hạ Chí': { nong: 'Ngày dài nhất, dương cực âm sinh; chống úng, trừ cỏ.', than: 'Tránh nắng trưa, bồi âm dịch (canh rau, trái cây); "đông bệnh hạ trị" — chữa bệnh hàn mùa đông lúc này.' },
  'Tiểu Thử': { nong: 'Nắng gắt, lúa sớm trổ; nhổ cỏ, phơi thóc giống.', than: 'Mồ hôi nhiều hao tân dịch; ăn nhạt, uống nước ấm, tránh lạnh đột ngột (quạt, điều hòa thẳng người).' },
  'Đại Thử': { nong: 'Nóng nhất năm, bão lũ; "đại thử bất nhiệt, ngũ cốc bất kết".', than: 'Phòng say nắng, dưỡng Tỳ Vị bằng cháo loãng, trà sen, ngủ đủ; không uống đá lạnh ồ ạt.' },
  'Lập Thu': { nong: 'Vào thu nhưng còn "thu lão hổ" nóng rớt; lúa mùa đẻ nhánh.', than: 'Thu dưỡng Phế: bắt đầu "thu táo" khô hanh — ăn mè, mật ong, lê; nằm sớm dậy sớm.' },
  'Xử Thử': { nong: 'Hết nóng, nắng đẹp phơi thóc; thu hoa màu, gieo rau vụ đông.', than: 'Ngày nóng đêm mát dễ cảm; "thu khốn" buồn ngủ — ngủ thêm một giờ, vận động buổi sáng.' },
  'Bạch Lộ': { nong: 'Sương trắng sáng sớm; thu hoạch rộ, trồng cải, hành.', than: '"Bạch lộ bất lộ thân" — không cởi trần, giữ ấm chân bụng; người hen suyễn, viêm mũi cẩn trọng.' },
  'Thu Phân': { nong: 'Ngày đêm bằng nhau; gặt đại trà, gieo mạch đông.', than: 'Khô hanh rõ — nhuận Phế dưỡng âm: lê, bách hợp, củ ấu; bớt cay, uống nước đều.' },
  'Hàn Lộ': { nong: 'Sương lạnh, gặt xong vụ mùa; trồng cải, cày ải đất.', than: '"Hàn lộ bất lộ cước" — lạnh từ chân lên: ngâm chân nước ấm, mang tất; ăn vừng đen, hạt dẻ; người lớn tuổi phòng tim mạch.' },
  'Sương Giáng': { nong: 'Sương muối đầu mùa, cây rụng lá; thu nốt rau củ, cất giữ.', than: '"Bổ đông bất như bổ Sương Giáng" — dưỡng Tỳ Vị trước khi vào đông; giữ ấm khớp gối.' },
  'Lập Đông': { nong: 'Vào đông, đồng ruộng nghỉ; cày ải, ủ phân, tích trữ.', than: 'Đông dưỡng Thận, chủ "tàng": ngủ sớm dậy muộn, ăn ấm, đồ đen (đậu đen, mè đen); giảm ra mồ hôi nhiều.' },
  'Tiểu Tuyết': { nong: 'Tuyết nhẹ phương Bắc; phương Nam gieo mạch, trữ rau muối.', than: 'Trời âm u dễ buồn bã — phơi nắng, vận động; ăn củ cải, bớt mặn để đỡ nặng Thận.' },
  'Đại Tuyết': { nong: 'Tuyết dày che mạch — "thụy tuyết triệu phong niên".', than: 'Ôn bổ Thận dương; giữ ấm đầu, cổ, bàn chân; hạn chế ra ngoài quá sớm, phòng cảm lạnh.' },
  'Đông Chí': { nong: 'Đêm dài nhất, âm cực dương sinh; bắt đầu "số cửu" đếm rét.', than: '"Đông chí nhất dương sinh" — bồi bổ đúng lúc, nghỉ ngơi, giữ tinh thần an tĩnh; không lao lực.' },
  'Tiểu Hàn': { nong: 'Rét đậm bắt đầu; che chắn mạ, chuồng trại.', than: 'Rét nhất năm phương Bắc — ôn dương dưỡng Thận, cháo ấm; dậy từ từ, tránh đột quỵ sáng sớm.' },
  'Đại Hàn': { nong: 'Rét cuối đông, chuẩn bị Tết; cày ải, tích phân cho vụ xuân.', than: 'Thổ nắm lệnh, kiện Tỳ để chuyển sang xuân; ăn nhẹ dần, bớt bổ nặng, chuẩn bị khí "sinh phát".' },
};
const tkWikiUrl = (t) => { const p = t.split(' '); return 'https://vi.wikipedia.org/wiki/' + encodeURIComponent(p[0] + ' ' + p.slice(1).join(' ').toLowerCase()); };

function TietKhiWheel({ date, anim, zoom }) {
  const lon = dlSunLong(date);
  const sec = Math.floor(lon / 15) % 24;
  const hanhCur = TIET_HANH[TIET_SECTOR[sec]] || 'Thổ';
  const cCur = NGU_HANH_COLOR[hanhCur];
  const tl = dlTuLenh(date);
  const cLenh = LICH_CAN_MAU[tl.canTen] || tl.color;
  const [sx, sy] = tkXY(lon, 98);        // mặt trời biểu kiến trên hoàng đạo
  // quỹ đạo elip — Mặt Trời ở tiêu điểm (tâm đồ). Độ dẹt thật e≈0.017 gần tròn, vẽ phóng đại để thấy cận/viễn nhật
  const TK_E = 0.32, TK_A = 50, TK_B = TK_A * Math.sqrt(1 - TK_E * TK_E), TK_LON_VIEN = 283; // Đất viễn nhật ≈ 4/7 (kinh độ nhật tâm 283°; cận nhật ≈ 3/1 ở 103°)
  const nu = (lon + 180 - 103) * Math.PI / 180;           // góc từ cận nhật
  const rE = TK_A * (1 - TK_E * TK_E) / (1 + TK_E * Math.cos(nu));
  const [ex, ey] = tkXY(lon + 180, rE);                   // trái đất trên elip, cùng hướng với đường Đất→Trời
  const [ocx, ocy] = tkXY(TK_LON_VIEN, TK_A * TK_E);      // tâm elip lệch về phía viễn nhật
  const thV = (TK_LON_VIEN - 270) * Math.PI / 180, rotEl = Math.atan2(-Math.cos(thV), -Math.sin(thV)) * 180 / Math.PI;
  const [pxC, pyC] = tkXY(103, TK_A * (1 - TK_E)), [pxV, pyV] = tkXY(283, TK_A * (1 + TK_E));
  const [lx, ly] = tkXY(lon, 150);       // đầu đường chiếu ra vành tiết
  const font = "'Be Vietnam Pro', sans-serif";
  // cận cảnh: khung nhìn 190px quanh cung đương thời (vẫn giữ hướng đồ)
  const [zx, zy] = tkXY(sec * 15 + 7.5, 132);
  const vb = zoom ? `${zx - 95} ${zy - 95} 190 190` : '0 0 360 360';
  // ngày thứ N của tiết → góc trong cung (tiết ≈ 15.2 ngày)
  const dayLon = (l0, d) => l0 + Math.min(15, Math.max(0, 15 * (d - 1) / 15.2));
  return (
    <svg viewBox={vb} style={{ width: '100%', height: 'auto', display: 'block', transition: anim ? 'all .35s' : 'none' }}>
      <defs>
        <radialGradient id="tk-troi" cx="50%" cy="50%" r="50%"><stop offset="0%" stopColor="#1a2b55" /><stop offset="70%" stopColor="#0d1733" /><stop offset="100%" stopColor="#080d1f" /></radialGradient>
        <radialGradient id="tk-sun" cx="50%" cy="50%" r="50%"><stop offset="0%" stopColor="#fff6cf" /><stop offset="45%" stopColor="#f2d27a" /><stop offset="100%" stopColor="rgba(242,210,122,0)" /></radialGradient>
      </defs>
      {/* vành chi 12 */}
      <circle cx="180" cy="180" r="179" fill="rgba(230,220,184,0.03)" stroke={INK.hair} />
      {TK_CHI_HAN.map((h, k) => {
        const c = 270 + 30 * k, { rot } = tkRot(c), [x, y] = tkXY(c, 168), [x2, y2] = tkXY(c, 156);
        const cur = Math.floor(((lon - 255 + 360) % 360) / 30) === k;
        return (
          <g key={h}>
            <text x={x} y={y} transform={`rotate(${rot} ${x} ${y})`} textAnchor="middle" dominantBaseline="central" fontSize="15" fontFamily="'Ma Shan Zheng', serif" fill={cur ? INK.gold : INK.dim}>{h}</text>
            <text x={x2} y={y2} transform={`rotate(${rot} ${x2} ${y2})`} textAnchor="middle" dominantBaseline="central" fontSize="6.5" fontWeight="700" fontFamily={font} letterSpacing="0.4" fill={cur ? INK.gold : INK.faint}>{TK_CHI_VIET[k].toUpperCase()}</text>
          </g>
        );
      })}
      {/* vành 24 tiết — mỗi cung chia theo CAN nắm lệnh (nhân nguyên tư lệnh) */}
      {TIET_SECTOR.map((t, s) => {
        const l0 = s * 15, l1 = l0 + 15, mid = l0 + 7.5, cur = s === sec;
        const { rot, flip } = tkRot(mid);
        const [xv, yv] = tkXY(mid, 133), [xh, yh] = tkXY(mid, 112);
        const hanh = TIET_HANH[t] || 'Thổ', col = NGU_HANH_COLOR[hanh];
        const parts = t.split(' ');
        const rules = DL_NGUYET_LENH[t] || [[1, 99, 'Mậu Thổ']];
        return (
          <g key={t}>
            {rules.map(([ds, de, can], i) => {
              const a0 = dayLon(l0, ds), a1 = de >= 99 ? l1 : dayLon(l0, de + 1);
              const cc = LICH_CAN_MAU[can.split(' ')[0]] || col;
              const dangLenh = cur && tl.can === can;
              return <path key={i} d={tkArc(a0, a1, 102, 150)} fill={`color-mix(in oklab, ${cc} ${dangLenh ? 46 : cur ? 26 : 13}%, transparent)`} stroke={dangLenh ? cc : 'rgba(230,220,184,0.12)'} strokeWidth={dangLenh ? 1 : 0.4} />;
            })}
            <path d={tkArc(l0, l1, 102, 150)} fill="none" stroke={cur ? col : INK.hair} strokeWidth={cur ? 1.2 : 0.6} />
            <text transform={`translate(${xv} ${yv}) rotate(${rot + (flip ? 180 : 0)})`} textAnchor="middle" dominantBaseline="central" fontSize="7" fontWeight="700" fontFamily={font} fill={cur ? '#fff8e6' : INK.text} opacity={cur ? 1 : 0.85}>
              <tspan x="0" dy="-4">{parts[0].toUpperCase()}</tspan><tspan x="0" dy="8">{parts.slice(1).join(' ').toUpperCase()}</tspan>
            </text>
            <text transform={`translate(${xh} ${yh}) rotate(${rot + (flip ? 180 : 0)})`} textAnchor="middle" dominantBaseline="central" fontSize="9.5" fontFamily="'Ma Shan Zheng', serif" fill={cur ? '#fff8e6' : INK.dim}>{TK_HAN[s]}</text>
          </g>
        );
      })}
      {TK_LAP.map(({ s, hanh }) => { const [x0, y0] = tkXY(s * 15, 150), [x1, y1] = tkXY(s * 15, 179); return <line key={s} x1={x0} y1={y0} x2={x1} y2={y1} stroke={NGU_HANH_COLOR[hanh]} strokeWidth="1.6" />; })}
      {/* bầu trời trong — quỹ đạo */}
      <circle cx="180" cy="180" r="100" fill="url(#tk-troi)" stroke={INK.goldDim} strokeWidth="0.8" />
      {TK_MUA.map((m, i) => { const [x, y] = tkXY(m.lon, 80), { rot } = tkRot(m.lon); const [x2, y2] = tkXY(m.lon, 93); const c = `color-mix(in oklab, ${NGU_HANH_COLOR[m.hanh]} 70%, #e6dcb8)`; return (
        <g key={m.ten}>
          <text x={x} y={y} transform={`rotate(${rot} ${x} ${y})`} textAnchor="middle" dominantBaseline="central" fontSize="13" fontFamily="'Ma Shan Zheng', serif" fill={c}>{m.han}</text>
          <text x={x2} y={y2} textAnchor="middle" dominantBaseline="central" fontSize="6.5" fontWeight="700" fontFamily={font} fill={INK.text} letterSpacing="0.6">{['XUÂN', 'HẠ', 'THU', 'ĐÔNG'][i]}</text>
        </g>); })}
      <ellipse cx={ocx} cy={ocy} rx={TK_A} ry={TK_B} transform={`rotate(${rotEl} ${ocx} ${ocy})`} fill="none" stroke="rgba(230,220,184,0.35)" strokeWidth="0.7" strokeDasharray="2 3" />
      <circle cx={pxC} cy={pyC} r="1.3" fill={INK.faint} /><text x={pxC} y={pyC - 4} textAnchor="middle" fontSize="4.6" fontFamily={font} fill={INK.faint}>cận nhật 3/1</text>
      <circle cx={pxV} cy={pyV} r="1.3" fill={INK.faint} /><text x={pxV} y={pyV + 7} textAnchor="middle" fontSize="4.6" fontFamily={font} fill={INK.faint}>viễn nhật 4/7</text>
      {/* đường Đất → Trời → vành tiết, màu can nắm lệnh */}
      <line x1={ex} y1={ey} x2={lx} y2={ly} stroke={cLenh} strokeWidth="1" strokeDasharray="3 2" opacity="0.95" />
      <circle cx={lx} cy={ly} r="2.6" fill={cLenh} />
      <circle cx="180" cy="180" r="22" fill="url(#tk-sun)" opacity="0.9" />
      <circle cx="180" cy="180" r="9" fill="#fff3c4" />
      <circle cx={ex} cy={ey} r="5" fill="#7fb3e8" stroke="#dbe9f7" strokeWidth="0.8" />
      <circle cx={ex} cy={ey} r="9" fill="none" stroke="#7fb3e8" strokeWidth="0.6" opacity="0.5">{anim && <animate attributeName="r" values="7;11;7" dur="2.4s" repeatCount="indefinite" />}</circle>
      <circle cx={sx} cy={sy} r="3" fill="#f2d27a" opacity="0.9" />
    </svg>
  );
}

function TietKhiSheet({ open, onClose, anim, onOpenLich }) {
  const [pham, setPham] = React.useState('thang'); // thanh kéo: tháng (±30) / năm (±365)
  const [offset, setOffset] = React.useState(0); // số ngày lệch so với bây giờ (hoạt cảnh/kéo)
  const [playing, setPlaying] = React.useState(false);
  const [zoom, setZoom] = React.useState(true); // cận cảnh mặc định — điện thoại đỡ căng mắt
  const rafRef = React.useRef(null);
  React.useEffect(() => { if (open) { setOffset(0); setPlaying(false); } }, [open]);
  React.useEffect(() => {
    if (!playing) { if (rafRef.current) cancelAnimationFrame(rafRef.current); return; }
    let last = performance.now();
    const step = (now) => {
      const dt = (now - last) / 1000; last = now;
      setOffset(o => { const n = o + dt * 14; if (n >= 365.25) { setPlaying(false); return 365.25; } return n; }); // một vòng năm ≈ 26 giây
      rafRef.current = requestAnimationFrame(step);
    };
    rafRef.current = requestAnimationFrame(step);
    return () => cancelAnimationFrame(rafRef.current);
  }, [playing]);
  if (!open) return null;

  const now = new Date();
  const date = new Date(now.getTime() + offset * 86400000);
  const lon = dlSunLong(date), sec = Math.floor(lon / 15) % 24;
  const tiet = TIET_SECTOR[sec], hanh = TIET_HANH[tiet] || 'Thổ', col = NGU_HANH_COLOR[hanh];
  const batDau = dlGiaoTietMoc(sec, date), ke = dlGiaoTietMoc(sec + 1, new Date(batDau.getTime() + 15 * 86400000));
  const conMs = ke.getTime() - date.getTime();
  const tl = dlTuLenh(date), rules = DL_NGUYET_LENH[tiet] || [];
  const d12 = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 12);
  const queTy = dlHexForGio(dlLunarInfo(d12), 0).hex;
  const conNgay = Math.floor(conMs / 86400000), conGio = Math.floor((conMs % 86400000) / 3600000);
  const font = "'Be Vietnam Pro', sans-serif";
  const chip = (label, onClick, active) => (
    <button onClick={onClick} style={{ padding: '7px 13px', borderRadius: 999, cursor: 'pointer', background: active ? 'rgba(var(--gold-rgb),0.16)' : INK.panel, border: `1px solid ${active ? INK.goldDim : INK.hair}`, fontFamily: font, fontSize: 11.5, fontWeight: 600, color: active ? INK.gold : INK.dim, whiteSpace: 'nowrap' }}>{label}</button>
  );
  return (
    <div style={{ position: 'absolute', inset: 0, zIndex: 46, display: 'flex', flexDirection: 'column', background: 'linear-gradient(180deg, #15111f 0%, #100d16 60%)', animation: anim ? 'tt-sheet 0.34s cubic-bezier(.2,.7,.2,1)' : 'none' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '54px 18px 12px', flexShrink: 0, borderBottom: `1px solid ${INK.hair}` }}>
        <div style={{ flex: 1 }}>
          <div style={{ ...han, fontSize: 23, color: INK.gold, lineHeight: 1 }}>節氣</div>
          <div style={{ fontFamily: font, fontSize: 11, color: INK.dim, marginTop: 3, letterSpacing: 0.3 }}>Vòng Tiết Khí · theo kinh độ mặt trời</div>
        </div>
        <button onClick={onClose} style={{ width: 36, height: 36, borderRadius: '50%', cursor: 'pointer', background: 'rgba(255,255,255,0.05)', border: `1px solid ${INK.hair}`, color: INK.dim, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 20, lineHeight: 1 }}>×</button>
      </div>
      <div style={{ flex: 1, overflowY: 'auto', padding: '10px 14px 40px' }}>
        <TietKhiWheel date={date} anim={anim} zoom={zoom} />
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, marginTop: 6, flexWrap: 'wrap' }}>
          <span style={{ fontFamily: "'Spectral', serif", fontSize: 24, fontWeight: 700, color: col, textShadow: `0 0 14px ${col}55` }}>{tiet}</span>
          <span style={{ ...han, fontSize: 20, color: col, opacity: 0.85 }}>{TK_HAN[sec]}</span>
          <span style={{ fontFamily: font, fontSize: 11.5, color: INK.dim }}>hành {hanh} · mặt trời {lon.toFixed(2)}°</span>
        </div>
        <div style={{ fontFamily: font, fontSize: 12, color: INK.dim, lineHeight: 1.7, marginTop: 4 }}>
          <span style={{ color: INK.text, fontWeight: 600 }}>{fmtDate(date)}</span>{offset !== 0 && <span style={{ color: INK.faint }}> · {offset > 0 ? '+' : ''}{Math.round(offset)} ngày</span>}
          <br />Vào tiết <span style={{ color: col, fontWeight: 600 }}>{tkFmtMoc(batDau)}</span> (mốc {sec * 15}°) · sang <span style={{ color: NGU_HANH_COLOR[TIET_HANH[TIET_SECTOR[(sec + 1) % 24]] || 'Thổ'], fontWeight: 600 }}>{TIET_SECTOR[(sec + 1) % 24]}</span> lúc {tkFmtMoc(ke)} — còn {conNgay} ngày {conGio} giờ
        </div>
        <div style={{ display: 'flex', gap: 6, marginTop: 10, flexWrap: 'wrap', alignItems: 'center' }}>
          <span style={{ fontFamily: font, fontSize: 11, color: INK.faint }}>Nắm lệnh trong tiết:</span>
          {rules.map(([ds, de, can]) => { const cc = LICH_CAN_MAU[can.split(' ')[0]] || col, cur = can === tl.can; return (
            <span key={ds} style={{ display: 'inline-flex', alignItems: 'center', gap: 5, padding: '3px 9px', borderRadius: 999, background: `color-mix(in oklab, ${cc} ${cur ? 30 : 12}%, transparent)`, border: `1px solid ${cur ? cc : 'transparent'}`, fontFamily: font, fontSize: 11, fontWeight: cur ? 700 : 500, color: cur ? '#fff8e6' : INK.dim, whiteSpace: 'nowrap' }}>
              <span style={{ width: 7, height: 7, borderRadius: '50%', background: cc }}></span>{de >= 99 ? `${ds}+` : `${ds}–${de}`} {can}{cur && <span style={{ opacity: 0.8 }}> · ngày {tl.ngayThu}</span>}
            </span>); })}
        </div>
        <div style={{ display: 'flex', gap: 8, marginTop: 12, alignItems: 'center', flexWrap: 'wrap' }}>
          {chip(playing ? '❚❚ Dừng' : '▶ Diễn một vòng năm', () => { if (!playing && offset >= 365) setOffset(0); setPlaying(p => !p); }, playing)}
          {chip('Hôm nay', () => { setPlaying(false); setOffset(0); }, offset === 0)}
          {chip(zoom ? '⊕ Toàn vòng' : '⊙ Cận cảnh', () => setZoom(z => !z), zoom)}
        </div>
        {/* thanh kéo: phạm vi tháng/năm + nút ‹ › từng ngày */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 12 }}>
          <button onClick={() => { setPlaying(false); setOffset(o => Math.round(o) - 1); }} style={{ width: 34, height: 34, borderRadius: 10, cursor: 'pointer', background: INK.panel, border: `1px solid ${INK.hair}`, color: INK.dim, fontSize: 16, flexShrink: 0 }}>‹</button>
          <input type="range" min={pham === 'thang' ? -30 : -365} max={pham === 'thang' ? 30 : 365} step={pham === 'thang' ? 0.25 : 1} value={Math.max(pham === 'thang' ? -30 : -365, Math.min(pham === 'thang' ? 30 : 365, offset))} onChange={e => { setPlaying(false); setOffset(parseFloat(e.target.value)); }} style={{ flex: 1, accentColor: INK.gold, height: 28 }} />
          <button onClick={() => { setPlaying(false); setOffset(o => Math.round(o) + 1); }} style={{ width: 34, height: 34, borderRadius: 10, cursor: 'pointer', background: INK.panel, border: `1px solid ${INK.hair}`, color: INK.dim, fontSize: 16, flexShrink: 0 }}>›</button>
          <div style={{ display: 'flex', background: INK.panel, border: `1px solid ${INK.hair}`, borderRadius: 999, padding: 2, flexShrink: 0 }}>
            {[['thang', 'Tháng'], ['nam', 'Năm']].map(([v, l]) => <button key={v} onClick={() => setPham(v)} style={{ padding: '5px 10px', borderRadius: 999, cursor: 'pointer', border: 'none', background: pham === v ? 'rgba(var(--gold-rgb),0.16)' : 'transparent', fontFamily: font, fontSize: 10.5, fontWeight: 600, color: pham === v ? INK.gold : INK.faint }}>{l}</button>)}
          </div>
        </div>
        <div style={{ display: 'flex', justifyContent: 'space-between', fontFamily: font, fontSize: 10, color: INK.faint, marginTop: 2, padding: '0 42px' }}><span>{pham === 'thang' ? '−30 ngày' : '−1 năm'}</span><span>hôm nay</span><span>{pham === 'thang' ? '+30 ngày' : '+1 năm'}</span></div>
        {/* quẻ ngày ấy theo Lịch Dịch Lý */}
        <button onClick={() => onOpenLich && onOpenLich(d12)} style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', marginTop: 12, padding: '9px 12px', borderRadius: 12, cursor: 'pointer', textAlign: 'left', background: 'rgba(var(--gold-rgb),0.07)', border: `1px solid ${INK.goldDim}44` }}>
          <span style={{ ...han, fontSize: 16, color: INK.goldDim }}>曆</span>
          <span style={{ flex: 1, fontFamily: font, fontSize: 12, color: INK.dim }}>Quẻ chánh giờ Tý ngày ấy: <span style={{ fontFamily: "'Spectral', serif", fontWeight: 700, color: INK.gold }}>{queTy.name}</span></span>
          <span style={{ fontFamily: font, fontSize: 11, color: INK.goldDim }}>12 giờ ›</span>
        </button>
        {/* Học dần theo tiết — nông vụ & cơ thể */}
        {TT_TIET_HOC[tiet] && (
          <InkCard style={{ padding: 14, marginTop: 12 }}>
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 8 }}>
              <span style={{ ...han, fontSize: 18, color: col }}>學</span>
              <span style={{ fontFamily: "'Spectral', serif", fontSize: 14, fontWeight: 700, color: INK.text }}>Học dần · {tiet}</span>
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: '22px minmax(0,1fr)', gap: '6px 8px', marginTop: 8, fontFamily: font, fontSize: 12.5, lineHeight: 1.55, color: INK.dim }}>
              <span style={{ ...han, fontSize: 15, color: NGU_HANH_COLOR['Mộc'], lineHeight: 1.4 }} title="nông vụ · thiên nhiên">禾</span><span style={{ textWrap: 'pretty' }}>{TT_TIET_HOC[tiet].nong}</span>
              <span style={{ ...han, fontSize: 15, color: NGU_HANH_COLOR['Hỏa'], lineHeight: 1.4 }} title="cơ thể · dưỡng sinh">身</span><span style={{ textWrap: 'pretty' }}>{TT_TIET_HOC[tiet].than}</span>
            </div>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 10, gap: 8, flexWrap: 'wrap' }}>
              <span style={{ fontFamily: font, fontSize: 10.5, color: INK.faint }}>Nông lịch gốc phương Bắc — xứ khác đối chiếu khí hậu tại chỗ.</span>
              <a href={tkWikiUrl(tiet)} target="_blank" rel="noopener" style={{ fontFamily: font, fontSize: 11, fontWeight: 600, color: INK.gold, textDecoration: 'none', borderBottom: `1px dotted ${INK.goldDim}` }}>Đọc thêm · Wikipedia ›</a>
            </div>
          </InkCard>
        )}
        {/* Nhân Nguyên Tư Lệnh Ca — câu tiết này + tiết kế */}
        <InkCard style={{ padding: 14, marginTop: 12 }}>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 8 }}>
            <span style={{ ...han, fontSize: 18, color: INK.gold }}>人元司令歌</span>
            <span style={{ fontFamily: font, fontSize: 10.5, color: INK.faint, letterSpacing: 0.3 }}>Nhân Nguyên Tư Lệnh Ca</span>
          </div>
          <div style={{ marginTop: 8 }}>{(TT_TU_LENH_CA[tiet] || []).map((l, i) => <TuLenhCau key={i} line={l} canLenh={tl.canTen} />)}</div>
          <div style={{ marginTop: 8, paddingTop: 8, borderTop: `1px dashed ${INK.hair}` }}>
            <div style={{ fontFamily: font, fontSize: 10.5, color: INK.faint, marginBottom: 2 }}>Tiết kế · {TIET_SECTOR[(sec + 1) % 24]} · {tkFmtMoc(ke)}</div>
            {(TT_TU_LENH_CA[TIET_SECTOR[(sec + 1) % 24]] || []).map((l, i) => <TuLenhCau key={i} line={l} canLenh="" dim />)}
          </div>
        </InkCard>
        <InkCard style={{ padding: 14, marginTop: 16 }}>
          <div style={{ fontFamily: "'Spectral', serif", fontSize: 14, fontWeight: 700, color: INK.text }}>Tiết khí đo bằng gì?</div>
          <p style={{ fontFamily: font, fontSize: 12.5, lineHeight: 1.6, color: INK.dim, margin: '6px 0 0', textWrap: 'pretty' }}>
            Trái Đất đi một vòng quanh Mặt Trời là một năm. Nhìn từ Đất, Mặt Trời tựa như trôi trên vòng hoàng đạo; vòng ấy chia đều 24 cung, mỗi cung 15°. Mặt Trời bước sang cung nào là vào tiết ấy — không theo ngày âm lịch, không theo bảng cố định. Đường chấm trên đồ: từ Đất qua Trời chiếu ra vành ngoài, đúng cung <span style={{ color: col, fontWeight: 600 }}>{tiet}</span>. Bốn vạch màu là bốn mốc Lập — cửa mùa. Quỹ đạo là elip, Mặt Trời ở một tiêu điểm — thật ra gần tròn (độ dẹt 0,017), ở đây vẽ phóng đại. Đầu tháng 1 Đất ở cận nhật, đi nhanh nên tiết mùa đông ngắn (~14,7 ngày); đầu tháng 7 viễn nhật, đi chậm nên tiết mùa hạ dài (~15,7 ngày). Mỗi cung lại chia mảng theo <span style={{ color: LICH_CAN_MAU[tl.canTen] || tl.color, fontWeight: 600 }}>can nắm lệnh</span> (nhân nguyên tư lệnh) — đường chấm mang màu can đang nắm lệnh hôm ấy.
          </p>
        </InkCard>
      </div>
    </div>
  );
}

Object.assign(window, { TietKhiSheet, dlGiaoTietMoc });
