/* Chinese radical learning game */

const CHINESE_RADICAL_STORAGE_KEY = 'ppt-chinese-radical-selection-v1';
const CHINESE_NOTES_STORAGE_KEY = 'ppt-chinese-topic-notes-v1';
const CHINESE_HANDWRITING_FONT = '"Ma Shan Zheng", "Kaiti SC", STKaiti, KaiTi, "PingFang SC", cursive';

const CHINESE_RADICALS = [
  { id: 'ren', radical: '人', alt: '亻', pinyin: 'rén', tone: 'ren2', meaning: 'person', vi: 'người', hint: 'Liên quan đến người và hành động của con người.', examples: ['你', '他', '休'] },
  { id: 'kou', radical: '口', alt: '', pinyin: 'kǒu', tone: 'kou3', meaning: 'mouth', vi: 'miệng', hint: 'Liên quan đến nói, ăn, miệng hoặc âm thanh.', examples: ['吗', '叫', '吃'] },
  { id: 'nu', radical: '女', alt: '', pinyin: 'nǚ', tone: 'nu3', meaning: 'woman', vi: 'nữ', hint: 'Thường gặp trong chữ về phụ nữ, gia đình, quan hệ.', examples: ['好', '妈', '姐'] },
  { id: 'zi', radical: '子', alt: '', pinyin: 'zǐ', tone: 'zi3', meaning: 'child', vi: 'con', hint: 'Gợi ý trẻ em, con cái, người nhỏ tuổi.', examples: ['好', '字', '孩'] },
  { id: 'xin', radical: '心', alt: '忄', pinyin: 'xīn', tone: 'xin1', meaning: 'heart', vi: 'tâm', hint: 'Cảm xúc, suy nghĩ, trạng thái tinh thần.', examples: ['想', '快', '忙'] },
  { id: 'shou', radical: '手', alt: '扌', pinyin: 'shǒu', tone: 'shou3', meaning: 'hand', vi: 'tay', hint: 'Hành động làm bằng tay.', examples: ['打', '找', '拿'] },
  { id: 'shui', radical: '水', alt: '氵', pinyin: 'shuǐ', tone: 'shui3', meaning: 'water', vi: 'nước', hint: 'Nước, chất lỏng, sông suối, trạng thái ẩm ướt.', examples: ['河', '海', '洗'] },
  { id: 'huo', radical: '火', alt: '灬', pinyin: 'huǒ', tone: 'huo3', meaning: 'fire', vi: 'lửa', hint: 'Lửa, nhiệt, nấu, cháy.', examples: ['热', '烧', '灯'] },
  { id: 'mu_tree', radical: '木', alt: '', pinyin: 'mù', tone: 'mu4', meaning: 'wood/tree', vi: 'mộc', hint: 'Cây, gỗ, vật liệu gỗ.', examples: ['林', '机', '桌'] },
  { id: 'ri', radical: '日', alt: '', pinyin: 'rì', tone: 'ri4', meaning: 'sun/day', vi: 'nhật', hint: 'Ngày, mặt trời, thời gian.', examples: ['明', '时', '晚'] },
  { id: 'yue', radical: '月', alt: '', pinyin: 'yuè', tone: 'yue4', meaning: 'moon/month', vi: 'nguyệt', hint: 'Trăng, tháng; trong nhiều chữ cũng gợi ý bộ phận cơ thể.', examples: ['明', '期', '腿'] },
  { id: 'mu_eye', radical: '目', alt: '', pinyin: 'mù', tone: 'mu4', meaning: 'eye', vi: 'mục', hint: 'Mắt, nhìn, quan sát.', examples: ['看', '眼', '睛'] },
  { id: 'yan', radical: '言', alt: '讠', pinyin: 'yán', tone: 'yan2', meaning: 'speech', vi: 'ngôn', hint: 'Nói, ngôn ngữ, lời hứa.', examples: ['说', '语', '请'] },
  { id: 'jin', radical: '金', alt: '钅', pinyin: 'jīn', tone: 'jin1', meaning: 'metal/gold', vi: 'kim', hint: 'Kim loại, tiền, vật dụng bằng kim loại.', examples: ['钱', '铁', '银'] },
  { id: 'tu', radical: '土', alt: '', pinyin: 'tǔ', tone: 'tu3', meaning: 'earth/soil', vi: 'thổ', hint: 'Đất, vùng đất, nền móng.', examples: ['地', '场', '城'] },
  { id: 'yu', radical: '玉', alt: '王', pinyin: 'yù', tone: 'yu4', meaning: 'jade', vi: 'ngọc', hint: 'Ngọc, đá quý, vật đẹp hoặc quý.', examples: ['球', '玩', '现'] },
  { id: 'shan', radical: '山', alt: '', pinyin: 'shān', tone: 'shan1', meaning: 'mountain', vi: 'sơn', hint: 'Núi, địa hình cao.', examples: ['岛', '峰', '岁'] },
  { id: 'tian', radical: '田', alt: '', pinyin: 'tián', tone: 'tian2', meaning: 'field', vi: 'điền', hint: 'Ruộng đồng, ô đất, canh tác.', examples: ['男', '界', '留'] },
  { id: 'mi_thread', radical: '糸', alt: '纟', pinyin: 'mì', tone: 'mi4', meaning: 'silk/thread', vi: 'mịch', hint: 'Sợi chỉ, dệt, kết nối.', examples: ['线', '给', '红'] },
  { id: 'cao', radical: '艹', alt: '', pinyin: 'cǎo', tone: 'cao3', meaning: 'grass', vi: 'thảo', hint: 'Cây cỏ, thực vật, hoa lá.', examples: ['花', '茶', '草'] },
  { id: 'chong', radical: '虫', alt: '', pinyin: 'chóng', tone: 'chong2', meaning: 'insect', vi: 'trùng', hint: 'Côn trùng, động vật nhỏ.', examples: ['蚊', '蛇', '蛋'] },
  { id: 'zu', radical: '足', alt: '', pinyin: 'zú', tone: 'zu2', meaning: 'foot', vi: 'túc', hint: 'Chân, đi lại, động tác bằng chân.', examples: ['跑', '跳', '跟'] },
  { id: 'che', radical: '车', alt: '', pinyin: 'chē', tone: 'che1', meaning: 'vehicle', vi: 'xa', hint: 'Xe, bánh xe, di chuyển.', examples: ['辆', '转', '轮'] },
  { id: 'ma', radical: '马', alt: '', pinyin: 'mǎ', tone: 'ma3', meaning: 'horse', vi: 'mã', hint: 'Ngựa; trong nhiều chữ dùng làm thành phần âm.', examples: ['妈', '吗', '码'] },
  { id: 'men', radical: '门', alt: '', pinyin: 'mén', tone: 'men2', meaning: 'door', vi: 'môn', hint: 'Cửa, cổng, không gian bên trong hoặc bên ngoài.', examples: ['问', '间', '闻'] },
  { id: 'shi_food', radical: '食', alt: '饣', pinyin: 'shí', tone: 'shi2', meaning: 'food', vi: 'thực', hint: 'Ăn uống, thực phẩm.', examples: ['饭', '饮', '饱'] },
  { id: 'yi', radical: '衣', alt: '衤', pinyin: 'yī', tone: 'yi1', meaning: 'clothing', vi: 'y', hint: 'Quần áo, vải, mặc.', examples: ['被', '裙', '补'] },
  { id: 'bei', radical: '贝', alt: '', pinyin: 'bèi', tone: 'bei4', meaning: 'shell/money', vi: 'bối', hint: 'Tiền, tài sản, mua bán.', examples: ['财', '货', '贵'] },
  { id: 'li', radical: '力', alt: '', pinyin: 'lì', tone: 'li4', meaning: 'power', vi: 'lực', hint: 'Sức mạnh, cố gắng, lao động.', examples: ['加', '动', '办'] },
  { id: 'dao', radical: '刀', alt: '刂', pinyin: 'dāo', tone: 'dao1', meaning: 'knife', vi: 'đao', hint: 'Dao, cắt, tách.', examples: ['到', '别', '分'] },
  { id: 'quan', radical: '犬', alt: '犭', pinyin: 'quǎn', tone: 'quan3', meaning: 'dog/animal', vi: 'khuyển', hint: 'Chó, thú vật, hành vi của động vật.', examples: ['狗', '猫', '狼'] },
  { id: 'niao', radical: '鸟', alt: '', pinyin: 'niǎo', tone: 'niao3', meaning: 'bird', vi: 'điểu', hint: 'Chim, loài bay.', examples: ['鸡', '鸭', '鹅'] },
  { id: 'yu_fish', radical: '鱼', alt: '', pinyin: 'yú', tone: 'yu2', meaning: 'fish', vi: 'ngư', hint: 'Cá, thủy sản.', examples: ['鲜', '鲤', '鲸'] },
  { id: 'shi_stone', radical: '石', alt: '', pinyin: 'shí', tone: 'shi2', meaning: 'stone', vi: 'thạch', hint: 'Đá, khoáng vật, vật cứng.', examples: ['码', '矿', '破'] },
  { id: 'mi_rice', radical: '米', alt: '', pinyin: 'mǐ', tone: 'mi3', meaning: 'rice', vi: 'mễ', hint: 'Gạo, hạt, lương thực.', examples: ['精', '粉', '粮'] },
  { id: 'zhu', radical: '竹', alt: '⺮', pinyin: 'zhú', tone: 'zhu2', meaning: 'bamboo', vi: 'trúc', hint: 'Tre, trúc, đồ làm từ tre.', examples: ['笑', '笔', '第'] },
  { id: 'er', radical: '耳', alt: '', pinyin: 'ěr', tone: 'er3', meaning: 'ear', vi: 'nhĩ', hint: 'Tai, nghe.', examples: ['听', '职', '聪'] },
  { id: 'mian', radical: '宀', alt: '', pinyin: 'mián', tone: 'mian2', meaning: 'roof', vi: 'miên', hint: 'Mái nhà, nơi ở, bên trong nhà.', examples: ['家', '字', '安'] },
  { id: 'chuo', radical: '辶', alt: '', pinyin: 'chuò', tone: 'chuo4', meaning: 'walk/go', vi: 'sước', hint: 'Di chuyển, đường đi, quá trình.', examples: ['这', '进', '远'] },
  { id: 'fu', radical: '阝', alt: '', pinyin: 'fù', tone: 'fu4', meaning: 'mound/city side', vi: 'phụ / ấp', hint: 'Bên trái thường liên quan đồi núi; bên phải thường liên quan thành ấp.', examples: ['院', '都', '阳'] },
  { id: 'guang', radical: '广', alt: '', pinyin: 'guǎng', tone: 'guang3', meaning: 'shelter', vi: 'nghiễm', hint: 'Mái che, công trình, không gian rộng.', examples: ['店', '座', '床'] },
  { id: 'ne', radical: '疒', alt: '', pinyin: 'nè', tone: 'ne4', meaning: 'sickness', vi: 'nạch', hint: 'Bệnh, đau, trạng thái cơ thể.', examples: ['病', '痛', '痒'] },
  { id: 'pu', radical: '攵', alt: '', pinyin: 'pū', tone: 'pu1', meaning: 'tap/strike', vi: 'phộc', hint: 'Đánh, tác động, hành động thay đổi.', examples: ['教', '放', '改'] },
  { id: 'qian', radical: '欠', alt: '', pinyin: 'qiàn', tone: 'qian4', meaning: 'lack/yawn', vi: 'khiếm', hint: 'Thiếu, nợ, hoặc liên quan đến hơi thở.', examples: ['欢', '歌', '欲'] },
  { id: 'ye', radical: '页', alt: '', pinyin: 'yè', tone: 'ye4', meaning: 'head/page', vi: 'hiệt', hint: 'Đầu, mặt, trang giấy trong chữ giản thể.', examples: ['题', '顾', '颜'] },
  { id: 'da', radical: '大', alt: '', pinyin: 'dà', tone: 'da4', meaning: 'big', vi: 'đại', hint: 'Lớn, to, mở rộng.', examples: ['天', '太', '奇'] },
  { id: 'xiao', radical: '小', alt: '⺌', pinyin: 'xiǎo', tone: 'xiao3', meaning: 'small', vi: 'tiểu', hint: 'Nhỏ, ít, nhỏ bé.', examples: ['少', '尖', '尘'] },
  { id: 'shi_show', radical: '示', alt: '礻', pinyin: 'shì', tone: 'shi4', meaning: 'show/altar', vi: 'thị', hint: 'Lễ nghi, tinh thần, chúc phúc.', examples: ['神', '礼', '祝'] },
  { id: 'he', radical: '禾', alt: '', pinyin: 'hé', tone: 'he2', meaning: 'grain', vi: 'hòa', hint: 'Lúa, cây lương thực, nông nghiệp.', examples: ['种', '秋', '秒'] },
  { id: 'yu_rain', radical: '雨', alt: '', pinyin: 'yǔ', tone: 'yu3', meaning: 'rain', vi: 'vũ', hint: 'Mưa, thời tiết, hiện tượng trời.', examples: ['雪', '雷', '露'] },
];

const CHINESE_TOPIC_GROUPS = [
  {
    id: 'people-body',
    label: 'People & Body',
    vi: 'Người và cơ thể',
    ids: ['ren', 'nu', 'zi', 'xin', 'shou', 'mu_eye', 'er', 'da', 'xiao'],
  },
  {
    id: 'speech-learning',
    label: 'Speech & Learning',
    vi: 'Ngôn ngữ và học tập',
    ids: ['kou', 'yan', 'shi_show', 'pu'],
  },
  {
    id: 'nature-time',
    label: 'Nature & Time',
    vi: 'Tự nhiên và thời gian',
    ids: ['shui', 'huo', 'mu_tree', 'ri', 'yue', 'tu', 'shan', 'tian', 'cao', 'yu_rain', 'he'],
  },
  {
    id: 'animals',
    label: 'Animals',
    vi: 'Động vật',
    ids: ['chong', 'ma', 'quan', 'niao', 'yu_fish'],
  },
  {
    id: 'home-place',
    label: 'Home & Places',
    vi: 'Nhà cửa và nơi chốn',
    ids: ['men', 'mian', 'guang', 'fu', 'chuo', 'che'],
  },
  {
    id: 'food-materials',
    label: 'Food & Materials',
    vi: 'Đồ ăn và vật liệu',
    ids: ['shi_food', 'yi', 'bei', 'jin', 'yu', 'mi_thread', 'shi_stone', 'mi_rice', 'zhu'],
  },
  {
    id: 'actions-state',
    label: 'Actions & State',
    vi: 'Hành động và trạng thái',
    ids: ['zu', 'li', 'dao', 'ne', 'qian', 'ye'],
  },
];

const CHINESE_COMPOSITIONS = [
  { topic: 'People & Family', parts: ['女', '子'], result: '好', pinyin: 'hǎo', meaning: 'good', note: 'Ghép nữ + con, học để nhớ chữ "hǎo".' },
  { topic: 'Nature', parts: ['日', '月'], result: '明', pinyin: 'míng', meaning: 'bright', note: 'Mặt trời + mặt trăng gợi ý sáng rõ.' },
  { topic: 'Actions', parts: ['亻', '木'], result: '休', pinyin: 'xiū', meaning: 'rest', note: 'Người dựa vào cây là nghỉ ngơi.' },
  { topic: 'Nature', parts: ['木', '木'], result: '林', pinyin: 'lín', meaning: 'woods', note: 'Hai cây thành rừng nhỏ.' },
  { topic: 'Nature', parts: ['氵', '青'], result: '清', pinyin: 'qīng', meaning: 'clear', note: 'Nước + xanh/trong gợi ý trong sạch.' },
  { topic: 'Speech', parts: ['口', '马'], result: '吗', pinyin: 'ma', meaning: 'question particle', note: 'Khẩu là nói; mã làm thành phần âm.' },
  { topic: 'Speech', parts: ['讠', '青'], result: '请', pinyin: 'qǐng', meaning: 'please/invite', note: 'Ngôn ngữ + thành phần âm qīng.' },
  { topic: 'Actions', parts: ['扌', '丁'], result: '打', pinyin: 'dǎ', meaning: 'hit/call', note: 'Tay + thành phần âm, thường là hành động bằng tay.' },
  { topic: 'Nature', parts: ['艹', '化'], result: '花', pinyin: 'huā', meaning: 'flower', note: 'Cỏ/cây + hóa, liên quan thực vật.' },
  { topic: 'Home', parts: ['宀', '女'], result: '安', pinyin: 'ān', meaning: 'peace', note: 'Dưới mái nhà gợi ý yên ổn.' },
  { topic: 'Money', parts: ['贝', '才'], result: '财', pinyin: 'cái', meaning: 'wealth', note: 'Bối gợi ý tiền/tài sản.' },
  { topic: 'Health', parts: ['疒', '丙'], result: '病', pinyin: 'bìng', meaning: 'illness', note: 'Nạch là bộ bệnh, gặp trong nhiều chữ về sức khỏe.' },
];

const CHINESE_SENTENCES = [
  { topic: 'Greeting', zh: '你好，我叫安。', en: 'Hello, my name is An.' },
  { topic: 'Home & Family', zh: '他在家休息。', en: 'He rests at home.' },
  { topic: 'Food & Drink', zh: '妈妈喝水。', en: 'Mom drinks water.' },
  { topic: 'Food & Drink', zh: '我喜欢喝茶。', en: 'I like drinking tea.' },
  { topic: 'Time & Weather', zh: '今天太阳很大。', en: 'The sun is bright today.' },
  { topic: 'School', zh: '明天我们去学校。', en: 'Tomorrow we go to school.' },
  { topic: 'Travel', zh: '请问，车站在哪里？', en: 'Excuse me, where is the station?' },
  { topic: 'School', zh: '她买了一个新书包。', en: 'She bought a new school bag.' },
  { topic: 'Nature', zh: '这条河很清。', en: 'This river is very clear.' },
  { topic: 'Home & Family', zh: '小猫在门口睡觉。', en: 'The kitten sleeps by the door.' },
  { topic: 'School', zh: '我用手写汉字。', en: 'I write Chinese characters by hand.' },
  { topic: 'Home & Family', zh: '桌子上有一本书。', en: 'There is a book on the table.' },
  { topic: 'Nature', zh: '秋天的山很漂亮。', en: 'The mountains in autumn are beautiful.' },
  { topic: 'Actions', zh: '他跑得很快。', en: 'He runs very fast.' },
  { topic: 'Food & Drink', zh: '我们一起吃饭。', en: 'We eat together.' },
  { topic: 'Health', zh: '医生说他没事。', en: 'The doctor says he is fine.' },
  { topic: 'Nature', zh: '她喜欢红色的花。', en: 'She likes red flowers.' },
  { topic: 'Time & Weather', zh: '雨下得很大。', en: 'It is raining heavily.' },
];

const CHINESE_DEFAULT_IDS = CHINESE_RADICALS.slice(0, 20).map(item => item.id);
const CHINESE_TOPIC_BY_RADICAL_ID = CHINESE_TOPIC_GROUPS.reduce((lookup, topic) => {
  topic.ids.forEach(id => {
    lookup[id] = topic;
  });
  return lookup;
}, {});
const CHINESE_NOTE_TOPICS = [
  { id: 'general', label: 'General', vi: 'Ghi chú chung', ids: [] },
  ...CHINESE_TOPIC_GROUPS,
  { id: 'sentences', label: 'Sentences', vi: 'Câu mẫu', ids: [] },
];

function getMarkedPinyin(text, fallback = '') {
  const pinyin = window.pinyinPro && window.pinyinPro.pinyin;
  if (!pinyin || !text) return fallback;

  try {
    const result = pinyin(text, {
      toneType: 'symbol',
      type: 'array',
      nonZh: 'removed',
    });
    return Array.isArray(result) && result.length ? result.join(' ') : fallback;
  } catch (err) {
    return fallback;
  }
}

function radicalPinyin(item) {
  return getMarkedPinyin(item.radical, item.pinyin) || item.pinyin;
}

function radicalTopic(item) {
  return CHINESE_TOPIC_BY_RADICAL_ID[item.id] || { id: 'other', label: 'Other', vi: 'Khác', ids: [] };
}

function groupRadicalsByTopic(radicals) {
  return CHINESE_TOPIC_GROUPS.map(topic => ({
    topic,
    items: radicals.filter(item => topic.ids.includes(item.id)),
  })).filter(group => group.items.length);
}

function groupItemsByTopic(items) {
  return items.reduce((groups, item) => {
    const key = item.topic || 'Other';
    if (!groups[key]) groups[key] = [];
    groups[key].push(item);
    return groups;
  }, {});
}

function sameSet(left, right) {
  return left.length === right.length && left.every(item => right.includes(item));
}

function speakChinese(text) {
  if (!text || !window.speechSynthesis || !window.SpeechSynthesisUtterance) return false;

  const utterance = new SpeechSynthesisUtterance(text);
  const voices = window.speechSynthesis.getVoices ? window.speechSynthesis.getVoices() : [];
  const preferredVoice = voices.find(voice => voice.lang && voice.lang.toLowerCase().startsWith('zh-cn'))
    || voices.find(voice => voice.lang && voice.lang.toLowerCase().startsWith('zh'));

  utterance.lang = preferredVoice ? preferredVoice.lang : 'zh-CN';
  utterance.rate = 0.82;
  utterance.pitch = 1;
  if (preferredVoice) utterance.voice = preferredVoice;

  window.speechSynthesis.cancel();
  window.speechSynthesis.speak(utterance);
  return true;
}

function normalizeSearchText(value) {
  return String(value || '')
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')
    .toLowerCase();
}

function sampleArray(items, count) {
  const pool = [...items];
  for (let i = pool.length - 1; i > 0; i -= 1) {
    const j = Math.floor(Math.random() * (i + 1));
    [pool[i], pool[j]] = [pool[j], pool[i]];
  }
  return pool.slice(0, count);
}

function buildChineseChoices(current) {
  const used = new Set([current.pinyin]);
  const distractors = sampleArray(
    CHINESE_RADICALS.filter(item => item.pinyin !== current.pinyin),
    CHINESE_RADICALS.length
  ).filter(item => {
    if (used.has(item.pinyin)) return false;
    used.add(item.pinyin);
    return true;
  }).slice(0, 3);

  return sampleArray([current, ...distractors], 4);
}

function ChineseLearningPage({ tweaks, onBackHome }) {
  const primary = tweaks.primaryColor;
  const [activeTab, setActiveTab] = React.useState('game');
  const [selectedIds, setSelectedIds] = React.useState(() => {
    try {
      const saved = JSON.parse(localStorage.getItem(CHINESE_RADICAL_STORAGE_KEY) || 'null');
      return Array.isArray(saved) && saved.length ? saved : CHINESE_DEFAULT_IDS;
    } catch (err) {
      return CHINESE_DEFAULT_IDS;
    }
  });
  const [duration, setDuration] = React.useState(60);
  const [timeLeft, setTimeLeft] = React.useState(60);
  const [running, setRunning] = React.useState(false);
  const [current, setCurrent] = React.useState(null);
  const [choices, setChoices] = React.useState([]);
  const [score, setScore] = React.useState({ correct: 0, total: 0, streak: 0 });
  const [feedback, setFeedback] = React.useState(null);
  const [query, setQuery] = React.useState('');
  const [sentenceIndex, setSentenceIndex] = React.useState(0);
  const [showSentenceEnglish, setShowSentenceEnglish] = React.useState(false);
  const [activeNoteTopic, setActiveNoteTopic] = React.useState('general');
  const [notes, setNotes] = React.useState(() => {
    try {
      const saved = JSON.parse(localStorage.getItem(CHINESE_NOTES_STORAGE_KEY) || '{}');
      return saved && typeof saved === 'object' && !Array.isArray(saved) ? saved : {};
    } catch (err) {
      return {};
    }
  });

  React.useEffect(() => {
    try {
      localStorage.setItem(CHINESE_RADICAL_STORAGE_KEY, JSON.stringify(selectedIds));
    } catch (err) {}
  }, [selectedIds]);

  React.useEffect(() => {
    try {
      localStorage.setItem(CHINESE_NOTES_STORAGE_KEY, JSON.stringify(notes));
    } catch (err) {}
  }, [notes]);

  React.useEffect(() => {
    if (!running || duration === 0) return undefined;
    const timer = window.setInterval(() => {
      setTimeLeft(value => {
        if (value <= 1) {
          window.clearInterval(timer);
          setRunning(false);
          setFeedback({ type: 'done', text: 'Time is up. Review the words below, then play again.' });
          return 0;
        }
        return value - 1;
      });
    }, 1000);
    return () => window.clearInterval(timer);
  }, [running, duration]);

  const selectedRadicals = CHINESE_RADICALS.filter(item => selectedIds.includes(item.id));
  const filteredRadicals = CHINESE_RADICALS.filter(item => {
    const topic = radicalTopic(item);
    const text = normalizeSearchText(`${item.radical} ${item.alt} ${item.pinyin} ${item.tone} ${item.meaning} ${item.vi} ${item.hint} ${topic.label} ${topic.vi} ${item.examples.join(' ')}`);
    return text.includes(normalizeSearchText(query.trim()));
  });

  const nextQuestion = React.useCallback((pool = selectedRadicals) => {
    if (!pool.length) {
      setCurrent(null);
      setChoices([]);
      return;
    }
    const next = pool[Math.floor(Math.random() * pool.length)];
    setCurrent(next);
    setChoices(buildChineseChoices(next));
  }, [selectedRadicals]);

  const startGame = () => {
    const pool = selectedRadicals.length ? selectedRadicals : CHINESE_RADICALS.slice(0, 10);
    setScore({ correct: 0, total: 0, streak: 0 });
    setFeedback(null);
    setTimeLeft(duration || 0);
    setRunning(true);
    nextQuestion(pool);
  };

  const answer = (choice) => {
    if (!running || !current) return;
    const ok = choice.pinyin === current.pinyin;
    setScore(prev => ({
      correct: prev.correct + (ok ? 1 : 0),
      total: prev.total + 1,
      streak: ok ? prev.streak + 1 : 0,
    }));
    setFeedback({
      type: ok ? 'correct' : 'wrong',
      text: ok
        ? `${current.radical}${current.alt ? ` / ${current.alt}` : ''} = ${radicalPinyin(current)} (${current.tone})`
        : `${current.radical}${current.alt ? ` / ${current.alt}` : ''} is ${radicalPinyin(current)} (${current.tone}), not ${radicalPinyin(choice)}.`,
    });
    window.setTimeout(() => nextQuestion(), 420);
  };

  const toggleRadical = (id) => {
    setSelectedIds(currentIds => (
      currentIds.includes(id)
        ? currentIds.filter(item => item !== id)
        : [...currentIds, id]
    ));
  };

  const updateNote = (topicId, value) => {
    setNotes(currentNotes => ({
      ...currentNotes,
      [topicId]: value,
    }));
  };

  const accuracy = score.total ? Math.round((score.correct / score.total) * 100) : 0;

  return (
    <main className="chinese-page" style={{
      minHeight: '100vh',
      padding: '118px clamp(18px, 4vw, 64px) 76px',
      background: 'var(--theme-bg)',
      color: 'var(--theme-text)',
      animation: 'fadeIn 0.3s ease',
    }}>
      <div style={{ width: '100%', maxWidth: 1220, margin: '0 auto' }}>
        <div className="chinese-header" style={{
          display: 'grid',
          gridTemplateColumns: 'minmax(0, 1fr) auto',
          alignItems: 'start',
          gap: 18,
          marginBottom: 22,
        }}>
          <div>
            <div style={{
              color: primary,
              fontSize: 12,
              fontWeight: 900,
              textTransform: 'uppercase',
              letterSpacing: 3,
              marginBottom: 10,
            }}>Chinese radicals practice</div>
            <h1 style={{
              margin: 0,
              color: 'var(--theme-strong)',
              fontSize: 'clamp(38px, 5vw, 68px)',
              lineHeight: 1.02,
              fontFamily: 'var(--display-font)',
            }}>Chinese</h1>
            <p style={{
              margin: '14px 0 0',
              maxWidth: 720,
              color: 'rgba(var(--theme-text-rgb),0.7)',
              fontSize: 16,
              lineHeight: 1.75,
            }}>
              Learn 50 common Chinese radicals by quick pinyin recall, vocabulary review, and character-building examples.
            </p>
          </div>
          <button type="button" onClick={onBackHome} style={chineseButtonStyle(primary, false)}>Back home</button>
        </div>

        <div className="chinese-tabs" style={{
          display: 'flex',
          gap: 10,
          flexWrap: 'wrap',
          marginBottom: 18,
        }}>
          {[
            ['game', 'Game play'],
            ['vocab', 'Vocabulary'],
            ['build', 'Build characters'],
            ['sentences', 'Sentences'],
            ['notes', 'Notes'],
          ].map(([id, label]) => (
            <button key={id} type="button" onClick={() => setActiveTab(id)} style={chineseButtonStyle(primary, activeTab === id)}>
              {label}
            </button>
          ))}
        </div>

        {activeTab === 'game' && (
          <ChineseGameTab
            primary={primary}
            selectedIds={selectedIds}
            selectedRadicals={selectedRadicals}
            duration={duration}
            setDuration={setDuration}
            timeLeft={timeLeft}
            running={running}
            current={current}
            choices={choices}
            score={score}
            accuracy={accuracy}
            feedback={feedback}
            startGame={startGame}
            answer={answer}
            toggleRadical={toggleRadical}
            setSelectedIds={setSelectedIds}
          />
        )}

        {activeTab === 'vocab' && (
          <ChineseVocabularyTab
            primary={primary}
            query={query}
            setQuery={setQuery}
            filteredRadicals={filteredRadicals}
          />
        )}

        {activeTab === 'build' && (
          <ChineseBuildTab primary={primary}/>
        )}

        {activeTab === 'sentences' && (
          <ChineseSentencesTab
            primary={primary}
            sentenceIndex={sentenceIndex}
            setSentenceIndex={setSentenceIndex}
            showEnglish={showSentenceEnglish}
            setShowEnglish={setShowSentenceEnglish}
          />
        )}

        {activeTab === 'notes' && (
          <ChineseNotesTab
            primary={primary}
            notes={notes}
            activeTopicId={activeNoteTopic}
            setActiveTopicId={setActiveNoteTopic}
            updateNote={updateNote}
          />
        )}
      </div>
    </main>
  );
}

function ChineseGameTab({
  primary,
  selectedIds,
  selectedRadicals,
  duration,
  setDuration,
  timeLeft,
  running,
  current,
  choices,
  score,
  accuracy,
  feedback,
  startGame,
  answer,
  toggleRadical,
  setSelectedIds,
}) {
  return (
    <div className="chinese-game-layout" style={{
      display: 'grid',
      gridTemplateColumns: 'minmax(0, 1.15fr) minmax(320px, 0.85fr)',
      gap: 18,
      alignItems: 'start',
    }}>
      <section style={chinesePanelStyle()}>
        <div style={{
          display: 'flex',
          justifyContent: 'space-between',
          gap: 14,
          alignItems: 'center',
          marginBottom: 18,
        }}>
          <div>
            <h2 style={{ margin: 0, color: 'var(--theme-strong)', fontSize: 24 }}>Quick pinyin game</h2>
            <p style={{ margin: '7px 0 0', color: 'rgba(var(--theme-text-rgb),0.62)', lineHeight: 1.6 }}>
              Look at the radical and choose its pinyin as fast as possible.
            </p>
          </div>
          <div style={{
            minWidth: 86,
            textAlign: 'center',
            padding: '12px 14px',
            borderRadius: 16,
            background: `${primary}12`,
            border: `1px solid ${primary}3D`,
            color: primary,
            fontWeight: 900,
          }}>{duration ? `${timeLeft}s` : 'No timer'}</div>
        </div>

        <div className="chinese-score-grid" style={{
          display: 'grid',
          gridTemplateColumns: 'repeat(3, minmax(0, 1fr))',
          gap: 10,
          marginBottom: 18,
        }}>
          <ChineseStat label="Correct" value={score.correct}/>
          <ChineseStat label="Accuracy" value={`${accuracy}%`}/>
          <ChineseStat label="Streak" value={score.streak}/>
        </div>

        <div style={{
          minHeight: 330,
          borderRadius: 24,
          background: 'rgba(var(--theme-bg-rgb),0.34)',
          border: '1px solid rgba(var(--theme-text-rgb),0.08)',
          display: 'grid',
          placeItems: 'center',
          padding: 24,
          textAlign: 'center',
        }}>
          {!running || !current ? (
            <div>
              <div style={{
                fontSize: 'clamp(56px, 9vw, 120px)',
                lineHeight: 1,
                color: primary,
                marginBottom: 14,
                fontFamily: '"PingFang SC", "Noto Sans CJK SC", var(--display-font)',
              }}>
                <ChineseSpeakText value="口" label="Play pronunciation for 口"/>
              </div>
              <div style={{
                margin: '-4px 0 16px',
                color: 'rgba(var(--theme-text-rgb),0.56)',
                fontSize: 15,
              }}>
                Handwriting <ChineseSpeakText value="口" label="Play pronunciation for 口" className="chinese-handwriting" style={{
                  color: 'var(--theme-strong)',
                  fontSize: 30,
                  fontFamily: CHINESE_HANDWRITING_FONT,
                  marginLeft: 8,
                }}/>
              </div>
              <p style={{ margin: '0 0 18px', color: 'rgba(var(--theme-text-rgb),0.68)', lineHeight: 1.7 }}>
                Select at least 4 radicals, choose a timer, then start.
              </p>
              <button
                type="button"
                disabled={selectedRadicals.length < 4}
                onClick={startGame}
                style={chineseButtonStyle(primary, true, selectedRadicals.length < 4)}
              >Start game</button>
            </div>
          ) : (
            <div style={{ width: '100%' }}>
              <div style={{
                color: 'rgba(var(--theme-text-rgb),0.58)',
                fontWeight: 800,
                textTransform: 'uppercase',
                letterSpacing: 2,
                fontSize: 12,
                marginBottom: 8,
              }}>What is this radical's pinyin?</div>
              <div style={{
                fontSize: 'clamp(78px, 13vw, 154px)',
                lineHeight: 1,
                color: 'var(--theme-strong)',
                fontFamily: '"PingFang SC", "Noto Sans CJK SC", var(--display-font)',
                marginBottom: 10,
              }}>
                <ChineseSpeakText value={current.radical} label={`Play pronunciation for ${current.radical}`}/>
              </div>
              <div style={{ color: primary, fontWeight: 800, marginBottom: 22 }}>
                {current.alt ? `Common form: ${current.alt}` : current.meaning}
              </div>
              <div style={{
                display: 'inline-flex',
                alignItems: 'center',
                gap: 10,
                padding: '8px 12px',
                borderRadius: 14,
                background: 'rgba(var(--theme-surface-rgb),0.3)',
                border: '1px solid rgba(var(--theme-text-rgb),0.08)',
                color: 'rgba(var(--theme-text-rgb),0.62)',
                marginBottom: 18,
              }}>
                <span style={{ fontSize: 12, fontWeight: 800, textTransform: 'uppercase' }}>Handwriting</span>
                <ChineseSpeakText
                  value={current.radical}
                  label={`Play pronunciation for ${current.radical}`}
                  className="chinese-handwriting"
                  style={{ color: primary, fontSize: 32, fontFamily: CHINESE_HANDWRITING_FONT }}
                />
              </div>
              <div className="chinese-choice-grid" style={{
                display: 'grid',
                gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
                gap: 12,
              }}>
                {choices.map(choice => (
                  <button key={`${choice.id}-${choice.pinyin}`} type="button" onClick={() => answer(choice)} style={{
                    ...chineseButtonStyle(primary, false),
                    minHeight: 62,
                    justifyContent: 'center',
                    fontSize: 20,
                    fontWeight: 900,
                  }}>{radicalPinyin(choice)}</button>
                ))}
              </div>
              {feedback && (
                <div style={{
                  marginTop: 16,
                  padding: '12px 14px',
                  borderRadius: 14,
                  color: feedback.type === 'wrong' ? '#FCA5A5' : feedback.type === 'done' ? '#FDE68A' : '#86EFAC',
                  background: feedback.type === 'wrong' ? 'rgba(239,68,68,0.1)' : feedback.type === 'done' ? 'rgba(245,158,11,0.1)' : 'rgba(34,197,94,0.1)',
                  border: `1px solid ${feedback.type === 'wrong' ? 'rgba(239,68,68,0.28)' : feedback.type === 'done' ? 'rgba(245,158,11,0.25)' : 'rgba(34,197,94,0.24)'}`,
                  fontWeight: 800,
                }}>{feedback.text}</div>
              )}
            </div>
          )}
        </div>
      </section>

      <aside style={chinesePanelStyle()}>
        <h2 style={{ margin: '0 0 14px', color: 'var(--theme-strong)', fontSize: 22 }}>Radicals and timer</h2>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, minmax(0, 1fr))', gap: 8, marginBottom: 16 }}>
          {[30, 60, 90, 0].map(value => (
            <button key={value} type="button" onClick={() => setDuration(value)} style={chineseSmallButtonStyle(primary, duration === value)}>
              {value ? `${value}s` : 'No limit'}
            </button>
          ))}
        </div>

        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginBottom: 12 }}>
          <button type="button" onClick={() => setSelectedIds(CHINESE_RADICALS.map(item => item.id))} style={chineseSmallButtonStyle(primary, false)}>All 50</button>
          <button type="button" onClick={() => setSelectedIds(CHINESE_DEFAULT_IDS)} style={chineseSmallButtonStyle(primary, false)}>Core 20</button>
          <button type="button" onClick={() => setSelectedIds([])} style={chineseSmallButtonStyle(primary, false)}>Clear</button>
        </div>

        <div style={{ marginBottom: 14 }}>
          <div style={{ color: 'rgba(var(--theme-text-rgb),0.62)', fontSize: 13, fontWeight: 800, marginBottom: 8 }}>Topics</div>
          <div className="chinese-topic-grid" style={{
            display: 'grid',
            gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
            gap: 8,
          }}>
            {CHINESE_TOPIC_GROUPS.map(topic => {
              const active = sameSet(selectedIds, topic.ids);
              return (
                <button key={topic.id} type="button" onClick={() => setSelectedIds(topic.ids)} title={topic.vi} style={{
                  ...chineseSmallButtonStyle(primary, active),
                  justifyContent: 'space-between',
                  textAlign: 'left',
                  minHeight: 46,
                }}>
                  <span>{topic.label}</span>
                  <span style={{ opacity: 0.68 }}>{topic.ids.length}</span>
                </button>
              );
            })}
          </div>
        </div>

        <div style={{ color: 'rgba(var(--theme-text-rgb),0.62)', fontSize: 13, marginBottom: 10 }}>
          Selected: {selectedIds.length}/50
        </div>
        <div className="chinese-radical-picker" style={{
          display: 'grid',
          gridTemplateColumns: 'repeat(5, minmax(0, 1fr))',
          gap: 8,
          maxHeight: 430,
          overflow: 'auto',
          paddingRight: 4,
        }}>
          {CHINESE_RADICALS.map(item => {
            const active = selectedIds.includes(item.id);
            const topic = radicalTopic(item);
            return (
              <button key={item.id} type="button" onClick={() => toggleRadical(item.id)} title={`${item.radical} ${radicalPinyin(item)}`} style={{
                minHeight: 58,
                borderRadius: 14,
                border: `1px solid ${active ? primary + '55' : 'rgba(var(--theme-text-rgb),0.08)'}`,
                background: active ? `${primary}13` : 'rgba(var(--theme-bg-rgb),0.34)',
                color: active ? primary : 'var(--theme-text)',
                cursor: 'pointer',
                display: 'flex',
                flexDirection: 'column',
                alignItems: 'center',
                justifyContent: 'center',
                gap: 3,
              }}>
                <span style={{ fontSize: 24, fontFamily: '"PingFang SC", "Noto Sans CJK SC", var(--display-font)' }}>{item.radical}</span>
                <span style={{ fontSize: 11, opacity: 0.75 }}>{radicalPinyin(item)}</span>
                <span style={{ fontSize: 9, opacity: 0.55, maxWidth: '100%', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{topic.label}</span>
              </button>
            );
          })}
        </div>
      </aside>
    </div>
  );
}

function ChineseVocabularyTab({ primary, query, setQuery, filteredRadicals }) {
  const groupedRadicals = groupRadicalsByTopic(filteredRadicals);

  return (
    <section style={chinesePanelStyle()}>
      <div style={{ display: 'flex', justifyContent: 'space-between', gap: 16, alignItems: 'center', marginBottom: 18, flexWrap: 'wrap' }}>
        <div>
          <h2 style={{ margin: 0, color: 'var(--theme-strong)', fontSize: 24 }}>50 radicals by topic</h2>
          <p style={{ margin: '7px 0 0', color: 'rgba(var(--theme-text-rgb),0.62)' }}>Review the exact words used in the game, grouped by learning topic.</p>
        </div>
        <input
          value={query}
          onChange={event => setQuery(event.target.value)}
          placeholder="Search radical, pinyin, meaning..."
          style={{
            minWidth: 280,
            maxWidth: 420,
            width: '100%',
            padding: '13px 15px',
            borderRadius: 14,
            border: '1px solid rgba(var(--theme-text-rgb),0.1)',
            background: 'rgba(var(--theme-bg-rgb),0.38)',
            color: 'var(--theme-text)',
            outline: 'none',
          }}
        />
      </div>

      {groupedRadicals.map(({ topic, items }) => (
        <section key={topic.id} style={{ marginTop: 20 }}>
          <div style={{
            display: 'flex',
            justifyContent: 'space-between',
            gap: 10,
            alignItems: 'baseline',
            marginBottom: 10,
          }}>
            <div>
              <h3 style={{ margin: 0, color: 'var(--theme-strong)', fontSize: 18 }}>{topic.label}</h3>
              <div style={{ color: 'rgba(var(--theme-text-rgb),0.52)', fontSize: 13, marginTop: 3 }}>{topic.vi}</div>
            </div>
            <div style={{ color: primary, fontWeight: 900, fontSize: 13 }}>{items.length} radicals</div>
          </div>
          <div className="chinese-vocab-grid" style={{
            display: 'grid',
            gridTemplateColumns: 'repeat(5, minmax(0, 1fr))',
            gap: 12,
          }}>
            {items.map(item => (
              <article key={item.id} style={{
                padding: 14,
                borderRadius: 18,
                background: 'rgba(var(--theme-bg-rgb),0.34)',
                border: '1px solid rgba(var(--theme-text-rgb),0.08)',
              }}>
                <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 8 }}>
                  <ChineseSpeakText value={item.radical} label={`Play pronunciation for ${item.radical}`} style={{
                    fontSize: 38,
                    lineHeight: 1,
                    color: primary,
                    fontFamily: '"PingFang SC", "Noto Sans CJK SC", var(--display-font)',
                  }}/>
                  {item.alt && <span style={{ color: 'rgba(var(--theme-text-rgb),0.42)', fontSize: 18 }}>{item.alt}</span>}
                </div>
                <div style={{
                  display: 'flex',
                  alignItems: 'center',
                  justifyContent: 'space-between',
                  gap: 8,
                  minHeight: 46,
                  padding: '7px 9px',
                  borderRadius: 13,
                  background: 'rgba(var(--theme-surface-rgb),0.22)',
                  border: '1px solid rgba(var(--theme-text-rgb),0.07)',
                  marginBottom: 10,
                }}>
                  <span style={{ color: 'rgba(var(--theme-text-rgb),0.48)', fontSize: 11, fontWeight: 800 }}>Handwriting</span>
                  <ChineseSpeakText
                    value={item.radical}
                    label={`Play pronunciation for ${item.radical}`}
                    className="chinese-handwriting"
                    style={{ color: primary, fontSize: 30, lineHeight: 1, fontFamily: CHINESE_HANDWRITING_FONT }}
                  />
                </div>
                <div style={{ color: 'var(--theme-strong)', fontWeight: 900, fontSize: 18 }}>{radicalPinyin(item)}</div>
                <div style={{ color: primary, fontSize: 12, fontWeight: 800, marginTop: 2 }}>{item.tone}</div>
                <div style={{ color: 'rgba(var(--theme-text-rgb),0.72)', fontSize: 13, marginTop: 8, lineHeight: 1.5 }}>
                  {item.meaning} / {item.vi}
                </div>
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: 5, marginTop: 10 }}>
                  {item.examples.map(word => {
                    const wordPinyin = getMarkedPinyin(word, '');
                    return (
                    <button key={word} type="button" onClick={() => speakChinese(word)} title={wordPinyin ? `${word} ${wordPinyin}` : word} style={{
                      padding: '4px 8px',
                      border: 0,
                      borderRadius: 9,
                      background: `${primary}10`,
                      color: primary,
                      fontSize: 15,
                      fontFamily: '"PingFang SC", "Noto Sans CJK SC", var(--display-font)',
                      display: 'inline-flex',
                      alignItems: 'center',
                      gap: 5,
                      cursor: 'pointer',
                    }}>
                      <span>{word}</span>
                      {wordPinyin && <span style={{ fontSize: 10, opacity: 0.7 }}>{wordPinyin}</span>}
                    </button>
                    );
                  })}
                </div>
              </article>
            ))}
          </div>
        </section>
      ))}
    </section>
  );
}

function ChineseBuildTab({ primary }) {
  const groupedCompositions = Object.entries(groupItemsByTopic(CHINESE_COMPOSITIONS));

  return (
    <section style={chinesePanelStyle()}>
      <div style={{ marginBottom: 18 }}>
        <h2 style={{ margin: 0, color: 'var(--theme-strong)', fontSize: 24 }}>How radicals build characters by topic</h2>
        <p style={{ margin: '7px 0 0', color: 'rgba(var(--theme-text-rgb),0.62)', lineHeight: 1.65 }}>
          Radicals often give a meaning hint. The other part may give sound, meaning, or both.
        </p>
      </div>

      {groupedCompositions.map(([topic, items]) => (
        <section key={topic} style={{ marginTop: 18 }}>
          <h3 style={{ margin: '0 0 10px', color: 'var(--theme-strong)', fontSize: 18 }}>{topic}</h3>
          <div className="chinese-build-grid" style={{
            display: 'grid',
            gridTemplateColumns: 'repeat(3, minmax(0, 1fr))',
            gap: 14,
          }}>
            {items.map(item => {
              const resultPinyin = getMarkedPinyin(item.result, item.pinyin);
              return (
              <article key={item.result} style={{
                padding: 18,
                borderRadius: 20,
                background: 'rgba(var(--theme-bg-rgb),0.34)',
                border: '1px solid rgba(var(--theme-text-rgb),0.08)',
              }}>
                <div style={{
                  display: 'grid',
                  gridTemplateColumns: '1fr auto 1fr auto 1fr',
                  alignItems: 'center',
                  gap: 8,
                  marginBottom: 14,
                }}>
                  {item.parts.map((part, index) => (
                    <React.Fragment key={`${item.result}-${part}-${index}`}>
                      <ChineseBigChar value={part} color={primary}/>
                      {index === 0 && <span style={{ color: 'rgba(var(--theme-text-rgb),0.42)', fontWeight: 900 }}>+</span>}
                    </React.Fragment>
                  ))}
                  <span style={{ color: 'rgba(var(--theme-text-rgb),0.42)', fontWeight: 900 }}>=</span>
                  <ChineseBigChar value={item.result} color="var(--theme-strong)"/>
                </div>
                <div style={{ color: primary, fontWeight: 900, marginBottom: 5 }}>{resultPinyin}</div>
                <div style={{ color: 'var(--theme-strong)', fontWeight: 800, marginBottom: 8 }}>{item.meaning}</div>
                <p style={{ margin: 0, color: 'rgba(var(--theme-text-rgb),0.68)', lineHeight: 1.65 }}>{item.note}</p>
              </article>
              );
            })}
          </div>
        </section>
      ))}
    </section>
  );
}

function ChineseSentencesTab({ primary, sentenceIndex, setSentenceIndex, showEnglish, setShowEnglish }) {
  const current = CHINESE_SENTENCES[sentenceIndex] || CHINESE_SENTENCES[0];
  const currentPinyin = getMarkedPinyin(current.zh, '');
  const groupedSentences = Object.entries(groupItemsByTopic(CHINESE_SENTENCES.map((item, index) => ({ ...item, index }))));

  const goToSentence = (nextIndex) => {
    const normalized = (nextIndex + CHINESE_SENTENCES.length) % CHINESE_SENTENCES.length;
    setSentenceIndex(normalized);
    setShowEnglish(false);
  };

  return (
    <section style={chinesePanelStyle()}>
      <div style={{ display: 'flex', justifyContent: 'space-between', gap: 16, alignItems: 'center', marginBottom: 18, flexWrap: 'wrap' }}>
        <div>
          <h2 style={{ margin: 0, color: 'var(--theme-strong)', fontSize: 24 }}>Sentence practice</h2>
          <p style={{ margin: '7px 0 0', color: 'rgba(var(--theme-text-rgb),0.62)', lineHeight: 1.65 }}>
            Practice with Chinese sentences, pinyin, and English meaning.
          </p>
        </div>
        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
          <button type="button" onClick={() => goToSentence(sentenceIndex - 1)} style={chineseSmallButtonStyle(primary, false)}>Previous</button>
          <button type="button" onClick={() => setShowEnglish(value => !value)} style={chineseSmallButtonStyle(primary, showEnglish)}>
            {showEnglish ? 'Hide English' : 'Show English'}
          </button>
          <button type="button" onClick={() => goToSentence(sentenceIndex + 1)} style={chineseSmallButtonStyle(primary, true)}>Next</button>
        </div>
      </div>

      <div className="chinese-sentence-layout" style={{
        display: 'grid',
        gridTemplateColumns: 'minmax(0, 0.95fr) minmax(0, 1.05fr)',
        gap: 16,
        alignItems: 'start',
      }}>
        <article style={{
          borderRadius: 22,
          padding: 22,
          background: 'rgba(var(--theme-bg-rgb),0.34)',
          border: `1px solid ${primary}30`,
        }}>
          <div style={{ color: primary, fontWeight: 900, fontSize: 13, marginBottom: 12 }}>
            Sentence {sentenceIndex + 1}/{CHINESE_SENTENCES.length}
          </div>
          <div style={{
            display: 'inline-flex',
            marginBottom: 12,
            padding: '5px 9px',
            borderRadius: 999,
            background: `${primary}14`,
            border: `1px solid ${primary}30`,
            color: primary,
            fontSize: 12,
            fontWeight: 900,
          }}>{current.topic}</div>
          <button type="button" onClick={() => speakChinese(current.zh)} aria-label={`Play pronunciation for ${current.zh}`} style={{
            border: 0,
            background: 'transparent',
            textAlign: 'left',
            width: '100%',
            cursor: 'pointer',
            color: 'var(--theme-strong)',
            fontSize: 'clamp(30px, 4.2vw, 54px)',
            lineHeight: 1.35,
            fontFamily: '"PingFang SC", "Noto Sans CJK SC", var(--display-font)',
            marginBottom: 14,
          }}>{current.zh}</button>
          <div style={{ color: primary, fontWeight: 800, lineHeight: 1.7, marginBottom: 16 }}>{currentPinyin}</div>
          <div style={{
            minHeight: 82,
            padding: '16px 18px',
            borderRadius: 18,
            background: 'rgba(var(--theme-surface-rgb),0.26)',
            border: '1px solid rgba(var(--theme-text-rgb),0.08)',
            color: showEnglish ? 'var(--theme-strong)' : 'rgba(var(--theme-text-rgb),0.34)',
            fontSize: 18,
            lineHeight: 1.55,
            fontWeight: 800,
          }}>{showEnglish ? current.en : 'English hidden'}</div>
        </article>

        <article style={{
          borderRadius: 22,
          padding: 22,
          background: 'rgba(var(--theme-bg-rgb),0.34)',
          border: '1px solid rgba(var(--theme-text-rgb),0.08)',
        }}>
          <h3 style={{ margin: '0 0 12px', color: 'var(--theme-strong)', fontSize: 18 }}>Handwriting view</h3>
          <button type="button" onClick={() => speakChinese(current.zh)} aria-label={`Play pronunciation for ${current.zh}`} className="chinese-handwriting" style={{
            minHeight: 174,
            width: '100%',
            border: 0,
            borderRadius: 20,
            padding: 20,
            display: 'grid',
            alignContent: 'center',
            textAlign: 'left',
            backgroundImage: 'linear-gradient(rgba(var(--theme-text-rgb),0.08) 1px, transparent 1px), linear-gradient(90deg, rgba(var(--theme-text-rgb),0.08) 1px, transparent 1px)',
            backgroundSize: '34px 34px',
            backgroundColor: 'rgba(var(--theme-surface-rgb),0.2)',
            color: primary,
            fontFamily: CHINESE_HANDWRITING_FONT,
            fontSize: 'clamp(32px, 5vw, 62px)',
            lineHeight: 1.45,
            cursor: 'pointer',
          }}>{current.zh}</button>
        </article>
      </div>

      {groupedSentences.map(([topic, items]) => (
        <section key={topic} style={{ marginTop: 18 }}>
          <h3 style={{ margin: '0 0 10px', color: 'var(--theme-strong)', fontSize: 18 }}>{topic}</h3>
          <div className="chinese-sentence-grid" style={{
            display: 'grid',
            gridTemplateColumns: 'repeat(3, minmax(0, 1fr))',
            gap: 12,
          }}>
            {items.map(item => (
              <button key={item.zh} type="button" onClick={() => goToSentence(item.index)} style={{
                textAlign: 'left',
                borderRadius: 18,
                border: `1px solid ${item.index === sentenceIndex ? primary + '55' : 'rgba(var(--theme-text-rgb),0.08)'}`,
                background: item.index === sentenceIndex ? `${primary}13` : 'rgba(var(--theme-bg-rgb),0.34)',
                color: 'var(--theme-text)',
                padding: 14,
                cursor: 'pointer',
              }}>
                <div style={{ color: item.index === sentenceIndex ? primary : 'var(--theme-strong)', fontSize: 22, fontWeight: 900, marginBottom: 6 }}>
                  {item.zh}
                </div>
                <div style={{ color: 'rgba(var(--theme-text-rgb),0.62)', lineHeight: 1.55 }}>{item.en}</div>
              </button>
            ))}
          </div>
        </section>
      ))}
    </section>
  );
}

function ChineseNotesTab({ primary, notes, activeTopicId, setActiveTopicId, updateNote }) {
  const activeTopic = CHINESE_NOTE_TOPICS.find(topic => topic.id === activeTopicId) || CHINESE_NOTE_TOPICS[0];
  const relatedRadicals = activeTopic.ids && activeTopic.ids.length
    ? CHINESE_RADICALS.filter(item => activeTopic.ids.includes(item.id))
    : [];
  const activeNote = notes[activeTopic.id] || '';
  const filledNotes = CHINESE_NOTE_TOPICS.filter(topic => (notes[topic.id] || '').trim());

  return (
    <section style={chinesePanelStyle()}>
      <div style={{ display: 'flex', justifyContent: 'space-between', gap: 16, alignItems: 'center', marginBottom: 18, flexWrap: 'wrap' }}>
        <div>
          <h2 style={{ margin: 0, color: 'var(--theme-strong)', fontSize: 24 }}>Notes by topic</h2>
          <p style={{ margin: '7px 0 0', color: 'rgba(var(--theme-text-rgb),0.62)', lineHeight: 1.65 }}>
            Notes are saved locally in this browser for quick review.
          </p>
        </div>
        <button
          type="button"
          onClick={() => updateNote(activeTopic.id, '')}
          disabled={!activeNote.trim()}
          style={chineseSmallButtonStyle(primary, false, !activeNote.trim())}
        >Clear topic</button>
      </div>

      <div className="chinese-notes-layout" style={{
        display: 'grid',
        gridTemplateColumns: 'minmax(240px, 0.35fr) minmax(0, 1fr)',
        gap: 16,
        alignItems: 'start',
      }}>
        <aside style={{
          borderRadius: 20,
          padding: 14,
          background: 'rgba(var(--theme-bg-rgb),0.34)',
          border: '1px solid rgba(var(--theme-text-rgb),0.08)',
        }}>
          <div style={{ color: 'rgba(var(--theme-text-rgb),0.54)', fontSize: 12, fontWeight: 900, marginBottom: 10, textTransform: 'uppercase' }}>
            Topics
          </div>
          <div className="chinese-note-topic-grid" style={{ display: 'grid', gap: 8 }}>
            {CHINESE_NOTE_TOPICS.map(topic => {
              const active = topic.id === activeTopic.id;
              const hasNote = Boolean((notes[topic.id] || '').trim());
              return (
                <button key={topic.id} type="button" onClick={() => setActiveTopicId(topic.id)} style={{
                  textAlign: 'left',
                  borderRadius: 14,
                  border: `1px solid ${active ? primary + '55' : 'rgba(var(--theme-text-rgb),0.08)'}`,
                  background: active ? `${primary}13` : 'rgba(var(--theme-surface-rgb),0.18)',
                  color: active ? primary : 'var(--theme-text)',
                  padding: '11px 12px',
                  cursor: 'pointer',
                }}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'center', fontWeight: 900 }}>
                    <span>{topic.label}</span>
                    {hasNote && <span style={{ color: primary, fontSize: 11 }}>saved</span>}
                  </div>
                  <div style={{ color: 'rgba(var(--theme-text-rgb),0.54)', fontSize: 12, marginTop: 3 }}>{topic.vi}</div>
                </button>
              );
            })}
          </div>
        </aside>

        <article style={{
          borderRadius: 20,
          padding: 18,
          background: 'rgba(var(--theme-bg-rgb),0.34)',
          border: `1px solid ${primary}24`,
        }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', gap: 12, alignItems: 'baseline', marginBottom: 12, flexWrap: 'wrap' }}>
            <div>
              <h3 style={{ margin: 0, color: 'var(--theme-strong)', fontSize: 21 }}>{activeTopic.label}</h3>
              <div style={{ color: 'rgba(var(--theme-text-rgb),0.56)', fontSize: 13, marginTop: 4 }}>{activeTopic.vi}</div>
            </div>
            <div style={{ color: primary, fontWeight: 900, fontSize: 13 }}>
              Auto saved
            </div>
          </div>

          {relatedRadicals.length > 0 && (
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginBottom: 14 }}>
              {relatedRadicals.map(item => (
                <button key={item.id} type="button" onClick={() => speakChinese(item.radical)} title={`${item.radical} ${radicalPinyin(item)}`} style={{
                  display: 'inline-flex',
                  alignItems: 'center',
                  gap: 7,
                  border: `1px solid ${primary}24`,
                  background: `${primary}0E`,
                  color: 'var(--theme-text)',
                  borderRadius: 999,
                  padding: '6px 9px',
                  cursor: 'pointer',
                }}>
                  <span style={{ color: primary, fontSize: 18, fontFamily: '"PingFang SC", "Noto Sans CJK SC", var(--display-font)' }}>{item.radical}</span>
                  <span style={{ fontSize: 12 }}>{radicalPinyin(item)}</span>
                </button>
              ))}
            </div>
          )}

          <textarea
            value={activeNote}
            onChange={event => updateNote(activeTopic.id, event.target.value)}
            placeholder={`Write notes for ${activeTopic.label}...`}
            style={{
              width: '100%',
              minHeight: 260,
              resize: 'vertical',
              borderRadius: 18,
              padding: 16,
              border: '1px solid rgba(var(--theme-text-rgb),0.1)',
              background: 'rgba(var(--theme-surface-rgb),0.2)',
              color: 'var(--theme-text)',
              outline: 'none',
              lineHeight: 1.65,
              fontSize: 15,
            }}
          />
        </article>
      </div>

      {filledNotes.length > 0 && (
        <div style={{ marginTop: 18 }}>
          <h3 style={{ margin: '0 0 10px', color: 'var(--theme-strong)', fontSize: 18 }}>Saved notes</h3>
          <div className="chinese-note-summary-grid" style={{
            display: 'grid',
            gridTemplateColumns: 'repeat(3, minmax(0, 1fr))',
            gap: 12,
          }}>
            {filledNotes.map(topic => (
              <button key={topic.id} type="button" onClick={() => setActiveTopicId(topic.id)} style={{
                textAlign: 'left',
                borderRadius: 16,
                border: '1px solid rgba(var(--theme-text-rgb),0.08)',
                background: 'rgba(var(--theme-bg-rgb),0.34)',
                color: 'var(--theme-text)',
                padding: 13,
                cursor: 'pointer',
              }}>
                <div style={{ color: primary, fontWeight: 900, marginBottom: 6 }}>{topic.label}</div>
                <div style={{
                  color: 'rgba(var(--theme-text-rgb),0.62)',
                  lineHeight: 1.55,
                  display: '-webkit-box',
                  WebkitLineClamp: 3,
                  WebkitBoxOrient: 'vertical',
                  overflow: 'hidden',
                }}>{notes[topic.id]}</div>
              </button>
            ))}
          </div>
        </div>
      )}
    </section>
  );
}

function ChineseSpeakText({ value, label, className = '', style = {} }) {
  return (
    <button
      type="button"
      className={className}
      onClick={() => speakChinese(value)}
      aria-label={label || `Play pronunciation for ${value}`}
      style={{
        display: 'inline',
        border: 0,
        padding: 0,
        margin: 0,
        background: 'transparent',
        color: 'inherit',
        font: 'inherit',
        fontSize: 'inherit',
        lineHeight: 'inherit',
        cursor: 'pointer',
        ...style,
      }}
    >
      {value}
    </button>
  );
}

function ChineseBigChar({ value, color }) {
  return (
    <button type="button" onClick={() => speakChinese(value)} aria-label={`Play pronunciation for ${value}`} style={{
      minHeight: 62,
      width: '100%',
      borderRadius: 16,
      display: 'grid',
      placeItems: 'center',
      background: 'rgba(var(--theme-surface-rgb),0.24)',
      border: '1px solid rgba(var(--theme-text-rgb),0.08)',
      color,
      fontSize: 34,
      fontWeight: 900,
      fontFamily: '"PingFang SC", "Noto Sans CJK SC", var(--display-font)',
      cursor: 'pointer',
    }}>{value}</button>
  );
}

function ChineseStat({ label, value }) {
  return (
    <div style={{
      padding: '12px 14px',
      borderRadius: 16,
      background: 'rgba(var(--theme-bg-rgb),0.34)',
      border: '1px solid rgba(var(--theme-text-rgb),0.08)',
    }}>
      <div style={{ color: 'rgba(var(--theme-text-rgb),0.48)', fontSize: 12, fontWeight: 800, textTransform: 'uppercase' }}>{label}</div>
      <div style={{ color: 'var(--theme-strong)', fontSize: 24, fontWeight: 900, marginTop: 3 }}>{value}</div>
    </div>
  );
}

function chinesePanelStyle() {
  return {
    padding: 22,
    borderRadius: 22,
    background: 'rgba(var(--theme-surface-rgb),0.22)',
    border: '1px solid rgba(var(--theme-text-rgb),0.08)',
    boxShadow: '0 18px 50px rgba(0,0,0,0.14)',
  };
}

function chineseButtonStyle(primary, active, disabled = false) {
  return {
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    gap: 8,
    padding: '11px 16px',
    minHeight: 42,
    borderRadius: 14,
    border: `1px solid ${active ? primary + '55' : 'rgba(var(--theme-text-rgb),0.10)'}`,
    background: active ? `${primary}16` : 'rgba(var(--theme-surface-rgb),0.34)',
    color: disabled ? 'rgba(var(--theme-text-rgb),0.34)' : active ? primary : 'var(--theme-text)',
    cursor: disabled ? 'not-allowed' : 'pointer',
    fontWeight: 800,
    transition: 'background 0.22s ease, border-color 0.22s ease, color 0.22s ease, transform 0.22s ease',
  };
}

function chineseSmallButtonStyle(primary, active, disabled = false) {
  return {
    ...chineseButtonStyle(primary, active, disabled),
    minHeight: 36,
    padding: '8px 10px',
    fontSize: 12,
  };
}

window.ChineseLearningPage = ChineseLearningPage;
