[{"data":1,"prerenderedAt":13304},["ShallowReactive",2],{"navigation_docs":3,"-tu-hoc-nuxt-4-practical-todo-app":420,"-tu-hoc-nuxt-4-practical-todo-app-surround":13299},[4,153],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":26},"Phỏng vấn .NET","i-lucide-briefcase","\u002Fphong-van","1.phong-van",[10,27,66,111,128],{"title":11,"icon":12,"path":13,"stem":14,"children":15,"page":26},"Tổng quan","i-lucide-compass","\u002Fphong-van\u002Ftong-quan","1.phong-van\u002F1.tong-quan",[16,21],{"title":17,"path":18,"stem":19,"icon":20},"Thị trường HCM","\u002Fphong-van\u002Ftong-quan\u002Fthi-truong","1.phong-van\u002F1.tong-quan\u002F1.thi-truong","i-lucide-trending-up",{"title":22,"path":23,"stem":24,"icon":25},"Lộ trình 7 ngày","\u002Fphong-van\u002Ftong-quan\u002Flo-trinh","1.phong-van\u002F1.tong-quan\u002F2.lo-trinh","i-lucide-calendar-days",false,{"title":22,"icon":25,"path":28,"stem":29,"children":30,"page":26},"\u002Fphong-van\u002Flo-trinh","1.phong-van\u002F2.lo-trinh",[31,36,41,46,51,56,61],{"title":32,"path":33,"stem":34,"icon":35},"Ngày 1 — C# cơ bản","\u002Fphong-van\u002Flo-trinh\u002Fngay-1-csharp-co-ban","1.phong-van\u002F2.lo-trinh\u002F1.ngay-1-csharp-co-ban","i-lucide-1",{"title":37,"path":38,"stem":39,"icon":40},"Ngày 2 — OOP và SOLID","\u002Fphong-van\u002Flo-trinh\u002Fngay-2-oop-solid","1.phong-van\u002F2.lo-trinh\u002F2.ngay-2-oop-solid","i-lucide-2",{"title":42,"path":43,"stem":44,"icon":45},"Ngày 3 — C# nâng cao","\u002Fphong-van\u002Flo-trinh\u002Fngay-3-csharp-nang-cao","1.phong-van\u002F2.lo-trinh\u002F3.ngay-3-csharp-nang-cao","i-lucide-3",{"title":47,"path":48,"stem":49,"icon":50},"Ngày 4 — Database và SQL","\u002Fphong-van\u002Flo-trinh\u002Fngay-4-database-sql","1.phong-van\u002F2.lo-trinh\u002F4.ngay-4-database-sql","i-lucide-4",{"title":52,"path":53,"stem":54,"icon":55},"Ngày 5 — ASP.NET Core và EF Core","\u002Fphong-van\u002Flo-trinh\u002Fngay-5-aspnet-efcore","1.phong-van\u002F2.lo-trinh\u002F5.ngay-5-aspnet-efcore","i-lucide-5",{"title":57,"path":58,"stem":59,"icon":60},"Ngày 6 — DSA coding","\u002Fphong-van\u002Flo-trinh\u002Fngay-6-dsa","1.phong-van\u002F2.lo-trinh\u002F6.ngay-6-dsa","i-lucide-6",{"title":62,"path":63,"stem":64,"icon":65},"Ngày 7 — Mock và HR","\u002Fphong-van\u002Flo-trinh\u002Fngay-7-mock-interview","1.phong-van\u002F2.lo-trinh\u002F7.ngay-7-mock-interview","i-lucide-7",{"title":67,"icon":68,"path":69,"stem":70,"children":71,"page":26},"Ngân hàng câu hỏi","i-lucide-database","\u002Fphong-van\u002Fngan-hang-cau-hoi","1.phong-van\u002F3.ngan-hang-cau-hoi",[72,77,82,87,92,97,102,107],{"title":73,"path":74,"stem":75,"icon":76},"C# (60+ câu)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fcsharp","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F1.csharp","i-lucide-hash",{"title":78,"path":79,"stem":80,"icon":81},"OOP và SOLID (40+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Foop","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F2.oop","i-lucide-shapes",{"title":83,"path":84,"stem":85,"icon":86},"SQL (40+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fsql","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F3.sql","i-lucide-database-zap",{"title":88,"path":89,"stem":90,"icon":91},"ASP.NET và EF (50+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Faspnet-ef","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F4.aspnet-ef","i-lucide-server",{"title":93,"path":94,"stem":95,"icon":96},"DSA và Coding (30+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fdsa-coding","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F5.dsa-coding","i-lucide-binary",{"title":98,"path":99,"stem":100,"icon":101},"HR và Soft skill (25+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fhr-behavior","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F6.hr-behavior","i-lucide-users",{"title":103,"path":104,"stem":105,"icon":106},"Design Pattern (55+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fdesign-pattern","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F7.design-pattern","i-lucide-puzzle",{"title":108,"path":109,"stem":110,"icon":91},"ASP.NET MVC và Web API","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Faspnet-mvc-webapi","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F8.aspnet-mvc-webapi",{"title":112,"itle":113,"icon":114,"path":115,"stem":116,"children":117,"page":26},"Tai Lieu","Tài liệu và Cheat sheet","i-lucide-book-marked","\u002Fphong-van\u002Ftai-lieu","1.phong-van\u002F4.tai-lieu",[118,123],{"title":119,"path":120,"stem":121,"icon":122},"Cheat Sheet","\u002Fphong-van\u002Ftai-lieu\u002Fcheatsheet","1.phong-van\u002F4.tai-lieu\u002F1.cheatsheet","i-lucide-clipboard-list",{"title":124,"path":125,"stem":126,"icon":127},"Nguồn tham khảo","\u002Fphong-van\u002Ftai-lieu\u002Fnguon","1.phong-van\u002F4.tai-lieu\u002F2.nguon","i-lucide-link",{"title":129,"icon":130,"path":131,"stem":132,"children":133,"page":26},"Phỏng vấn English","i-lucide-globe","\u002Fphong-van\u002Fenglish-interview","1.phong-van\u002F5.english-interview",[134,138,143,148],{"title":135,"path":136,"stem":137,"icon":130},"Tổng quan English","\u002Fphong-van\u002Fenglish-interview\u002Ftong-quan","1.phong-van\u002F5.english-interview\u002F1.tong-quan",{"title":139,"path":140,"stem":141,"icon":142},"Vocabulary","\u002Fphong-van\u002Fenglish-interview\u002Fvocabulary","1.phong-van\u002F5.english-interview\u002F2.vocabulary","i-lucide-book-a",{"title":144,"path":145,"stem":146,"icon":147},"Technical Q và A (EN)","\u002Fphong-van\u002Fenglish-interview\u002Ftechnical-en","1.phong-van\u002F5.english-interview\u002F3.technical-en","i-lucide-code",{"title":149,"path":150,"stem":151,"icon":152},"Behavioral (EN)","\u002Fphong-van\u002Fenglish-interview\u002Fbehavioral-en","1.phong-van\u002F5.english-interview\u002F4.behavioral-en","i-lucide-message-square-text",{"title":154,"icon":155,"path":156,"stem":157,"children":158},"Tự học","i-lucide-graduation-cap","\u002Ftu-hoc","2.tu-hoc",[159,161,287,310,333,357,384],{"title":154,"path":156,"stem":160,"icon":155},"2.tu-hoc\u002Findex",{"title":162,"icon":68,"path":163,"stem":164,"children":165},"Database","\u002Ftu-hoc\u002Fdatabase","2.tu-hoc\u002F1.database\u002Findex",[166,167,197,222,243,248],{"title":162,"path":163,"stem":164,"icon":68},{"title":168,"icon":91,"path":169,"stem":170,"children":171,"page":26},"SQL Server","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server","2.tu-hoc\u002F1.database\u002F2.sql-server",[172,177,182,187,192],{"title":173,"path":174,"stem":175,"icon":176},"1. Giới thiệu và Cài đặt","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F1.gioi-thieu-cai-dat","i-lucide-play-circle",{"title":178,"path":179,"stem":180,"icon":181},"2. Cú pháp cơ bản","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Fcu-phap-co-ban","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F2.cu-phap-co-ban","i-lucide-code-2",{"title":183,"path":184,"stem":185,"icon":186},"3. Truy vấn nâng cao","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Ftruy-van-nang-cao","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F3.truy-van-nang-cao","i-lucide-git-merge",{"title":188,"path":189,"stem":190,"icon":191},"4. Đặc thù và Performance","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Fdac-thu-performance","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F4.dac-thu-performance","i-lucide-gauge",{"title":193,"path":194,"stem":195,"icon":196},"5. Q và A phỏng vấn","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Fcau-hoi-phong-van","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F5.cau-hoi-phong-van","i-lucide-message-circle-question",{"title":198,"icon":199,"path":200,"stem":201,"children":202,"page":26},"PostgreSQL","i-simple-icons-postgresql","\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql","2.tu-hoc\u002F1.database\u002F3.postgresql",[203,206,209,212,215,218],{"title":173,"path":204,"stem":205,"icon":176},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F1.gioi-thieu-cai-dat",{"title":178,"path":207,"stem":208,"icon":181},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Fcu-phap-co-ban","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F2.cu-phap-co-ban",{"title":183,"path":210,"stem":211,"icon":186},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Ftruy-van-nang-cao","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F3.truy-van-nang-cao",{"title":188,"path":213,"stem":214,"icon":191},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Fdac-thu-performance","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F4.dac-thu-performance",{"title":193,"path":216,"stem":217,"icon":196},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Fcau-hoi-phong-van","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F5.cau-hoi-phong-van",{"title":219,"path":220,"stem":221,"icon":96},"6. Tích hợp C# và Postgres","\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Ftich-hop-csharp","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F6.tich-hop-csharp",{"title":223,"icon":224,"path":225,"stem":226,"children":227,"page":26},"MySQL","i-simple-icons-mysql","\u002Ftu-hoc\u002Fdatabase\u002Fmysql","2.tu-hoc\u002F1.database\u002F4.mysql",[228,231,234,237,240],{"title":173,"path":229,"stem":230,"icon":176},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F1.database\u002F4.mysql\u002F1.gioi-thieu-cai-dat",{"title":178,"path":232,"stem":233,"icon":181},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Fcu-phap-co-ban","2.tu-hoc\u002F1.database\u002F4.mysql\u002F2.cu-phap-co-ban",{"title":183,"path":235,"stem":236,"icon":186},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Ftruy-van-nang-cao","2.tu-hoc\u002F1.database\u002F4.mysql\u002F3.truy-van-nang-cao",{"title":188,"path":238,"stem":239,"icon":191},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Fdac-thu-performance","2.tu-hoc\u002F1.database\u002F4.mysql\u002F4.dac-thu-performance",{"title":193,"path":241,"stem":242,"icon":196},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Fcau-hoi-phong-van","2.tu-hoc\u002F1.database\u002F4.mysql\u002F5.cau-hoi-phong-van",{"title":244,"path":245,"stem":246,"icon":247},"So sánh 3 RDBMS","\u002Ftu-hoc\u002Fdatabase\u002Fso-sanh","2.tu-hoc\u002F1.database\u002F5.so-sanh","i-lucide-columns-3",{"title":249,"path":250,"stem":251,"children":252,"icon":254},"Supabase (BaaS)","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase","2.tu-hoc\u002F1.database\u002F6.supabase\u002Findex",[253,255,259,264,269,274,278,283],{"title":249,"path":250,"stem":251,"icon":254},"i-simple-icons-supabase",{"title":256,"path":257,"stem":258,"icon":68},"1. Database","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fdatabase","2.tu-hoc\u002F1.database\u002F6.supabase\u002F1.database",{"title":260,"path":261,"stem":262,"icon":263},"2. Authentication","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fauthentication","2.tu-hoc\u002F1.database\u002F6.supabase\u002F2.authentication","i-lucide-key",{"title":265,"path":266,"stem":267,"icon":268},"3. Storage & Realtime","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fstorage-realtime","2.tu-hoc\u002F1.database\u002F6.supabase\u002F3.storage-realtime","i-lucide-zap",{"title":270,"path":271,"stem":272,"icon":273},"4. Edge Functions & AI","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Ffunctions-ai","2.tu-hoc\u002F1.database\u002F6.supabase\u002F4.functions-ai","i-lucide-brain",{"title":275,"path":276,"stem":277,"icon":181},"5. API & SDKs","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fapi-sdk","2.tu-hoc\u002F1.database\u002F6.supabase\u002F5.api-sdk",{"title":279,"path":280,"stem":281,"icon":282},"6. Platform & Deploy","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fplatform-deploy","2.tu-hoc\u002F1.database\u002F6.supabase\u002F6.platform-deploy","i-lucide-cloud",{"title":284,"path":285,"stem":286,"icon":196},"7. Interview & Q&A","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Finterview","2.tu-hoc\u002F1.database\u002F6.supabase\u002F7.interview",{"title":288,"icon":289,"path":290,"stem":291,"children":292},"TypeScript","i-simple-icons-typescript","\u002Ftu-hoc\u002Ftypescript","2.tu-hoc\u002F2.typescript\u002Findex",[293,294,297,301,306],{"title":288,"path":290,"stem":291,"icon":289},{"title":173,"path":295,"stem":296,"icon":176},"\u002Ftu-hoc\u002Ftypescript\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F2.typescript\u002F1.gioi-thieu-cai-dat",{"title":298,"path":299,"stem":300,"icon":81},"2. Type System","\u002Ftu-hoc\u002Ftypescript\u002Ftype-system","2.tu-hoc\u002F2.typescript\u002F2.type-system",{"title":302,"path":303,"stem":304,"icon":305},"3. Advanced types","\u002Ftu-hoc\u002Ftypescript\u002Fadvanced-types","2.tu-hoc\u002F2.typescript\u002F3.advanced-types","i-lucide-braces",{"title":307,"path":308,"stem":309,"icon":196},"4. Q và A phỏng vấn","\u002Ftu-hoc\u002Ftypescript\u002Fcau-hoi-phong-van","2.tu-hoc\u002F2.typescript\u002F4.cau-hoi-phong-van",{"title":311,"icon":81,"path":312,"stem":313,"children":314},"OOP","\u002Ftu-hoc\u002Foop","2.tu-hoc\u002F3.oop\u002Findex",[315,316,321,326,330],{"title":311,"path":312,"stem":313,"icon":81},{"title":317,"path":318,"stem":319,"icon":320},"1. Bốn tính chất","\u002Ftu-hoc\u002Foop\u002F4-tinh-chat","2.tu-hoc\u002F3.oop\u002F1.4-tinh-chat","i-lucide-circle-dot",{"title":322,"path":323,"stem":324,"icon":325},"2. SOLID","\u002Ftu-hoc\u002Foop\u002Fsolid","2.tu-hoc\u002F3.oop\u002F2.solid","i-lucide-shield",{"title":327,"path":328,"stem":329,"icon":106},"3. Design Pattern","\u002Ftu-hoc\u002Foop\u002Fdesign-pattern","2.tu-hoc\u002F3.oop\u002F3.design-pattern",{"title":307,"path":331,"stem":332,"icon":196},"\u002Ftu-hoc\u002Foop\u002Fcau-hoi-phong-van","2.tu-hoc\u002F3.oop\u002F4.cau-hoi-phong-van",{"title":334,"icon":305,"path":335,"stem":336,"children":337},"Generic","\u002Ftu-hoc\u002Fgeneric","2.tu-hoc\u002F4.generic\u002Findex",[338,339,344,349,354],{"title":334,"path":335,"stem":336,"icon":305},{"title":340,"path":341,"stem":342,"icon":343},"1. Khái niệm và cú pháp","\u002Ftu-hoc\u002Fgeneric\u002Fkhai-niem","2.tu-hoc\u002F4.generic\u002F1.khai-niem","i-lucide-book",{"title":345,"path":346,"stem":347,"icon":348},"2. Constraint và Variance","\u002Ftu-hoc\u002Fgeneric\u002Fconstraint-variance","2.tu-hoc\u002F4.generic\u002F2.constraint-variance","i-lucide-filter",{"title":350,"path":351,"stem":352,"icon":353},"3. C# vs TS","\u002Ftu-hoc\u002Fgeneric\u002Fso-sanh","2.tu-hoc\u002F4.generic\u002F3.so-sanh","i-lucide-git-compare",{"title":307,"path":355,"stem":356,"icon":196},"\u002Ftu-hoc\u002Fgeneric\u002Fcau-hoi-phong-van","2.tu-hoc\u002F4.generic\u002F4.cau-hoi-phong-van",{"title":358,"icon":359,"path":360,"stem":361,"children":362},"Nuxt 4","i-simple-icons-nuxt","\u002Ftu-hoc\u002Fnuxt-4","2.tu-hoc\u002F5.nuxt-4\u002Findex",[363,364,367,372,376,379],{"title":358,"path":360,"stem":361,"icon":359},{"title":173,"path":365,"stem":366,"icon":176},"\u002Ftu-hoc\u002Fnuxt-4\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F5.nuxt-4\u002F1.gioi-thieu-cai-dat",{"title":368,"path":369,"stem":370,"icon":371},"2. Routing và Rendering","\u002Ftu-hoc\u002Fnuxt-4\u002Frouting-rendering","2.tu-hoc\u002F5.nuxt-4\u002F2.routing-rendering","i-lucide-route",{"title":373,"path":374,"stem":375,"icon":91},"3. Data và Server","\u002Ftu-hoc\u002Fnuxt-4\u002Fdata-server","2.tu-hoc\u002F5.nuxt-4\u002F3.data-server",{"title":307,"path":377,"stem":378,"icon":196},"\u002Ftu-hoc\u002Fnuxt-4\u002Fcau-hoi-phong-van","2.tu-hoc\u002F5.nuxt-4\u002F4.cau-hoi-phong-van",{"title":380,"path":381,"stem":382,"icon":383},"5. Practical Todo App","\u002Ftu-hoc\u002Fnuxt-4\u002Fpractical-todo-app","2.tu-hoc\u002F5.nuxt-4\u002F5.practical-todo-app","i-lucide-check-square",{"title":385,"path":386,"stem":387,"children":388,"icon":390},"Tailwind CSS","\u002Ftu-hoc\u002Ftailwindcss","2.tu-hoc\u002F6.tailwindcss\u002Findex",[389,391,396,401,406,411,416],{"title":385,"path":386,"stem":387,"icon":390},"i-simple-icons-tailwindcss",{"title":392,"path":393,"stem":394,"icon":395},"1. Layout & Spacing","\u002Ftu-hoc\u002Ftailwindcss\u002Flayout-spacing","2.tu-hoc\u002F6.tailwindcss\u002F1.layout-spacing","i-lucide-layout",{"title":397,"path":398,"stem":399,"icon":400},"2. Typography & Colors","\u002Ftu-hoc\u002Ftailwindcss\u002Ftypography-colors","2.tu-hoc\u002F6.tailwindcss\u002F2.typography-colors","i-lucide-type",{"title":402,"path":403,"stem":404,"icon":405},"3. Interactive & Effects","\u002Ftu-hoc\u002Ftailwindcss\u002Finteractive-effects","2.tu-hoc\u002F6.tailwindcss\u002F3.interactive-effects","i-lucide-sparkles",{"title":407,"path":408,"stem":409,"icon":410},"4. Responsive & Dark Mode","\u002Ftu-hoc\u002Ftailwindcss\u002Fresponsive-darkmode","2.tu-hoc\u002F6.tailwindcss\u002F4.responsive-darkmode","i-lucide-monitor-smartphone",{"title":412,"path":413,"stem":414,"icon":415},"5. Customization & Theme","\u002Ftu-hoc\u002Ftailwindcss\u002Fcustomization","2.tu-hoc\u002F6.tailwindcss\u002F5.customization","i-lucide-settings-2",{"title":417,"path":418,"stem":419,"icon":181},"6. Practical Projects","\u002Ftu-hoc\u002Ftailwindcss\u002Fpractical-projects","2.tu-hoc\u002F6.tailwindcss\u002F6.practical-projects",{"id":421,"title":422,"body":423,"description":13292,"extension":13293,"links":13294,"meta":13295,"navigation":13296,"path":381,"seo":13297,"stem":382,"__hash__":13298},"docs\u002F2.tu-hoc\u002F5.nuxt-4\u002F5.practical-todo-app.md","Practical: Xây dựng Todo App với Nuxt 4",{"type":424,"value":425,"toc":13244},"minimark",[426,431,448,465,470,611,615,625,628,632,635,690,697,703,707,715,958,1004,1012,1218,1237,1270,1280,1282,1286,1292,1487,1491,1500,1523,1541,1547,1584,1609,1621,1623,1630,1635,3603,3607,3660,3667,3686,3694,3716,3722,3747,3757,3782,3788,3813,3815,3819,3823,3828,4858,4861,4868,4905,4912,4947,4954,4977,4984,5012,5019,5024,5026,5030,5035,6823,6826,6833,6853,6860,6880,6889,6919,6926,6934,6941,6989,6996,7004,7011,7019,7026,7051,7059,7077,7079,7083,7088,7833,7836,7843,7961,7995,8002,8027,8034,8053,8055,8059,8064,9467,9470,9477,9491,9498,9526,9533,9592,9697,9702,9722,9724,9731,9736,10605,10608,10614,10634,10640,10701,10706,10740,10742,10749,10756,11035,11042,11064,11066,11070,11075,11653,11656,11661,11680,11687,11708,11710,11717,11722,11835,11869,11871,11875,11991,11995,12170,12172,12176,12179,12183,12190,12394,12398,12407,12431,12560,12564,12570,12681,12687,12706,12710,12716,13003,13007,13012,13128,13130,13134,13143,13193,13230,13241],[427,428,430],"h2",{"id":429},"tổng-quan-project","Tổng quan project",[432,433,434,435,439,440,443,444,447],"p",{},"Trong bài này, chúng ta sẽ xây dựng một ",[436,437,438],"strong",{},"Todo App hoàn chỉnh"," từ A đến Z bằng Nuxt 4. Đây là bài ",[436,441,442],{},"hands-on"," — bạn code theo từng bước, áp dụng ",[436,445,446],{},"tất cả kiến thức"," đã học ở các section trước vào một project thật.",[449,450,453,456,457,460,461,464],"callout",{"color":451,"icon":452},"primary","i-lucide-info",[436,454,455],{},"Mục tiêu:"," Không chỉ xem code — bạn sẽ hiểu ",[436,458,459],{},"tại sao"," mỗi dòng code được viết như vậy, ",[436,462,463],{},"cách Nuxt 4 hoạt động"," bên dưới, và tự tin build project riêng sau khi hoàn thành.",[466,467,469],"h3",{"id":468},"những-gì-sẽ-học","Những gì sẽ học",[471,472,473,480,498,507,519,527,539,547,559,567,578,586,597,603],"ul",{},[474,475,476,479],"li",{},[436,477,478],{},"Khởi tạo project Nuxt 4"," — hiểu từng file generate ra.",[474,481,482,485,486,490,491,490,494,497],{},[436,483,484],{},"TypeScript types"," — ",[487,488,489],"code",{},"interface",", ",[487,492,493],{},"type",[487,495,496],{},"crypto.randomUUID()",".",[474,499,500,506],{},[436,501,502,503],{},"Composable ",[487,504,505],{},"useTodos"," — trái tim của app, quản lý state tập trung.",[474,508,509,514,515,518],{},[436,510,511],{},[487,512,513],{},"useState"," — shared state SSR-safe, khác ",[487,516,517],{},"ref"," thế nào.",[474,520,521,526],{},[436,522,523],{},[487,524,525],{},"computed"," — reactive derivation, tự cập nhật khi dependency thay đổi.",[474,528,529,534,535,538],{},[436,530,531],{},[487,532,533],{},"watch"," — sync state với ",[487,536,537],{},"localStorage"," để persist dữ liệu.",[474,540,541,546],{},[436,542,543],{},[487,544,545],{},"v-model"," — two-way binding trong form.",[474,548,549,558],{},[436,550,551,554,555],{},[487,552,553],{},"defineProps"," \u002F ",[487,556,557],{},"defineEmits"," — typed component API.",[474,560,561,566],{},[436,562,563],{},[487,564,565],{},"defineModel"," — Vue 3.4+ syntactic sugar cho v-model component.",[474,568,569,577],{},[436,570,571,554,574],{},[487,572,573],{},"v-if",[487,575,576],{},"v-else"," — conditional rendering cho edit mode.",[474,579,580,585],{},[436,581,582],{},[487,583,584],{},"TransitionGroup"," — animation khi thêm\u002Fxóa item.",[474,587,588,596],{},[436,589,590,554,593],{},[487,591,592],{},"useHead",[487,594,595],{},"useSeoMeta"," — SEO built-in của Nuxt 4.",[474,598,599,602],{},[436,600,601],{},"CSS scoped"," — style không leak giữa các component.",[474,604,605,610],{},[436,606,607],{},[487,608,609],{},"import.meta.client"," — SSR guard cho code chỉ chạy ở browser.",[466,612,614],{"id":613},"cấu-trúc-project","Cấu trúc project",[616,617,622],"pre",{"className":618,"code":620,"language":621},[619],"language-text","todo-app\u002F\n├── app.vue                    # Root layout\n├── pages\u002F\n│   └── index.vue              # Main todo page\n├── components\u002F\n│   ├── TodoForm.vue            # Add new todo\n│   ├── TodoList.vue            # List all todos\n│   ├── TodoItem.vue            # Single todo row\n│   └── TodoFilter.vue          # Filter: all\u002Factive\u002Fcompleted\n├── composables\u002F\n│   └── useTodos.ts             # Todo state management\n├── types\u002F\n│   └── todo.ts                 # TypeScript types\n├── assets\u002F\n│   └── css\u002F\n│       └── main.css            # Global styles + CSS custom properties\n├── app.config.ts               # App configuration\n└── nuxt.config.ts              # Nuxt configuration\n","text",[487,623,620],{"__ignoreMap":624},"",[626,627],"hr",{},[427,629,631],{"id":630},"_1-khởi-tạo-dự-án","1. Khởi tạo dự án",[432,633,634],{},"Mở terminal và chạy các lệnh sau:",[616,636,640],{"className":637,"code":638,"language":639,"meta":624,"style":624},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","npx nuxi init todo-app\ncd todo-app\nnpm install\nnpm run dev\n","bash",[487,641,642,661,670,679],{"__ignoreMap":624},[643,644,647,651,655,658],"span",{"class":645,"line":646},"line",1,[643,648,650],{"class":649},"sBMFI","npx",[643,652,654],{"class":653},"sfazB"," nuxi",[643,656,657],{"class":653}," init",[643,659,660],{"class":653}," todo-app\n",[643,662,664,668],{"class":645,"line":663},2,[643,665,667],{"class":666},"s2Zo4","cd",[643,669,660],{"class":653},[643,671,673,676],{"class":645,"line":672},3,[643,674,675],{"class":649},"npm",[643,677,678],{"class":653}," install\n",[643,680,682,684,687],{"class":645,"line":681},4,[643,683,675],{"class":649},[643,685,686],{"class":653}," run",[643,688,689],{"class":653}," dev\n",[432,691,692,693,696],{},"Sau khi chạy ",[487,694,695],{},"npx nuxi init todo-app",", Nuxt sẽ tạo ra cấu trúc thư mục sau:",[616,698,701],{"className":699,"code":700,"language":621},[619],"todo-app\u002F\n├── .nuxt\u002F               # Thư mục build — Nuxt tự quản lý\n├── app\u002F                 # Code app của bạn\n│   ├── app.vue          # Root component\n│   └── assets\u002F          # Static assets\n├── node_modules\u002F        # Dependencies\n├── .gitignore           # Git ignore rules\n├── nuxt.config.ts       # Cấu hình Nuxt\n├── package.json         # Project metadata + scripts\n├── tsconfig.json        # TypeScript config\n└── README.md            # Project readme\n",[487,702,700],{"__ignoreMap":624},[466,704,706],{"id":705},"giải-thích-từng-file","Giải thích từng file",[432,708,709,714],{},[436,710,711],{},[487,712,713],{},"package.json"," — Khai báo tên project, scripts, và dependencies:",[616,716,720],{"className":717,"code":718,"language":719,"meta":624,"style":624},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"name\": \"todo-app\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"scripts\": {\n    \"build\": \"nuxt build\",\n    \"dev\": \"nuxt dev\",\n    \"generate\": \"nuxt generate\",\n    \"preview\": \"nuxt preview\"\n  },\n  \"dependencies\": {\n    \"nuxt\": \"^4.0.0\",\n    \"vue\": \"latest\"\n  }\n}\n","json",[487,721,722,728,754,773,787,802,824,845,866,886,892,906,927,946,952],{"__ignoreMap":624},[643,723,724],{"class":645,"line":646},[643,725,727],{"class":726},"sMK4o","{\n",[643,729,730,733,737,740,743,746,749,751],{"class":645,"line":663},[643,731,732],{"class":726},"  \"",[643,734,736],{"class":735},"spNyl","name",[643,738,739],{"class":726},"\"",[643,741,742],{"class":726},":",[643,744,745],{"class":726}," \"",[643,747,748],{"class":653},"todo-app",[643,750,739],{"class":726},[643,752,753],{"class":726},",\n",[643,755,756,758,760,762,764,766,769,771],{"class":645,"line":672},[643,757,732],{"class":726},[643,759,493],{"class":735},[643,761,739],{"class":726},[643,763,742],{"class":726},[643,765,745],{"class":726},[643,767,768],{"class":653},"module",[643,770,739],{"class":726},[643,772,753],{"class":726},[643,774,775,777,780,782,784],{"class":645,"line":681},[643,776,732],{"class":726},[643,778,779],{"class":735},"private",[643,781,739],{"class":726},[643,783,742],{"class":726},[643,785,786],{"class":726}," true,\n",[643,788,790,792,795,797,799],{"class":645,"line":789},5,[643,791,732],{"class":726},[643,793,794],{"class":735},"scripts",[643,796,739],{"class":726},[643,798,742],{"class":726},[643,800,801],{"class":726}," {\n",[643,803,805,808,811,813,815,817,820,822],{"class":645,"line":804},6,[643,806,807],{"class":726},"    \"",[643,809,810],{"class":649},"build",[643,812,739],{"class":726},[643,814,742],{"class":726},[643,816,745],{"class":726},[643,818,819],{"class":653},"nuxt build",[643,821,739],{"class":726},[643,823,753],{"class":726},[643,825,827,829,832,834,836,838,841,843],{"class":645,"line":826},7,[643,828,807],{"class":726},[643,830,831],{"class":649},"dev",[643,833,739],{"class":726},[643,835,742],{"class":726},[643,837,745],{"class":726},[643,839,840],{"class":653},"nuxt dev",[643,842,739],{"class":726},[643,844,753],{"class":726},[643,846,848,850,853,855,857,859,862,864],{"class":645,"line":847},8,[643,849,807],{"class":726},[643,851,852],{"class":649},"generate",[643,854,739],{"class":726},[643,856,742],{"class":726},[643,858,745],{"class":726},[643,860,861],{"class":653},"nuxt generate",[643,863,739],{"class":726},[643,865,753],{"class":726},[643,867,869,871,874,876,878,880,883],{"class":645,"line":868},9,[643,870,807],{"class":726},[643,872,873],{"class":649},"preview",[643,875,739],{"class":726},[643,877,742],{"class":726},[643,879,745],{"class":726},[643,881,882],{"class":653},"nuxt preview",[643,884,885],{"class":726},"\"\n",[643,887,889],{"class":645,"line":888},10,[643,890,891],{"class":726},"  },\n",[643,893,895,897,900,902,904],{"class":645,"line":894},11,[643,896,732],{"class":726},[643,898,899],{"class":735},"dependencies",[643,901,739],{"class":726},[643,903,742],{"class":726},[643,905,801],{"class":726},[643,907,909,911,914,916,918,920,923,925],{"class":645,"line":908},12,[643,910,807],{"class":726},[643,912,913],{"class":649},"nuxt",[643,915,739],{"class":726},[643,917,742],{"class":726},[643,919,745],{"class":726},[643,921,922],{"class":653},"^4.0.0",[643,924,739],{"class":726},[643,926,753],{"class":726},[643,928,930,932,935,937,939,941,944],{"class":645,"line":929},13,[643,931,807],{"class":726},[643,933,934],{"class":649},"vue",[643,936,739],{"class":726},[643,938,742],{"class":726},[643,940,745],{"class":726},[643,942,943],{"class":653},"latest",[643,945,885],{"class":726},[643,947,949],{"class":645,"line":948},14,[643,950,951],{"class":726},"  }\n",[643,953,955],{"class":645,"line":954},15,[643,956,957],{"class":726},"}\n",[471,959,960,978,984,989,994,999],{},[474,961,962,965,966,969,970,973,974,977],{},[487,963,964],{},"\"type\": \"module\""," — dùng ESM (",[487,967,968],{},"import","\u002F",[487,971,972],{},"export",") thay CommonJS (",[487,975,976],{},"require",").",[474,979,980,983],{},[487,981,982],{},"\"private\": true"," — chặn không cho publish nhầm lên npm registry.",[474,985,986,988],{},[487,987,840],{}," — chế độ development, hot reload (HMR), devtools.",[474,990,991,993],{},[487,992,819],{}," — build production.",[474,995,996,998],{},[487,997,861],{}," — pre-render toàn bộ static HTML (SSG).",[474,1000,1001,1003],{},[487,1002,882],{}," — xem trước production build local.",[432,1005,1006,1011],{},[436,1007,1008],{},[487,1009,1010],{},"nuxt.config.ts"," — Trái tim cấu hình của app:",[616,1013,1017],{"className":1014,"code":1015,"language":1016,"meta":624,"style":624},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F nuxt.config.ts\nexport default defineNuxtConfig({\n  compatibilityDate: '2025-07-01',\n  devtools: { enabled: true },\n  modules: [],\n  css: ['~\u002Fassets\u002Fcss\u002Fmain.css'],\n  app: {\n    head: {\n      title: 'Todo App',\n      meta: [\n        { name: 'viewport', content: 'width=device-width, initial-scale=1' }\n      ]\n    }\n  }\n})\n","typescript",[487,1018,1019,1025,1042,1061,1083,1095,1117,1126,1135,1151,1161,1196,1201,1206,1210],{"__ignoreMap":624},[643,1020,1021],{"class":645,"line":646},[643,1022,1024],{"class":1023},"sHwdD","\u002F\u002F nuxt.config.ts\n",[643,1026,1027,1030,1033,1036,1040],{"class":645,"line":663},[643,1028,972],{"class":1029},"s7zQu",[643,1031,1032],{"class":1029}," default",[643,1034,1035],{"class":666}," defineNuxtConfig",[643,1037,1039],{"class":1038},"sTEyZ","(",[643,1041,727],{"class":726},[643,1043,1044,1048,1050,1053,1056,1059],{"class":645,"line":672},[643,1045,1047],{"class":1046},"swJcz","  compatibilityDate",[643,1049,742],{"class":726},[643,1051,1052],{"class":726}," '",[643,1054,1055],{"class":653},"2025-07-01",[643,1057,1058],{"class":726},"'",[643,1060,753],{"class":726},[643,1062,1063,1066,1068,1071,1074,1076,1080],{"class":645,"line":681},[643,1064,1065],{"class":1046},"  devtools",[643,1067,742],{"class":726},[643,1069,1070],{"class":726}," {",[643,1072,1073],{"class":1046}," enabled",[643,1075,742],{"class":726},[643,1077,1079],{"class":1078},"sfNiH"," true",[643,1081,1082],{"class":726}," },\n",[643,1084,1085,1088,1090,1093],{"class":645,"line":789},[643,1086,1087],{"class":1046},"  modules",[643,1089,742],{"class":726},[643,1091,1092],{"class":1038}," []",[643,1094,753],{"class":726},[643,1096,1097,1100,1102,1105,1107,1110,1112,1115],{"class":645,"line":804},[643,1098,1099],{"class":1046},"  css",[643,1101,742],{"class":726},[643,1103,1104],{"class":1038}," [",[643,1106,1058],{"class":726},[643,1108,1109],{"class":653},"~\u002Fassets\u002Fcss\u002Fmain.css",[643,1111,1058],{"class":726},[643,1113,1114],{"class":1038},"]",[643,1116,753],{"class":726},[643,1118,1119,1122,1124],{"class":645,"line":826},[643,1120,1121],{"class":1046},"  app",[643,1123,742],{"class":726},[643,1125,801],{"class":726},[643,1127,1128,1131,1133],{"class":645,"line":847},[643,1129,1130],{"class":1046},"    head",[643,1132,742],{"class":726},[643,1134,801],{"class":726},[643,1136,1137,1140,1142,1144,1147,1149],{"class":645,"line":868},[643,1138,1139],{"class":1046},"      title",[643,1141,742],{"class":726},[643,1143,1052],{"class":726},[643,1145,1146],{"class":653},"Todo App",[643,1148,1058],{"class":726},[643,1150,753],{"class":726},[643,1152,1153,1156,1158],{"class":645,"line":888},[643,1154,1155],{"class":1046},"      meta",[643,1157,742],{"class":726},[643,1159,1160],{"class":1038}," [\n",[643,1162,1163,1166,1169,1171,1173,1176,1178,1181,1184,1186,1188,1191,1193],{"class":645,"line":894},[643,1164,1165],{"class":726},"        {",[643,1167,1168],{"class":1046}," name",[643,1170,742],{"class":726},[643,1172,1052],{"class":726},[643,1174,1175],{"class":653},"viewport",[643,1177,1058],{"class":726},[643,1179,1180],{"class":726},",",[643,1182,1183],{"class":1046}," content",[643,1185,742],{"class":726},[643,1187,1052],{"class":726},[643,1189,1190],{"class":653},"width=device-width, initial-scale=1",[643,1192,1058],{"class":726},[643,1194,1195],{"class":726}," }\n",[643,1197,1198],{"class":645,"line":908},[643,1199,1200],{"class":1038},"      ]\n",[643,1202,1203],{"class":645,"line":929},[643,1204,1205],{"class":726},"    }\n",[643,1207,1208],{"class":645,"line":948},[643,1209,951],{"class":726},[643,1211,1212,1215],{"class":645,"line":954},[643,1213,1214],{"class":726},"}",[643,1216,1217],{"class":1038},")\n",[449,1219,1222,1223,1229,1230,1232,1233,1236],{"color":1220,"icon":1221},"warning","i-lucide-star","⭐ ",[436,1224,1225,1226,742],{},"Giải thích ",[487,1227,1228],{},"compatibilityDate"," Pin behavior của Nuxt tại một thời điểm cụ thể. Khi upgrade Nuxt version mới, các thay đổi behavior chỉ áp dụng nếu ",[487,1231,1228],{}," >= ngày release. Giữ cho app không bất ngờ bị vỡ khi upgrade. Đặt là ",[487,1234,1235],{},"'2025-07-01'"," là ngày release Nuxt 4 stable.",[471,1238,1239,1245,1251,1264],{},[474,1240,1241,1244],{},[487,1242,1243],{},"devtools: { enabled: true }"," — bật Nuxt DevTools (giao diện debug trong browser).",[474,1246,1247,1250],{},[487,1248,1249],{},"modules: []"," — danh sách module Nuxt sẽ dùng.",[474,1252,1253,1256,1257,1260,1261,497],{},[487,1254,1255],{},"css: ['~\u002Fassets\u002Fcss\u002Fmain.css']"," — import global CSS. ",[487,1258,1259],{},"~\u002F"," là alias trỏ đến thư mục ",[487,1262,1263],{},"app\u002F",[474,1265,1266,1269],{},[487,1267,1268],{},"app.head"," — cài đặt meta tag, title mặc định.",[432,1271,692,1272,1275,1276,1279],{},[487,1273,1274],{},"npm run dev",", mở browser vào ",[487,1277,1278],{},"http:\u002F\u002Flocalhost:3000"," — bạn sẽ thấy trang welcome của Nuxt.",[626,1281],{},[427,1283,1285],{"id":1284},"_2-định-nghĩa-typescript-types","2. Định nghĩa TypeScript types",[432,1287,1288,1289,742],{},"Tạo file ",[487,1290,1291],{},"app\u002Ftypes\u002Ftodo.ts",[616,1293,1295],{"className":1014,"code":1294,"language":1016,"meta":624,"style":624},"\u002F\u002F app\u002Ftypes\u002Ftodo.ts\n\n\u002F**\n * Todo item — dữ liệu cốt lõi của app.\n * Dùng `interface` để mô tả hình dạng của object.\n *\u002F\nexport interface Todo {\n  \u002F** Unique identifier — dùng crypto.randomUUID() để generate *\u002F\n  id: string\n\n  \u002F** Nội dung của todo *\u002F\n  title: string\n\n  \u002F** Trạng thái hoàn thành *\u002F\n  completed: boolean\n\n  \u002F** Thời gian tạo — dùng để sort hoặc hiển thị *\u002F\n  createdAt: Date\n}\n\n\u002F**\n * Filter type — literal union type.\n * Chỉ có thể là 1 trong 3 giá trị này.\n *\u002F\nexport type TodoFilter = 'all' | 'active' | 'completed'\n",[487,1296,1297,1302,1308,1313,1318,1323,1328,1340,1345,1355,1359,1364,1373,1377,1382,1392,1397,1403,1414,1419,1424,1429,1435,1441,1446],{"__ignoreMap":624},[643,1298,1299],{"class":645,"line":646},[643,1300,1301],{"class":1023},"\u002F\u002F app\u002Ftypes\u002Ftodo.ts\n",[643,1303,1304],{"class":645,"line":663},[643,1305,1307],{"emptyLinePlaceholder":1306},true,"\n",[643,1309,1310],{"class":645,"line":672},[643,1311,1312],{"class":1023},"\u002F**\n",[643,1314,1315],{"class":645,"line":681},[643,1316,1317],{"class":1023}," * Todo item — dữ liệu cốt lõi của app.\n",[643,1319,1320],{"class":645,"line":789},[643,1321,1322],{"class":1023}," * Dùng `interface` để mô tả hình dạng của object.\n",[643,1324,1325],{"class":645,"line":804},[643,1326,1327],{"class":1023}," *\u002F\n",[643,1329,1330,1332,1335,1338],{"class":645,"line":826},[643,1331,972],{"class":1029},[643,1333,1334],{"class":735}," interface",[643,1336,1337],{"class":649}," Todo",[643,1339,801],{"class":726},[643,1341,1342],{"class":645,"line":847},[643,1343,1344],{"class":1023},"  \u002F** Unique identifier — dùng crypto.randomUUID() để generate *\u002F\n",[643,1346,1347,1350,1352],{"class":645,"line":868},[643,1348,1349],{"class":1046},"  id",[643,1351,742],{"class":726},[643,1353,1354],{"class":649}," string\n",[643,1356,1357],{"class":645,"line":888},[643,1358,1307],{"emptyLinePlaceholder":1306},[643,1360,1361],{"class":645,"line":894},[643,1362,1363],{"class":1023},"  \u002F** Nội dung của todo *\u002F\n",[643,1365,1366,1369,1371],{"class":645,"line":908},[643,1367,1368],{"class":1046},"  title",[643,1370,742],{"class":726},[643,1372,1354],{"class":649},[643,1374,1375],{"class":645,"line":929},[643,1376,1307],{"emptyLinePlaceholder":1306},[643,1378,1379],{"class":645,"line":948},[643,1380,1381],{"class":1023},"  \u002F** Trạng thái hoàn thành *\u002F\n",[643,1383,1384,1387,1389],{"class":645,"line":954},[643,1385,1386],{"class":1046},"  completed",[643,1388,742],{"class":726},[643,1390,1391],{"class":649}," boolean\n",[643,1393,1395],{"class":645,"line":1394},16,[643,1396,1307],{"emptyLinePlaceholder":1306},[643,1398,1400],{"class":645,"line":1399},17,[643,1401,1402],{"class":1023},"  \u002F** Thời gian tạo — dùng để sort hoặc hiển thị *\u002F\n",[643,1404,1406,1409,1411],{"class":645,"line":1405},18,[643,1407,1408],{"class":1046},"  createdAt",[643,1410,742],{"class":726},[643,1412,1413],{"class":649}," Date\n",[643,1415,1417],{"class":645,"line":1416},19,[643,1418,957],{"class":726},[643,1420,1422],{"class":645,"line":1421},20,[643,1423,1307],{"emptyLinePlaceholder":1306},[643,1425,1427],{"class":645,"line":1426},21,[643,1428,1312],{"class":1023},[643,1430,1432],{"class":645,"line":1431},22,[643,1433,1434],{"class":1023}," * Filter type — literal union type.\n",[643,1436,1438],{"class":645,"line":1437},23,[643,1439,1440],{"class":1023}," * Chỉ có thể là 1 trong 3 giá trị này.\n",[643,1442,1444],{"class":645,"line":1443},24,[643,1445,1327],{"class":1023},[643,1447,1449,1451,1454,1457,1460,1462,1465,1467,1470,1472,1475,1477,1479,1481,1484],{"class":645,"line":1448},25,[643,1450,972],{"class":1029},[643,1452,1453],{"class":735}," type",[643,1455,1456],{"class":649}," TodoFilter",[643,1458,1459],{"class":726}," =",[643,1461,1052],{"class":726},[643,1463,1464],{"class":653},"all",[643,1466,1058],{"class":726},[643,1468,1469],{"class":726}," |",[643,1471,1052],{"class":726},[643,1473,1474],{"class":653},"active",[643,1476,1058],{"class":726},[643,1478,1469],{"class":726},[643,1480,1052],{"class":726},[643,1482,1483],{"class":653},"completed",[643,1485,1486],{"class":726},"'\n",[466,1488,1490],{"id":1489},"giải-thích-chi-tiết","Giải thích chi tiết",[432,1492,1493],{},[436,1494,1495,1497,1498,742],{},[487,1496,489],{}," vs ",[487,1499,493],{},[1501,1502,1503,1515],"field-group",{},[1504,1505,1507,1508,1511,1512,1514],"field",{"name":489,"type":1506},"Object shape","Dùng để mô tả hình dạng của object. Có thể extend (",[487,1509,1510],{},"extends","), merge declaration. Khuyên dùng ",[487,1513,489],{}," cho object (public API, component props).",[1504,1516,1518,1519,1522],{"name":493,"type":1517},"Union \u002F primitive \u002F utility","Dùng cho union type (",[487,1520,1521],{},"'all' | 'active' | 'completed'","), literal type, mapped type, utility type. Linh hoạt hơn nhưng không merge được.",[449,1524,1222,1525,1528,1529,1531,1532,1534,1535,1537,1538,1540],{"color":1220,"icon":1221},[436,1526,1527],{},"Quy tắc chọn:"," ",[487,1530,489],{}," cho object shape (Todo), ",[487,1533,493],{}," cho union\u002Fliteral (TodoFilter). Nói chung: dùng ",[487,1536,489],{}," cho đến khi cần feature chỉ có ở ",[487,1539,493],{}," (union, intersection phức tạp).",[432,1542,1543],{},[436,1544,1545,742],{},[487,1546,496],{},[616,1548,1550],{"className":1014,"code":1549,"language":1016,"meta":624,"style":624},"\u002F\u002F Tạo ID duy nhất — không trùng lặp, không cần thư viện ngoài\nconst id = crypto.randomUUID()\n\u002F\u002F Ví dụ: \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\"\n",[487,1551,1552,1557,1579],{"__ignoreMap":624},[643,1553,1554],{"class":645,"line":646},[643,1555,1556],{"class":1023},"\u002F\u002F Tạo ID duy nhất — không trùng lặp, không cần thư viện ngoài\n",[643,1558,1559,1562,1565,1568,1571,1573,1576],{"class":645,"line":663},[643,1560,1561],{"class":735},"const",[643,1563,1564],{"class":1038}," id ",[643,1566,1567],{"class":726},"=",[643,1569,1570],{"class":1038}," crypto",[643,1572,497],{"class":726},[643,1574,1575],{"class":666},"randomUUID",[643,1577,1578],{"class":1038},"()\n",[643,1580,1581],{"class":645,"line":672},[643,1582,1583],{"class":1023},"\u002F\u002F Ví dụ: \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\"\n",[471,1585,1586,1589,1596,1603],{},[474,1587,1588],{},"Là Web API built-in, hoạt động ở cả browser và Node.js (Nitro server).",[474,1590,1591,1592,1595],{},"Tốt hơn ",[487,1593,1594],{},"Math.random()"," — UUID là globally unique, không bị collide.",[474,1597,1598,1599,1602],{},"Không cần ",[487,1600,1601],{},"uuid"," npm package.",[474,1604,1605,1606,1608],{},"Trong SSR: Nitro server chạy Node.js >= 19 có ",[487,1607,496],{}," built-in.",[432,1610,1611,1616,1617,1620],{},[436,1612,1613],{},[487,1614,1615],{},"createdAt: Date"," — Lưu ý: Khi serialize qua JSON (localStorage), ",[487,1618,1619],{},"Date"," sẽ thành string. Ta sẽ xử lý việc parse lại trong composable.",[626,1622],{},[427,1624,1626,1627,1629],{"id":1625},"_3-composable-usetodos-trái-tim-của-app","3. Composable ",[487,1628,505],{}," — Trái tim của app",[432,1631,1288,1632,742],{},[487,1633,1634],{},"app\u002Fcomposables\u002FuseTodos.ts",[616,1636,1638],{"className":1014,"code":1637,"language":1016,"meta":624,"style":624},"\u002F\u002F app\u002Fcomposables\u002FuseTodos.ts\n\nimport type { Todo, TodoFilter } from '~\u002Ftypes\u002Ftodo'\n\n\u002F**\n * useTodos — composable quản lý toàn bộ state và logic của Todo App.\n *\n * Shared state qua useState → mọi component gọi useTodos() đều\n * đọc\u002Fghi cùng một state, không cần prop drilling hay emit lòng vòng.\n *\u002F\nexport const useTodos = () => {\n  \u002F\u002F ============================================================\n  \u002F\u002F STATE — dùng useState cho shared state SSR-safe\n  \u002F\u002F ============================================================\n\n  \u002F**\n   * useState\u003CTodo[]>('todos', () => [])\n   *\n   * - Key 'todos': unique identifier trong toàn app.\n   *   Mọi component gọi useState('todos') đều lấy cùng 1 ref.\n   * - Lazy init () => []: chỉ chạy 1 lần khi state chưa tồn tại.\n   *   Dùng arrow function để tránh chạy không cần thiết ở SSR.\n   *\u002F\n  const todos = useState\u003CTodo[]>('todos', () => [])\n\n  \u002F**\n   * Filter state — cũng share qua useState để TodoList và TodoFilter\n   * cùng đọc 1 nguồn filter mà không cần emit qua lại.\n   *\u002F\n  const filter = useState\u003CTodoFilter>('todoFilter', () => 'all')\n\n  \u002F\u002F ============================================================\n  \u002F\u002F LOCALSTORAGE PERSISTENCE\n  \u002F\u002F ============================================================\n\n  \u002F\u002F Load dữ liệu từ localStorage khi app khởi tạo (chỉ ở client)\n  if (import.meta.client) {\n    try {\n      const stored = localStorage.getItem('nuxt-todos')\n      if (stored) {\n        \u002F\u002F Parse JSON và chuyển createdAt từ string thành Date\n        const parsed: Todo[] = JSON.parse(stored)\n        todos.value = parsed.map(todo => ({\n          ...todo,\n          createdAt: new Date(todo.createdAt)\n        }))\n      }\n    } catch {\n      \u002F\u002F Nếu localStorage bị corrupt hoặc không có dữ liệu — bỏ qua\n      console.warn('Failed to load todos from localStorage')\n    }\n  }\n\n  \u002F**\n   * watch — theo dõi sự thay đổi của todos và tự động lưu vào localStorage.\n   *\n   * { deep: true }: theo dõi cả thay đổi bên trong array (add\u002Fremove item,\n   * thay đổi completed, thay đổi title). Mặc định watch chỉ theo dõi\n   * tham chiếu của ref — không phát hiện mutation bên trong.\n   *\u002F\n  if (import.meta.client) {\n    watch(todos, (newTodos) => {\n      localStorage.setItem('nuxt-todos', JSON.stringify(newTodos))\n    }, { deep: true })\n  }\n\n  \u002F\u002F ============================================================\n  \u002F\u002F COMPUTED — reactive derivation\n  \u002F\u002F ============================================================\n\n  \u002F**\n   * computed — tự động tính toán lại khi dependency (todos) thay đổi.\n   * Giống như Excel formula — thay đổi input, output tự update.\n   *\u002F\n  const activeTodos = computed(() => todos.value.filter(t => !t.completed))\n  const completedTodos = computed(() => todos.value.filter(t => t.completed))\n\n  const activeCount = computed(() => activeTodos.value.length)\n  const completedCount = computed(() => completedTodos.value.length)\n\n  \u002F**\n   * filteredTodos — dựa trên filter state hiện tại, lọc todos tương ứng.\n   * Đây là computed được dùng trong TodoList để render đúng danh sách.\n   *\u002F\n  const filteredTodos = computed(() => {\n    switch (filter.value) {\n      case 'active':\n        return activeTodos.value\n      case 'completed':\n        return completedTodos.value\n      default:\n        return todos.value\n    }\n  })\n\n  \u002F\u002F ============================================================\n  \u002F\u002F CRUD OPERATIONS\n  \u002F\u002F ============================================================\n\n  \u002F**\n   * Thêm todo mới.\n   * - Dùng crypto.randomUUID() cho id duy nhất.\n   * - Đặt createdAt là thời điểm hiện tại.\n   * - Tạo object Todo mới và push vào array.\n   *\u002F\n  const addTodo = (title: string) => {\n    const trimmed = title.trim()\n    if (!trimmed) return  \u002F\u002F Không thêm todo rỗng\n\n    const todo: Todo = {\n      id: crypto.randomUUID(),\n      title: trimmed,\n      completed: false,\n      createdAt: new Date()\n    }\n    todos.value.push(todo)\n  }\n\n  \u002F**\n   * Toggle trạng thái completed của todo.\n   * Tìm todo theo id, nếu thấy thì đảo ngược completed.\n   *\u002F\n  const toggleTodo = (id: string) => {\n    const todo = todos.value.find(t => t.id === id)\n    if (todo) {\n      todo.completed = !todo.completed\n    }\n  }\n\n  \u002F**\n   * Xóa todo theo id.\n   * Dùng filter để tạo array mới không chứa id đó.\n   * Gán lại cho todos.value để trigger reactivity.\n   *\u002F\n  const removeTodo = (id: string) => {\n    todos.value = todos.value.filter(t => t.id !== id)\n  }\n\n  \u002F**\n   * Chỉnh sửa title của todo.\n   * Tìm todo theo id, cập nhật title mới.\n   *\u002F\n  const editTodo = (id: string, title: string) => {\n    const trimmed = title.trim()\n    if (!trimmed) {\n      \u002F\u002F Nếu title rỗng sau khi trim — xóa todo luôn\n      removeTodo(id)\n      return\n    }\n\n    const todo = todos.value.find(t => t.id === id)\n    if (todo) {\n      todo.title = trimmed\n    }\n  }\n\n  \u002F**\n   * Xóa tất cả todo đã hoàn thành.\n   * Dùng filter để giữ lại những todo chưa completed.\n   *\u002F\n  const clearCompleted = () => {\n    todos.value = todos.value.filter(t => !t.completed)\n  }\n\n  \u002F\u002F ============================================================\n  \u002F\u002F RETURN — chỉ expose những gì component cần\n  \u002F\u002F ============================================================\n\n  return {\n    \u002F\u002F State\n    todos,\n    filter,\n\n    \u002F\u002F Computed\n    activeTodos,\n    completedTodos,\n    activeCount,\n    completedCount,\n    filteredTodos,\n\n    \u002F\u002F Actions\n    addTodo,\n    toggleTodo,\n    removeTodo,\n    editTodo,\n    clearCompleted\n  }\n}\n",[487,1639,1640,1645,1649,1676,1680,1684,1689,1694,1699,1704,1708,1728,1733,1738,1742,1746,1751,1756,1761,1766,1771,1776,1781,1786,1829,1833,1838,1844,1850,1855,1897,1902,1907,1913,1918,1923,1929,1955,1963,1993,2008,2014,2046,2078,2088,2113,2122,2128,2139,2145,2167,2172,2177,2182,2187,2193,2198,2204,2210,2216,2221,2242,2266,2300,2319,2324,2329,2334,2340,2345,2350,2355,2361,2367,2372,2421,2464,2469,2500,2530,2535,2540,2546,2552,2557,2577,2595,2610,2623,2636,2647,2655,2666,2671,2679,2684,2689,2695,2700,2705,2710,2716,2722,2728,2734,2739,2765,2786,2808,2813,2829,2847,2858,2871,2885,2890,2911,2916,2921,2926,2932,2938,2943,2968,3008,3021,3042,3047,3052,3057,3062,3068,3074,3080,3085,3109,3149,3154,3159,3164,3170,3176,3181,3213,3230,3245,3251,3263,3269,3274,3279,3316,3329,3343,3348,3353,3358,3363,3369,3375,3380,3396,3433,3438,3443,3448,3454,3459,3464,3472,3478,3485,3493,3498,3504,3512,3520,3528,3536,3544,3549,3555,3563,3571,3579,3587,3593,3598],{"__ignoreMap":624},[643,1641,1642],{"class":645,"line":646},[643,1643,1644],{"class":1023},"\u002F\u002F app\u002Fcomposables\u002FuseTodos.ts\n",[643,1646,1647],{"class":645,"line":663},[643,1648,1307],{"emptyLinePlaceholder":1306},[643,1650,1651,1653,1655,1657,1659,1661,1663,1666,1669,1671,1674],{"class":645,"line":672},[643,1652,968],{"class":1029},[643,1654,1453],{"class":1029},[643,1656,1070],{"class":726},[643,1658,1337],{"class":1038},[643,1660,1180],{"class":726},[643,1662,1456],{"class":1038},[643,1664,1665],{"class":726}," }",[643,1667,1668],{"class":1029}," from",[643,1670,1052],{"class":726},[643,1672,1673],{"class":653},"~\u002Ftypes\u002Ftodo",[643,1675,1486],{"class":726},[643,1677,1678],{"class":645,"line":681},[643,1679,1307],{"emptyLinePlaceholder":1306},[643,1681,1682],{"class":645,"line":789},[643,1683,1312],{"class":1023},[643,1685,1686],{"class":645,"line":804},[643,1687,1688],{"class":1023}," * useTodos — composable quản lý toàn bộ state và logic của Todo App.\n",[643,1690,1691],{"class":645,"line":826},[643,1692,1693],{"class":1023}," *\n",[643,1695,1696],{"class":645,"line":847},[643,1697,1698],{"class":1023}," * Shared state qua useState → mọi component gọi useTodos() đều\n",[643,1700,1701],{"class":645,"line":868},[643,1702,1703],{"class":1023}," * đọc\u002Fghi cùng một state, không cần prop drilling hay emit lòng vòng.\n",[643,1705,1706],{"class":645,"line":888},[643,1707,1327],{"class":1023},[643,1709,1710,1712,1715,1718,1720,1723,1726],{"class":645,"line":894},[643,1711,972],{"class":1029},[643,1713,1714],{"class":735}," const",[643,1716,1717],{"class":1038}," useTodos ",[643,1719,1567],{"class":726},[643,1721,1722],{"class":726}," ()",[643,1724,1725],{"class":735}," =>",[643,1727,801],{"class":726},[643,1729,1730],{"class":645,"line":908},[643,1731,1732],{"class":1023},"  \u002F\u002F ============================================================\n",[643,1734,1735],{"class":645,"line":929},[643,1736,1737],{"class":1023},"  \u002F\u002F STATE — dùng useState cho shared state SSR-safe\n",[643,1739,1740],{"class":645,"line":948},[643,1741,1732],{"class":1023},[643,1743,1744],{"class":645,"line":954},[643,1745,1307],{"emptyLinePlaceholder":1306},[643,1747,1748],{"class":645,"line":1394},[643,1749,1750],{"class":1023},"  \u002F**\n",[643,1752,1753],{"class":645,"line":1399},[643,1754,1755],{"class":1023},"   * useState\u003CTodo[]>('todos', () => [])\n",[643,1757,1758],{"class":645,"line":1405},[643,1759,1760],{"class":1023},"   *\n",[643,1762,1763],{"class":645,"line":1416},[643,1764,1765],{"class":1023},"   * - Key 'todos': unique identifier trong toàn app.\n",[643,1767,1768],{"class":645,"line":1421},[643,1769,1770],{"class":1023},"   *   Mọi component gọi useState('todos') đều lấy cùng 1 ref.\n",[643,1772,1773],{"class":645,"line":1426},[643,1774,1775],{"class":1023},"   * - Lazy init () => []: chỉ chạy 1 lần khi state chưa tồn tại.\n",[643,1777,1778],{"class":645,"line":1431},[643,1779,1780],{"class":1023},"   *   Dùng arrow function để tránh chạy không cần thiết ở SSR.\n",[643,1782,1783],{"class":645,"line":1437},[643,1784,1785],{"class":1023},"   *\u002F\n",[643,1787,1788,1791,1794,1796,1799,1802,1805,1808,1811,1813,1815,1818,1820,1822,1824,1826],{"class":645,"line":1443},[643,1789,1790],{"class":735},"  const",[643,1792,1793],{"class":1038}," todos",[643,1795,1459],{"class":726},[643,1797,1798],{"class":666}," useState",[643,1800,1801],{"class":726},"\u003C",[643,1803,1804],{"class":649},"Todo",[643,1806,1807],{"class":1046},"[]",[643,1809,1810],{"class":726},">",[643,1812,1039],{"class":1046},[643,1814,1058],{"class":726},[643,1816,1817],{"class":653},"todos",[643,1819,1058],{"class":726},[643,1821,1180],{"class":726},[643,1823,1722],{"class":726},[643,1825,1725],{"class":735},[643,1827,1828],{"class":1046}," [])\n",[643,1830,1831],{"class":645,"line":1448},[643,1832,1307],{"emptyLinePlaceholder":1306},[643,1834,1836],{"class":645,"line":1835},26,[643,1837,1750],{"class":1023},[643,1839,1841],{"class":645,"line":1840},27,[643,1842,1843],{"class":1023},"   * Filter state — cũng share qua useState để TodoList và TodoFilter\n",[643,1845,1847],{"class":645,"line":1846},28,[643,1848,1849],{"class":1023},"   * cùng đọc 1 nguồn filter mà không cần emit qua lại.\n",[643,1851,1853],{"class":645,"line":1852},29,[643,1854,1785],{"class":1023},[643,1856,1858,1860,1863,1865,1867,1869,1872,1874,1876,1878,1881,1883,1885,1887,1889,1891,1893,1895],{"class":645,"line":1857},30,[643,1859,1790],{"class":735},[643,1861,1862],{"class":1038}," filter",[643,1864,1459],{"class":726},[643,1866,1798],{"class":666},[643,1868,1801],{"class":726},[643,1870,1871],{"class":649},"TodoFilter",[643,1873,1810],{"class":726},[643,1875,1039],{"class":1046},[643,1877,1058],{"class":726},[643,1879,1880],{"class":653},"todoFilter",[643,1882,1058],{"class":726},[643,1884,1180],{"class":726},[643,1886,1722],{"class":726},[643,1888,1725],{"class":735},[643,1890,1052],{"class":726},[643,1892,1464],{"class":653},[643,1894,1058],{"class":726},[643,1896,1217],{"class":1046},[643,1898,1900],{"class":645,"line":1899},31,[643,1901,1307],{"emptyLinePlaceholder":1306},[643,1903,1905],{"class":645,"line":1904},32,[643,1906,1732],{"class":1023},[643,1908,1910],{"class":645,"line":1909},33,[643,1911,1912],{"class":1023},"  \u002F\u002F LOCALSTORAGE PERSISTENCE\n",[643,1914,1916],{"class":645,"line":1915},34,[643,1917,1732],{"class":1023},[643,1919,1921],{"class":645,"line":1920},35,[643,1922,1307],{"emptyLinePlaceholder":1306},[643,1924,1926],{"class":645,"line":1925},36,[643,1927,1928],{"class":1023},"  \u002F\u002F Load dữ liệu từ localStorage khi app khởi tạo (chỉ ở client)\n",[643,1930,1932,1935,1938,1940,1942,1945,1947,1950,1953],{"class":645,"line":1931},37,[643,1933,1934],{"class":1029},"  if",[643,1936,1937],{"class":1046}," (",[643,1939,968],{"class":1029},[643,1941,497],{"class":726},[643,1943,1944],{"class":1038},"meta",[643,1946,497],{"class":726},[643,1948,1949],{"class":1038},"client",[643,1951,1952],{"class":1046},") ",[643,1954,727],{"class":726},[643,1956,1958,1961],{"class":645,"line":1957},38,[643,1959,1960],{"class":1029},"    try",[643,1962,801],{"class":726},[643,1964,1966,1969,1972,1974,1977,1979,1982,1984,1986,1989,1991],{"class":645,"line":1965},39,[643,1967,1968],{"class":735},"      const",[643,1970,1971],{"class":1038}," stored",[643,1973,1459],{"class":726},[643,1975,1976],{"class":1038}," localStorage",[643,1978,497],{"class":726},[643,1980,1981],{"class":666},"getItem",[643,1983,1039],{"class":1046},[643,1985,1058],{"class":726},[643,1987,1988],{"class":653},"nuxt-todos",[643,1990,1058],{"class":726},[643,1992,1217],{"class":1046},[643,1994,1996,1999,2001,2004,2006],{"class":645,"line":1995},40,[643,1997,1998],{"class":1029},"      if",[643,2000,1937],{"class":1046},[643,2002,2003],{"class":1038},"stored",[643,2005,1952],{"class":1046},[643,2007,727],{"class":726},[643,2009,2011],{"class":645,"line":2010},41,[643,2012,2013],{"class":1023},"        \u002F\u002F Parse JSON và chuyển createdAt từ string thành Date\n",[643,2015,2017,2020,2023,2025,2027,2030,2032,2035,2037,2040,2042,2044],{"class":645,"line":2016},42,[643,2018,2019],{"class":735},"        const",[643,2021,2022],{"class":1038}," parsed",[643,2024,742],{"class":726},[643,2026,1337],{"class":649},[643,2028,2029],{"class":1046},"[] ",[643,2031,1567],{"class":726},[643,2033,2034],{"class":1038}," JSON",[643,2036,497],{"class":726},[643,2038,2039],{"class":666},"parse",[643,2041,1039],{"class":1046},[643,2043,2003],{"class":1038},[643,2045,1217],{"class":1046},[643,2047,2049,2052,2054,2057,2059,2061,2063,2066,2068,2072,2074,2076],{"class":645,"line":2048},43,[643,2050,2051],{"class":1038},"        todos",[643,2053,497],{"class":726},[643,2055,2056],{"class":1038},"value",[643,2058,1459],{"class":726},[643,2060,2022],{"class":1038},[643,2062,497],{"class":726},[643,2064,2065],{"class":666},"map",[643,2067,1039],{"class":1046},[643,2069,2071],{"class":2070},"sHdIc","todo",[643,2073,1725],{"class":735},[643,2075,1937],{"class":1046},[643,2077,727],{"class":726},[643,2079,2081,2084,2086],{"class":645,"line":2080},44,[643,2082,2083],{"class":726},"          ...",[643,2085,2071],{"class":1038},[643,2087,753],{"class":726},[643,2089,2091,2094,2096,2099,2102,2104,2106,2108,2111],{"class":645,"line":2090},45,[643,2092,2093],{"class":1046},"          createdAt",[643,2095,742],{"class":726},[643,2097,2098],{"class":726}," new",[643,2100,2101],{"class":666}," Date",[643,2103,1039],{"class":1046},[643,2105,2071],{"class":1038},[643,2107,497],{"class":726},[643,2109,2110],{"class":1038},"createdAt",[643,2112,1217],{"class":1046},[643,2114,2116,2119],{"class":645,"line":2115},46,[643,2117,2118],{"class":726},"        }",[643,2120,2121],{"class":1046},"))\n",[643,2123,2125],{"class":645,"line":2124},47,[643,2126,2127],{"class":726},"      }\n",[643,2129,2131,2134,2137],{"class":645,"line":2130},48,[643,2132,2133],{"class":726},"    }",[643,2135,2136],{"class":1029}," catch",[643,2138,801],{"class":726},[643,2140,2142],{"class":645,"line":2141},49,[643,2143,2144],{"class":1023},"      \u002F\u002F Nếu localStorage bị corrupt hoặc không có dữ liệu — bỏ qua\n",[643,2146,2148,2151,2153,2156,2158,2160,2163,2165],{"class":645,"line":2147},50,[643,2149,2150],{"class":1038},"      console",[643,2152,497],{"class":726},[643,2154,2155],{"class":666},"warn",[643,2157,1039],{"class":1046},[643,2159,1058],{"class":726},[643,2161,2162],{"class":653},"Failed to load todos from localStorage",[643,2164,1058],{"class":726},[643,2166,1217],{"class":1046},[643,2168,2170],{"class":645,"line":2169},51,[643,2171,1205],{"class":726},[643,2173,2175],{"class":645,"line":2174},52,[643,2176,951],{"class":726},[643,2178,2180],{"class":645,"line":2179},53,[643,2181,1307],{"emptyLinePlaceholder":1306},[643,2183,2185],{"class":645,"line":2184},54,[643,2186,1750],{"class":1023},[643,2188,2190],{"class":645,"line":2189},55,[643,2191,2192],{"class":1023},"   * watch — theo dõi sự thay đổi của todos và tự động lưu vào localStorage.\n",[643,2194,2196],{"class":645,"line":2195},56,[643,2197,1760],{"class":1023},[643,2199,2201],{"class":645,"line":2200},57,[643,2202,2203],{"class":1023},"   * { deep: true }: theo dõi cả thay đổi bên trong array (add\u002Fremove item,\n",[643,2205,2207],{"class":645,"line":2206},58,[643,2208,2209],{"class":1023},"   * thay đổi completed, thay đổi title). Mặc định watch chỉ theo dõi\n",[643,2211,2213],{"class":645,"line":2212},59,[643,2214,2215],{"class":1023},"   * tham chiếu của ref — không phát hiện mutation bên trong.\n",[643,2217,2219],{"class":645,"line":2218},60,[643,2220,1785],{"class":1023},[643,2222,2224,2226,2228,2230,2232,2234,2236,2238,2240],{"class":645,"line":2223},61,[643,2225,1934],{"class":1029},[643,2227,1937],{"class":1046},[643,2229,968],{"class":1029},[643,2231,497],{"class":726},[643,2233,1944],{"class":1038},[643,2235,497],{"class":726},[643,2237,1949],{"class":1038},[643,2239,1952],{"class":1046},[643,2241,727],{"class":726},[643,2243,2245,2248,2250,2252,2254,2256,2259,2262,2264],{"class":645,"line":2244},62,[643,2246,2247],{"class":666},"    watch",[643,2249,1039],{"class":1046},[643,2251,1817],{"class":1038},[643,2253,1180],{"class":726},[643,2255,1937],{"class":726},[643,2257,2258],{"class":2070},"newTodos",[643,2260,2261],{"class":726},")",[643,2263,1725],{"class":735},[643,2265,801],{"class":726},[643,2267,2269,2272,2274,2277,2279,2281,2283,2285,2287,2289,2291,2294,2296,2298],{"class":645,"line":2268},63,[643,2270,2271],{"class":1038},"      localStorage",[643,2273,497],{"class":726},[643,2275,2276],{"class":666},"setItem",[643,2278,1039],{"class":1046},[643,2280,1058],{"class":726},[643,2282,1988],{"class":653},[643,2284,1058],{"class":726},[643,2286,1180],{"class":726},[643,2288,2034],{"class":1038},[643,2290,497],{"class":726},[643,2292,2293],{"class":666},"stringify",[643,2295,1039],{"class":1046},[643,2297,2258],{"class":1038},[643,2299,2121],{"class":1046},[643,2301,2303,2306,2308,2311,2313,2315,2317],{"class":645,"line":2302},64,[643,2304,2305],{"class":726},"    },",[643,2307,1070],{"class":726},[643,2309,2310],{"class":1046}," deep",[643,2312,742],{"class":726},[643,2314,1079],{"class":1078},[643,2316,1665],{"class":726},[643,2318,1217],{"class":1046},[643,2320,2322],{"class":645,"line":2321},65,[643,2323,951],{"class":726},[643,2325,2327],{"class":645,"line":2326},66,[643,2328,1307],{"emptyLinePlaceholder":1306},[643,2330,2332],{"class":645,"line":2331},67,[643,2333,1732],{"class":1023},[643,2335,2337],{"class":645,"line":2336},68,[643,2338,2339],{"class":1023},"  \u002F\u002F COMPUTED — reactive derivation\n",[643,2341,2343],{"class":645,"line":2342},69,[643,2344,1732],{"class":1023},[643,2346,2348],{"class":645,"line":2347},70,[643,2349,1307],{"emptyLinePlaceholder":1306},[643,2351,2353],{"class":645,"line":2352},71,[643,2354,1750],{"class":1023},[643,2356,2358],{"class":645,"line":2357},72,[643,2359,2360],{"class":1023},"   * computed — tự động tính toán lại khi dependency (todos) thay đổi.\n",[643,2362,2364],{"class":645,"line":2363},73,[643,2365,2366],{"class":1023},"   * Giống như Excel formula — thay đổi input, output tự update.\n",[643,2368,2370],{"class":645,"line":2369},74,[643,2371,1785],{"class":1023},[643,2373,2375,2377,2380,2382,2385,2387,2390,2392,2394,2396,2398,2400,2403,2405,2408,2410,2413,2415,2417,2419],{"class":645,"line":2374},75,[643,2376,1790],{"class":735},[643,2378,2379],{"class":1038}," activeTodos",[643,2381,1459],{"class":726},[643,2383,2384],{"class":666}," computed",[643,2386,1039],{"class":1046},[643,2388,2389],{"class":726},"()",[643,2391,1725],{"class":735},[643,2393,1793],{"class":1038},[643,2395,497],{"class":726},[643,2397,2056],{"class":1038},[643,2399,497],{"class":726},[643,2401,2402],{"class":666},"filter",[643,2404,1039],{"class":1046},[643,2406,2407],{"class":2070},"t",[643,2409,1725],{"class":735},[643,2411,2412],{"class":726}," !",[643,2414,2407],{"class":1038},[643,2416,497],{"class":726},[643,2418,1483],{"class":1038},[643,2420,2121],{"class":1046},[643,2422,2424,2426,2429,2431,2433,2435,2437,2439,2441,2443,2445,2447,2449,2451,2453,2455,2458,2460,2462],{"class":645,"line":2423},76,[643,2425,1790],{"class":735},[643,2427,2428],{"class":1038}," completedTodos",[643,2430,1459],{"class":726},[643,2432,2384],{"class":666},[643,2434,1039],{"class":1046},[643,2436,2389],{"class":726},[643,2438,1725],{"class":735},[643,2440,1793],{"class":1038},[643,2442,497],{"class":726},[643,2444,2056],{"class":1038},[643,2446,497],{"class":726},[643,2448,2402],{"class":666},[643,2450,1039],{"class":1046},[643,2452,2407],{"class":2070},[643,2454,1725],{"class":735},[643,2456,2457],{"class":1038}," t",[643,2459,497],{"class":726},[643,2461,1483],{"class":1038},[643,2463,2121],{"class":1046},[643,2465,2467],{"class":645,"line":2466},77,[643,2468,1307],{"emptyLinePlaceholder":1306},[643,2470,2472,2474,2477,2479,2481,2483,2485,2487,2489,2491,2493,2495,2498],{"class":645,"line":2471},78,[643,2473,1790],{"class":735},[643,2475,2476],{"class":1038}," activeCount",[643,2478,1459],{"class":726},[643,2480,2384],{"class":666},[643,2482,1039],{"class":1046},[643,2484,2389],{"class":726},[643,2486,1725],{"class":735},[643,2488,2379],{"class":1038},[643,2490,497],{"class":726},[643,2492,2056],{"class":1038},[643,2494,497],{"class":726},[643,2496,2497],{"class":1038},"length",[643,2499,1217],{"class":1046},[643,2501,2503,2505,2508,2510,2512,2514,2516,2518,2520,2522,2524,2526,2528],{"class":645,"line":2502},79,[643,2504,1790],{"class":735},[643,2506,2507],{"class":1038}," completedCount",[643,2509,1459],{"class":726},[643,2511,2384],{"class":666},[643,2513,1039],{"class":1046},[643,2515,2389],{"class":726},[643,2517,1725],{"class":735},[643,2519,2428],{"class":1038},[643,2521,497],{"class":726},[643,2523,2056],{"class":1038},[643,2525,497],{"class":726},[643,2527,2497],{"class":1038},[643,2529,1217],{"class":1046},[643,2531,2533],{"class":645,"line":2532},80,[643,2534,1307],{"emptyLinePlaceholder":1306},[643,2536,2538],{"class":645,"line":2537},81,[643,2539,1750],{"class":1023},[643,2541,2543],{"class":645,"line":2542},82,[643,2544,2545],{"class":1023},"   * filteredTodos — dựa trên filter state hiện tại, lọc todos tương ứng.\n",[643,2547,2549],{"class":645,"line":2548},83,[643,2550,2551],{"class":1023},"   * Đây là computed được dùng trong TodoList để render đúng danh sách.\n",[643,2553,2555],{"class":645,"line":2554},84,[643,2556,1785],{"class":1023},[643,2558,2560,2562,2565,2567,2569,2571,2573,2575],{"class":645,"line":2559},85,[643,2561,1790],{"class":735},[643,2563,2564],{"class":1038}," filteredTodos",[643,2566,1459],{"class":726},[643,2568,2384],{"class":666},[643,2570,1039],{"class":1046},[643,2572,2389],{"class":726},[643,2574,1725],{"class":735},[643,2576,801],{"class":726},[643,2578,2580,2583,2585,2587,2589,2591,2593],{"class":645,"line":2579},86,[643,2581,2582],{"class":1029},"    switch",[643,2584,1937],{"class":1046},[643,2586,2402],{"class":1038},[643,2588,497],{"class":726},[643,2590,2056],{"class":1038},[643,2592,1952],{"class":1046},[643,2594,727],{"class":726},[643,2596,2598,2601,2603,2605,2607],{"class":645,"line":2597},87,[643,2599,2600],{"class":1029},"      case",[643,2602,1052],{"class":726},[643,2604,1474],{"class":653},[643,2606,1058],{"class":726},[643,2608,2609],{"class":726},":\n",[643,2611,2613,2616,2618,2620],{"class":645,"line":2612},88,[643,2614,2615],{"class":1029},"        return",[643,2617,2379],{"class":1038},[643,2619,497],{"class":726},[643,2621,2622],{"class":1038},"value\n",[643,2624,2626,2628,2630,2632,2634],{"class":645,"line":2625},89,[643,2627,2600],{"class":1029},[643,2629,1052],{"class":726},[643,2631,1483],{"class":653},[643,2633,1058],{"class":726},[643,2635,2609],{"class":726},[643,2637,2639,2641,2643,2645],{"class":645,"line":2638},90,[643,2640,2615],{"class":1029},[643,2642,2428],{"class":1038},[643,2644,497],{"class":726},[643,2646,2622],{"class":1038},[643,2648,2650,2653],{"class":645,"line":2649},91,[643,2651,2652],{"class":1029},"      default",[643,2654,2609],{"class":726},[643,2656,2658,2660,2662,2664],{"class":645,"line":2657},92,[643,2659,2615],{"class":1029},[643,2661,1793],{"class":1038},[643,2663,497],{"class":726},[643,2665,2622],{"class":1038},[643,2667,2669],{"class":645,"line":2668},93,[643,2670,1205],{"class":726},[643,2672,2674,2677],{"class":645,"line":2673},94,[643,2675,2676],{"class":726},"  }",[643,2678,1217],{"class":1046},[643,2680,2682],{"class":645,"line":2681},95,[643,2683,1307],{"emptyLinePlaceholder":1306},[643,2685,2687],{"class":645,"line":2686},96,[643,2688,1732],{"class":1023},[643,2690,2692],{"class":645,"line":2691},97,[643,2693,2694],{"class":1023},"  \u002F\u002F CRUD OPERATIONS\n",[643,2696,2698],{"class":645,"line":2697},98,[643,2699,1732],{"class":1023},[643,2701,2703],{"class":645,"line":2702},99,[643,2704,1307],{"emptyLinePlaceholder":1306},[643,2706,2708],{"class":645,"line":2707},100,[643,2709,1750],{"class":1023},[643,2711,2713],{"class":645,"line":2712},101,[643,2714,2715],{"class":1023},"   * Thêm todo mới.\n",[643,2717,2719],{"class":645,"line":2718},102,[643,2720,2721],{"class":1023},"   * - Dùng crypto.randomUUID() cho id duy nhất.\n",[643,2723,2725],{"class":645,"line":2724},103,[643,2726,2727],{"class":1023},"   * - Đặt createdAt là thời điểm hiện tại.\n",[643,2729,2731],{"class":645,"line":2730},104,[643,2732,2733],{"class":1023},"   * - Tạo object Todo mới và push vào array.\n",[643,2735,2737],{"class":645,"line":2736},105,[643,2738,1785],{"class":1023},[643,2740,2742,2744,2747,2749,2751,2754,2756,2759,2761,2763],{"class":645,"line":2741},106,[643,2743,1790],{"class":735},[643,2745,2746],{"class":1038}," addTodo",[643,2748,1459],{"class":726},[643,2750,1937],{"class":726},[643,2752,2753],{"class":2070},"title",[643,2755,742],{"class":726},[643,2757,2758],{"class":649}," string",[643,2760,2261],{"class":726},[643,2762,1725],{"class":735},[643,2764,801],{"class":726},[643,2766,2768,2771,2774,2776,2779,2781,2784],{"class":645,"line":2767},107,[643,2769,2770],{"class":735},"    const",[643,2772,2773],{"class":1038}," trimmed",[643,2775,1459],{"class":726},[643,2777,2778],{"class":1038}," title",[643,2780,497],{"class":726},[643,2782,2783],{"class":666},"trim",[643,2785,1578],{"class":1046},[643,2787,2789,2792,2794,2797,2800,2802,2805],{"class":645,"line":2788},108,[643,2790,2791],{"class":1029},"    if",[643,2793,1937],{"class":1046},[643,2795,2796],{"class":726},"!",[643,2798,2799],{"class":1038},"trimmed",[643,2801,1952],{"class":1046},[643,2803,2804],{"class":1029},"return",[643,2806,2807],{"class":1023},"  \u002F\u002F Không thêm todo rỗng\n",[643,2809,2811],{"class":645,"line":2810},109,[643,2812,1307],{"emptyLinePlaceholder":1306},[643,2814,2816,2818,2821,2823,2825,2827],{"class":645,"line":2815},110,[643,2817,2770],{"class":735},[643,2819,2820],{"class":1038}," todo",[643,2822,742],{"class":726},[643,2824,1337],{"class":649},[643,2826,1459],{"class":726},[643,2828,801],{"class":726},[643,2830,2832,2835,2837,2839,2841,2843,2845],{"class":645,"line":2831},111,[643,2833,2834],{"class":1046},"      id",[643,2836,742],{"class":726},[643,2838,1570],{"class":1038},[643,2840,497],{"class":726},[643,2842,1575],{"class":666},[643,2844,2389],{"class":1046},[643,2846,753],{"class":726},[643,2848,2850,2852,2854,2856],{"class":645,"line":2849},112,[643,2851,1139],{"class":1046},[643,2853,742],{"class":726},[643,2855,2773],{"class":1038},[643,2857,753],{"class":726},[643,2859,2861,2864,2866,2869],{"class":645,"line":2860},113,[643,2862,2863],{"class":1046},"      completed",[643,2865,742],{"class":726},[643,2867,2868],{"class":1078}," false",[643,2870,753],{"class":726},[643,2872,2874,2877,2879,2881,2883],{"class":645,"line":2873},114,[643,2875,2876],{"class":1046},"      createdAt",[643,2878,742],{"class":726},[643,2880,2098],{"class":726},[643,2882,2101],{"class":666},[643,2884,1578],{"class":1046},[643,2886,2888],{"class":645,"line":2887},115,[643,2889,1205],{"class":726},[643,2891,2893,2896,2898,2900,2902,2905,2907,2909],{"class":645,"line":2892},116,[643,2894,2895],{"class":1038},"    todos",[643,2897,497],{"class":726},[643,2899,2056],{"class":1038},[643,2901,497],{"class":726},[643,2903,2904],{"class":666},"push",[643,2906,1039],{"class":1046},[643,2908,2071],{"class":1038},[643,2910,1217],{"class":1046},[643,2912,2914],{"class":645,"line":2913},117,[643,2915,951],{"class":726},[643,2917,2919],{"class":645,"line":2918},118,[643,2920,1307],{"emptyLinePlaceholder":1306},[643,2922,2924],{"class":645,"line":2923},119,[643,2925,1750],{"class":1023},[643,2927,2929],{"class":645,"line":2928},120,[643,2930,2931],{"class":1023},"   * Toggle trạng thái completed của todo.\n",[643,2933,2935],{"class":645,"line":2934},121,[643,2936,2937],{"class":1023},"   * Tìm todo theo id, nếu thấy thì đảo ngược completed.\n",[643,2939,2941],{"class":645,"line":2940},122,[643,2942,1785],{"class":1023},[643,2944,2946,2948,2951,2953,2955,2958,2960,2962,2964,2966],{"class":645,"line":2945},123,[643,2947,1790],{"class":735},[643,2949,2950],{"class":1038}," toggleTodo",[643,2952,1459],{"class":726},[643,2954,1937],{"class":726},[643,2956,2957],{"class":2070},"id",[643,2959,742],{"class":726},[643,2961,2758],{"class":649},[643,2963,2261],{"class":726},[643,2965,1725],{"class":735},[643,2967,801],{"class":726},[643,2969,2971,2973,2975,2977,2979,2981,2983,2985,2988,2990,2992,2994,2996,2998,3000,3003,3006],{"class":645,"line":2970},124,[643,2972,2770],{"class":735},[643,2974,2820],{"class":1038},[643,2976,1459],{"class":726},[643,2978,1793],{"class":1038},[643,2980,497],{"class":726},[643,2982,2056],{"class":1038},[643,2984,497],{"class":726},[643,2986,2987],{"class":666},"find",[643,2989,1039],{"class":1046},[643,2991,2407],{"class":2070},[643,2993,1725],{"class":735},[643,2995,2457],{"class":1038},[643,2997,497],{"class":726},[643,2999,2957],{"class":1038},[643,3001,3002],{"class":726}," ===",[643,3004,3005],{"class":1038}," id",[643,3007,1217],{"class":1046},[643,3009,3011,3013,3015,3017,3019],{"class":645,"line":3010},125,[643,3012,2791],{"class":1029},[643,3014,1937],{"class":1046},[643,3016,2071],{"class":1038},[643,3018,1952],{"class":1046},[643,3020,727],{"class":726},[643,3022,3024,3027,3029,3031,3033,3035,3037,3039],{"class":645,"line":3023},126,[643,3025,3026],{"class":1038},"      todo",[643,3028,497],{"class":726},[643,3030,1483],{"class":1038},[643,3032,1459],{"class":726},[643,3034,2412],{"class":726},[643,3036,2071],{"class":1038},[643,3038,497],{"class":726},[643,3040,3041],{"class":1038},"completed\n",[643,3043,3045],{"class":645,"line":3044},127,[643,3046,1205],{"class":726},[643,3048,3050],{"class":645,"line":3049},128,[643,3051,951],{"class":726},[643,3053,3055],{"class":645,"line":3054},129,[643,3056,1307],{"emptyLinePlaceholder":1306},[643,3058,3060],{"class":645,"line":3059},130,[643,3061,1750],{"class":1023},[643,3063,3065],{"class":645,"line":3064},131,[643,3066,3067],{"class":1023},"   * Xóa todo theo id.\n",[643,3069,3071],{"class":645,"line":3070},132,[643,3072,3073],{"class":1023},"   * Dùng filter để tạo array mới không chứa id đó.\n",[643,3075,3077],{"class":645,"line":3076},133,[643,3078,3079],{"class":1023},"   * Gán lại cho todos.value để trigger reactivity.\n",[643,3081,3083],{"class":645,"line":3082},134,[643,3084,1785],{"class":1023},[643,3086,3088,3090,3093,3095,3097,3099,3101,3103,3105,3107],{"class":645,"line":3087},135,[643,3089,1790],{"class":735},[643,3091,3092],{"class":1038}," removeTodo",[643,3094,1459],{"class":726},[643,3096,1937],{"class":726},[643,3098,2957],{"class":2070},[643,3100,742],{"class":726},[643,3102,2758],{"class":649},[643,3104,2261],{"class":726},[643,3106,1725],{"class":735},[643,3108,801],{"class":726},[643,3110,3112,3114,3116,3118,3120,3122,3124,3126,3128,3130,3132,3134,3136,3138,3140,3142,3145,3147],{"class":645,"line":3111},136,[643,3113,2895],{"class":1038},[643,3115,497],{"class":726},[643,3117,2056],{"class":1038},[643,3119,1459],{"class":726},[643,3121,1793],{"class":1038},[643,3123,497],{"class":726},[643,3125,2056],{"class":1038},[643,3127,497],{"class":726},[643,3129,2402],{"class":666},[643,3131,1039],{"class":1046},[643,3133,2407],{"class":2070},[643,3135,1725],{"class":735},[643,3137,2457],{"class":1038},[643,3139,497],{"class":726},[643,3141,2957],{"class":1038},[643,3143,3144],{"class":726}," !==",[643,3146,3005],{"class":1038},[643,3148,1217],{"class":1046},[643,3150,3152],{"class":645,"line":3151},137,[643,3153,951],{"class":726},[643,3155,3157],{"class":645,"line":3156},138,[643,3158,1307],{"emptyLinePlaceholder":1306},[643,3160,3162],{"class":645,"line":3161},139,[643,3163,1750],{"class":1023},[643,3165,3167],{"class":645,"line":3166},140,[643,3168,3169],{"class":1023},"   * Chỉnh sửa title của todo.\n",[643,3171,3173],{"class":645,"line":3172},141,[643,3174,3175],{"class":1023},"   * Tìm todo theo id, cập nhật title mới.\n",[643,3177,3179],{"class":645,"line":3178},142,[643,3180,1785],{"class":1023},[643,3182,3184,3186,3189,3191,3193,3195,3197,3199,3201,3203,3205,3207,3209,3211],{"class":645,"line":3183},143,[643,3185,1790],{"class":735},[643,3187,3188],{"class":1038}," editTodo",[643,3190,1459],{"class":726},[643,3192,1937],{"class":726},[643,3194,2957],{"class":2070},[643,3196,742],{"class":726},[643,3198,2758],{"class":649},[643,3200,1180],{"class":726},[643,3202,2778],{"class":2070},[643,3204,742],{"class":726},[643,3206,2758],{"class":649},[643,3208,2261],{"class":726},[643,3210,1725],{"class":735},[643,3212,801],{"class":726},[643,3214,3216,3218,3220,3222,3224,3226,3228],{"class":645,"line":3215},144,[643,3217,2770],{"class":735},[643,3219,2773],{"class":1038},[643,3221,1459],{"class":726},[643,3223,2778],{"class":1038},[643,3225,497],{"class":726},[643,3227,2783],{"class":666},[643,3229,1578],{"class":1046},[643,3231,3233,3235,3237,3239,3241,3243],{"class":645,"line":3232},145,[643,3234,2791],{"class":1029},[643,3236,1937],{"class":1046},[643,3238,2796],{"class":726},[643,3240,2799],{"class":1038},[643,3242,1952],{"class":1046},[643,3244,727],{"class":726},[643,3246,3248],{"class":645,"line":3247},146,[643,3249,3250],{"class":1023},"      \u002F\u002F Nếu title rỗng sau khi trim — xóa todo luôn\n",[643,3252,3254,3257,3259,3261],{"class":645,"line":3253},147,[643,3255,3256],{"class":666},"      removeTodo",[643,3258,1039],{"class":1046},[643,3260,2957],{"class":1038},[643,3262,1217],{"class":1046},[643,3264,3266],{"class":645,"line":3265},148,[643,3267,3268],{"class":1029},"      return\n",[643,3270,3272],{"class":645,"line":3271},149,[643,3273,1205],{"class":726},[643,3275,3277],{"class":645,"line":3276},150,[643,3278,1307],{"emptyLinePlaceholder":1306},[643,3280,3282,3284,3286,3288,3290,3292,3294,3296,3298,3300,3302,3304,3306,3308,3310,3312,3314],{"class":645,"line":3281},151,[643,3283,2770],{"class":735},[643,3285,2820],{"class":1038},[643,3287,1459],{"class":726},[643,3289,1793],{"class":1038},[643,3291,497],{"class":726},[643,3293,2056],{"class":1038},[643,3295,497],{"class":726},[643,3297,2987],{"class":666},[643,3299,1039],{"class":1046},[643,3301,2407],{"class":2070},[643,3303,1725],{"class":735},[643,3305,2457],{"class":1038},[643,3307,497],{"class":726},[643,3309,2957],{"class":1038},[643,3311,3002],{"class":726},[643,3313,3005],{"class":1038},[643,3315,1217],{"class":1046},[643,3317,3319,3321,3323,3325,3327],{"class":645,"line":3318},152,[643,3320,2791],{"class":1029},[643,3322,1937],{"class":1046},[643,3324,2071],{"class":1038},[643,3326,1952],{"class":1046},[643,3328,727],{"class":726},[643,3330,3332,3334,3336,3338,3340],{"class":645,"line":3331},153,[643,3333,3026],{"class":1038},[643,3335,497],{"class":726},[643,3337,2753],{"class":1038},[643,3339,1459],{"class":726},[643,3341,3342],{"class":1038}," trimmed\n",[643,3344,3346],{"class":645,"line":3345},154,[643,3347,1205],{"class":726},[643,3349,3351],{"class":645,"line":3350},155,[643,3352,951],{"class":726},[643,3354,3356],{"class":645,"line":3355},156,[643,3357,1307],{"emptyLinePlaceholder":1306},[643,3359,3361],{"class":645,"line":3360},157,[643,3362,1750],{"class":1023},[643,3364,3366],{"class":645,"line":3365},158,[643,3367,3368],{"class":1023},"   * Xóa tất cả todo đã hoàn thành.\n",[643,3370,3372],{"class":645,"line":3371},159,[643,3373,3374],{"class":1023},"   * Dùng filter để giữ lại những todo chưa completed.\n",[643,3376,3378],{"class":645,"line":3377},160,[643,3379,1785],{"class":1023},[643,3381,3383,3385,3388,3390,3392,3394],{"class":645,"line":3382},161,[643,3384,1790],{"class":735},[643,3386,3387],{"class":1038}," clearCompleted",[643,3389,1459],{"class":726},[643,3391,1722],{"class":726},[643,3393,1725],{"class":735},[643,3395,801],{"class":726},[643,3397,3399,3401,3403,3405,3407,3409,3411,3413,3415,3417,3419,3421,3423,3425,3427,3429,3431],{"class":645,"line":3398},162,[643,3400,2895],{"class":1038},[643,3402,497],{"class":726},[643,3404,2056],{"class":1038},[643,3406,1459],{"class":726},[643,3408,1793],{"class":1038},[643,3410,497],{"class":726},[643,3412,2056],{"class":1038},[643,3414,497],{"class":726},[643,3416,2402],{"class":666},[643,3418,1039],{"class":1046},[643,3420,2407],{"class":2070},[643,3422,1725],{"class":735},[643,3424,2412],{"class":726},[643,3426,2407],{"class":1038},[643,3428,497],{"class":726},[643,3430,1483],{"class":1038},[643,3432,1217],{"class":1046},[643,3434,3436],{"class":645,"line":3435},163,[643,3437,951],{"class":726},[643,3439,3441],{"class":645,"line":3440},164,[643,3442,1307],{"emptyLinePlaceholder":1306},[643,3444,3446],{"class":645,"line":3445},165,[643,3447,1732],{"class":1023},[643,3449,3451],{"class":645,"line":3450},166,[643,3452,3453],{"class":1023},"  \u002F\u002F RETURN — chỉ expose những gì component cần\n",[643,3455,3457],{"class":645,"line":3456},167,[643,3458,1732],{"class":1023},[643,3460,3462],{"class":645,"line":3461},168,[643,3463,1307],{"emptyLinePlaceholder":1306},[643,3465,3467,3470],{"class":645,"line":3466},169,[643,3468,3469],{"class":1029},"  return",[643,3471,801],{"class":726},[643,3473,3475],{"class":645,"line":3474},170,[643,3476,3477],{"class":1023},"    \u002F\u002F State\n",[643,3479,3481,3483],{"class":645,"line":3480},171,[643,3482,2895],{"class":1038},[643,3484,753],{"class":726},[643,3486,3488,3491],{"class":645,"line":3487},172,[643,3489,3490],{"class":1038},"    filter",[643,3492,753],{"class":726},[643,3494,3496],{"class":645,"line":3495},173,[643,3497,1307],{"emptyLinePlaceholder":1306},[643,3499,3501],{"class":645,"line":3500},174,[643,3502,3503],{"class":1023},"    \u002F\u002F Computed\n",[643,3505,3507,3510],{"class":645,"line":3506},175,[643,3508,3509],{"class":1038},"    activeTodos",[643,3511,753],{"class":726},[643,3513,3515,3518],{"class":645,"line":3514},176,[643,3516,3517],{"class":1038},"    completedTodos",[643,3519,753],{"class":726},[643,3521,3523,3526],{"class":645,"line":3522},177,[643,3524,3525],{"class":1038},"    activeCount",[643,3527,753],{"class":726},[643,3529,3531,3534],{"class":645,"line":3530},178,[643,3532,3533],{"class":1038},"    completedCount",[643,3535,753],{"class":726},[643,3537,3539,3542],{"class":645,"line":3538},179,[643,3540,3541],{"class":1038},"    filteredTodos",[643,3543,753],{"class":726},[643,3545,3547],{"class":645,"line":3546},180,[643,3548,1307],{"emptyLinePlaceholder":1306},[643,3550,3552],{"class":645,"line":3551},181,[643,3553,3554],{"class":1023},"    \u002F\u002F Actions\n",[643,3556,3558,3561],{"class":645,"line":3557},182,[643,3559,3560],{"class":1038},"    addTodo",[643,3562,753],{"class":726},[643,3564,3566,3569],{"class":645,"line":3565},183,[643,3567,3568],{"class":1038},"    toggleTodo",[643,3570,753],{"class":726},[643,3572,3574,3577],{"class":645,"line":3573},184,[643,3575,3576],{"class":1038},"    removeTodo",[643,3578,753],{"class":726},[643,3580,3582,3585],{"class":645,"line":3581},185,[643,3583,3584],{"class":1038},"    editTodo",[643,3586,753],{"class":726},[643,3588,3590],{"class":645,"line":3589},186,[643,3591,3592],{"class":1038},"    clearCompleted\n",[643,3594,3596],{"class":645,"line":3595},187,[643,3597,951],{"class":726},[643,3599,3601],{"class":645,"line":3600},188,[643,3602,957],{"class":726},[466,3604,3606],{"id":3605},"giải-thích-sâu-từng-dòng","Giải thích sâu từng dòng",[449,3608,3609,3621],{"color":1220,"icon":1221},[432,3610,1222,3611],{},[436,3612,3613,1497,3615,3617,3618,3620],{},[487,3614,513],{},[487,3616,517],{}," — tại sao dùng ",[487,3619,513],{},"?",[471,3622,3623,3639,3652],{},[474,3624,3625,3630,3631,3634,3635,3638],{},[436,3626,3627],{},[487,3628,3629],{},"useState('todos', () => [])",": Tạo một ref được ",[436,3632,3633],{},"share giữa tất cả component"," gọi cùng key ",[487,3636,3637],{},"'todos'",". SSR-safe — mỗi request server có state riêng.",[474,3640,3641,3647,3648,3651],{},[436,3642,3643,3646],{},[487,3644,3645],{},"ref([])"," trong module scope",": State sẽ ",[436,3649,3650],{},"shared giữa các request"," ở SSR — request A thay state request B. Rất nguy hiểm.",[474,3653,3654,3659],{},[436,3655,3656,3658],{},[487,3657,3645],{}," trong component",": Mỗi component có state riêng — không share được giữa TodoForm và TodoList.",[432,3661,3662,742],{},[436,3663,3664,3665],{},"Key ",[487,3666,3637],{},[471,3668,3669,3672,3679],{},[474,3670,3671],{},"Unique identifier cho state này trong toàn app.",[474,3673,3674,3675,3678],{},"Mọi component gọi ",[487,3676,3677],{},"useState('todos')"," đều lấy cùng một reactive ref.",[474,3680,3681,3682,3685],{},"Nếu sai key khác (",[487,3683,3684],{},"'my-todos'","), sẽ tạo state khác — không share được.",[432,3687,3688,742],{},[436,3689,3690,3691],{},"Lazy init ",[487,3692,3693],{},"() => []",[471,3695,3696,3703,3710],{},[474,3697,3698,3699,3702],{},"Truyền function thay vì giá trị trực tiếp để ",[436,3700,3701],{},"không chạy"," khi state đã tồn tại.",[474,3704,3705,3706,3709],{},"Nuxt gọi init function ",[436,3707,3708],{},"chỉ 1 lần"," khi key chưa được đăng ký.",[474,3711,3712,3713,3715],{},"Nếu truyền ",[487,3714,1807],{}," trực tiếp, sẽ tạo array mới mỗi lần không cần thiết.",[432,3717,3718,742],{},[436,3719,3720],{},[487,3721,525],{},[471,3723,3724,3731,3734,3740],{},[474,3725,3726,3727,3730],{},"Là ",[436,3728,3729],{},"derived state"," — tự động tính lại khi dependency thay đổi.",[474,3732,3733],{},"Cache kết quả — chỉ re-evaluate khi dependency thực sự thay đổi.",[474,3735,3736,3737,497],{},"Read-only — không thể gán trực tiếp ",[487,3738,3739],{},"computed.value = ...",[474,3741,3742,3743,3746],{},"Giống như ",[487,3744,3745],{},"useMemo"," trong React nhưng tự động track dependency.",[432,3748,3749,742],{},[436,3750,3751,3753,3754],{},[487,3752,533],{}," với ",[487,3755,3756],{},"{ deep: true }",[471,3758,3759,3768,3771,3776],{},[474,3760,3761,3762,3764,3765,977],{},"Mặc định ",[487,3763,533],{}," chỉ theo dõi tham chiếu của ref (",[487,3766,3767],{},"todos.value === newRef",[474,3769,3770],{},"Với array, khi push\u002Fremove item, tham chiếu không đổi → watch không chạy.",[474,3772,3773,3775],{},[487,3774,3756],{},": theo dõi recursive — phát hiện thay đổi bên trong array\u002Fobject.",[474,3777,3778,3781],{},[436,3779,3780],{},"Trade-off",": deep watch tốn performance hơn cho array lớn. Với todo app (vài trăm item) — không đáng kể.",[432,3783,3784,742],{},[436,3785,3786],{},[487,3787,609],{},[471,3789,3790,3799,3804,3810],{},[474,3791,3792,3795,3796,497],{},[436,3793,3794],{},"SSR guard",": code trong block này ",[436,3797,3798],{},"chỉ chạy ở browser",[474,3800,3801,3803],{},[487,3802,537],{}," không tồn tại ở server (Node.js) — nếu không guard sẽ throw error.",[474,3805,3806,3809],{},[487,3807,3808],{},"import.meta.server"," — ngược lại, chỉ chạy ở server.",[474,3811,3812],{},"Đây là Vite built-in, không phải Nuxt-specific.",[626,3814],{},[427,3816,3818],{"id":3817},"_4-components","4. Components",[466,3820,3822],{"id":3821},"_41-todoformvue-form-thêm-todo-mới","4.1 TodoForm.vue — Form thêm todo mới",[432,3824,1288,3825,742],{},[487,3826,3827],{},"app\u002Fcomponents\u002FTodoForm.vue",[616,3829,3832],{"className":3830,"code":3831,"language":934,"meta":624,"style":624},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003Cscript setup lang=\"ts\">\n\u002F**\n * TodoForm — component form để thêm todo mới.\n *\n * Sử dụng:\n * - ref: local state cho input text.\n * - useTodos: lấy addTodo từ composable.\n * - v-model: two-way binding giữa input và ref.\n * - @submit.prevent: ngăn form reload page.\n *\u002F\n\nconst newTodo = ref('')\nconst { addTodo } = useTodos()\n\n\u002F**\n * Xử lý khi form submit.\n * - Trim để loại bỏ khoảng trắng thừa ở đầu\u002Fcuối.\n * - Nếu rỗng sau trim — không làm gì (tránh thêm todo rỗng).\n * - Gọi addTodo, sau đó reset input.\n *\u002F\nconst handleSubmit = () => {\n  const trimmed = newTodo.value.trim()\n  if (!trimmed) return\n\n  addTodo(trimmed)\n  newTodo.value = ''\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cform\n    class=\"todo-form\"\n    @submit.prevent=\"handleSubmit\"\n  >\n    \u003Cinput\n      v-model=\"newTodo\"\n      type=\"text\"\n      class=\"todo-input\"\n      placeholder=\"What needs to be done?\"\n      autocomplete=\"off\"\n    \u002F>\n    \u003Cbutton\n      type=\"submit\"\n      class=\"btn-add\"\n      :disabled=\"!newTodo.trim()\"\n    >\n      Add\n    \u003C\u002Fbutton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n.todo-form {\n  display: flex;\n  gap: 8px;\n  margin-bottom: 24px;\n}\n\n.todo-input {\n  flex: 1;\n  padding: 12px 16px;\n  font-size: 16px;\n  border: 2px solid var(--color-border);\n  border-radius: var(--radius);\n  background: var(--color-bg);\n  color: var(--color-text);\n  outline: none;\n  transition: border-color 0.2s;\n}\n\n.todo-input:focus {\n  border-color: var(--color-primary);\n}\n\n.todo-input::placeholder {\n  color: var(--color-text-muted);\n}\n\n.btn-add {\n  padding: 12px 24px;\n  font-size: 16px;\n  font-weight: 600;\n  border: none;\n  border-radius: var(--radius);\n  background: var(--color-primary);\n  color: white;\n  cursor: pointer;\n  transition: opacity 0.2s, transform 0.1s;\n}\n\n.btn-add:hover:not(:disabled) {\n  opacity: 0.9;\n}\n\n.btn-add:active:not(:disabled) {\n  transform: scale(0.97);\n}\n\n.btn-add:disabled {\n  opacity: 0.4;\n  cursor: not-allowed;\n}\n\u003C\u002Fstyle>\n",[487,3833,3834,3859,3863,3868,3872,3877,3882,3887,3892,3897,3901,3905,3924,3942,3946,3950,3955,3960,3965,3970,3974,3989,4010,4025,4029,4040,4054,4058,4067,4071,4080,4088,4102,4116,4121,4129,4143,4156,4170,4184,4198,4203,4210,4223,4236,4250,4255,4260,4270,4280,4288,4292,4304,4312,4326,4339,4351,4355,4359,4367,4379,4394,4405,4429,4446,4462,4478,4490,4505,4509,4513,4526,4542,4546,4550,4564,4579,4583,4587,4595,4607,4617,4629,4639,4653,4667,4678,4690,4711,4715,4719,4745,4757,4761,4765,4787,4804,4808,4812,4824,4835,4846,4850],{"__ignoreMap":624},[643,3835,3836,3838,3841,3844,3847,3849,3851,3854,3856],{"class":645,"line":646},[643,3837,1801],{"class":726},[643,3839,3840],{"class":1046},"script",[643,3842,3843],{"class":735}," setup",[643,3845,3846],{"class":735}," lang",[643,3848,1567],{"class":726},[643,3850,739],{"class":726},[643,3852,3853],{"class":653},"ts",[643,3855,739],{"class":726},[643,3857,3858],{"class":726},">\n",[643,3860,3861],{"class":645,"line":663},[643,3862,1312],{"class":1023},[643,3864,3865],{"class":645,"line":672},[643,3866,3867],{"class":1023}," * TodoForm — component form để thêm todo mới.\n",[643,3869,3870],{"class":645,"line":681},[643,3871,1693],{"class":1023},[643,3873,3874],{"class":645,"line":789},[643,3875,3876],{"class":1023}," * Sử dụng:\n",[643,3878,3879],{"class":645,"line":804},[643,3880,3881],{"class":1023}," * - ref: local state cho input text.\n",[643,3883,3884],{"class":645,"line":826},[643,3885,3886],{"class":1023}," * - useTodos: lấy addTodo từ composable.\n",[643,3888,3889],{"class":645,"line":847},[643,3890,3891],{"class":1023}," * - v-model: two-way binding giữa input và ref.\n",[643,3893,3894],{"class":645,"line":868},[643,3895,3896],{"class":1023}," * - @submit.prevent: ngăn form reload page.\n",[643,3898,3899],{"class":645,"line":888},[643,3900,1327],{"class":1023},[643,3902,3903],{"class":645,"line":894},[643,3904,1307],{"emptyLinePlaceholder":1306},[643,3906,3907,3909,3912,3914,3917,3919,3922],{"class":645,"line":908},[643,3908,1561],{"class":735},[643,3910,3911],{"class":1038}," newTodo ",[643,3913,1567],{"class":726},[643,3915,3916],{"class":666}," ref",[643,3918,1039],{"class":1038},[643,3920,3921],{"class":726},"''",[643,3923,1217],{"class":1038},[643,3925,3926,3928,3930,3933,3935,3937,3940],{"class":645,"line":929},[643,3927,1561],{"class":735},[643,3929,1070],{"class":726},[643,3931,3932],{"class":1038}," addTodo ",[643,3934,1214],{"class":726},[643,3936,1459],{"class":726},[643,3938,3939],{"class":666}," useTodos",[643,3941,1578],{"class":1038},[643,3943,3944],{"class":645,"line":948},[643,3945,1307],{"emptyLinePlaceholder":1306},[643,3947,3948],{"class":645,"line":954},[643,3949,1312],{"class":1023},[643,3951,3952],{"class":645,"line":1394},[643,3953,3954],{"class":1023}," * Xử lý khi form submit.\n",[643,3956,3957],{"class":645,"line":1399},[643,3958,3959],{"class":1023}," * - Trim để loại bỏ khoảng trắng thừa ở đầu\u002Fcuối.\n",[643,3961,3962],{"class":645,"line":1405},[643,3963,3964],{"class":1023}," * - Nếu rỗng sau trim — không làm gì (tránh thêm todo rỗng).\n",[643,3966,3967],{"class":645,"line":1416},[643,3968,3969],{"class":1023}," * - Gọi addTodo, sau đó reset input.\n",[643,3971,3972],{"class":645,"line":1421},[643,3973,1327],{"class":1023},[643,3975,3976,3978,3981,3983,3985,3987],{"class":645,"line":1426},[643,3977,1561],{"class":735},[643,3979,3980],{"class":1038}," handleSubmit ",[643,3982,1567],{"class":726},[643,3984,1722],{"class":726},[643,3986,1725],{"class":735},[643,3988,801],{"class":726},[643,3990,3991,3993,3995,3997,4000,4002,4004,4006,4008],{"class":645,"line":1431},[643,3992,1790],{"class":735},[643,3994,2773],{"class":1038},[643,3996,1459],{"class":726},[643,3998,3999],{"class":1038}," newTodo",[643,4001,497],{"class":726},[643,4003,2056],{"class":1038},[643,4005,497],{"class":726},[643,4007,2783],{"class":666},[643,4009,1578],{"class":1046},[643,4011,4012,4014,4016,4018,4020,4022],{"class":645,"line":1437},[643,4013,1934],{"class":1029},[643,4015,1937],{"class":1046},[643,4017,2796],{"class":726},[643,4019,2799],{"class":1038},[643,4021,1952],{"class":1046},[643,4023,4024],{"class":1029},"return\n",[643,4026,4027],{"class":645,"line":1443},[643,4028,1307],{"emptyLinePlaceholder":1306},[643,4030,4031,4034,4036,4038],{"class":645,"line":1448},[643,4032,4033],{"class":666},"  addTodo",[643,4035,1039],{"class":1046},[643,4037,2799],{"class":1038},[643,4039,1217],{"class":1046},[643,4041,4042,4045,4047,4049,4051],{"class":645,"line":1835},[643,4043,4044],{"class":1038},"  newTodo",[643,4046,497],{"class":726},[643,4048,2056],{"class":1038},[643,4050,1459],{"class":726},[643,4052,4053],{"class":726}," ''\n",[643,4055,4056],{"class":645,"line":1840},[643,4057,957],{"class":726},[643,4059,4060,4063,4065],{"class":645,"line":1846},[643,4061,4062],{"class":726},"\u003C\u002F",[643,4064,3840],{"class":1046},[643,4066,3858],{"class":726},[643,4068,4069],{"class":645,"line":1852},[643,4070,1307],{"emptyLinePlaceholder":1306},[643,4072,4073,4075,4078],{"class":645,"line":1857},[643,4074,1801],{"class":726},[643,4076,4077],{"class":1046},"template",[643,4079,3858],{"class":726},[643,4081,4082,4085],{"class":645,"line":1899},[643,4083,4084],{"class":726},"  \u003C",[643,4086,4087],{"class":1046},"form\n",[643,4089,4090,4093,4095,4097,4100],{"class":645,"line":1904},[643,4091,4092],{"class":735},"    class",[643,4094,1567],{"class":726},[643,4096,739],{"class":726},[643,4098,4099],{"class":653},"todo-form",[643,4101,885],{"class":726},[643,4103,4104,4107,4109,4111,4114],{"class":645,"line":1909},[643,4105,4106],{"class":735},"    @submit.prevent",[643,4108,1567],{"class":726},[643,4110,739],{"class":726},[643,4112,4113],{"class":653},"handleSubmit",[643,4115,885],{"class":726},[643,4117,4118],{"class":645,"line":1915},[643,4119,4120],{"class":726},"  >\n",[643,4122,4123,4126],{"class":645,"line":1920},[643,4124,4125],{"class":726},"    \u003C",[643,4127,4128],{"class":1046},"input\n",[643,4130,4131,4134,4136,4138,4141],{"class":645,"line":1925},[643,4132,4133],{"class":735},"      v-model",[643,4135,1567],{"class":726},[643,4137,739],{"class":726},[643,4139,4140],{"class":653},"newTodo",[643,4142,885],{"class":726},[643,4144,4145,4148,4150,4152,4154],{"class":645,"line":1931},[643,4146,4147],{"class":735},"      type",[643,4149,1567],{"class":726},[643,4151,739],{"class":726},[643,4153,621],{"class":653},[643,4155,885],{"class":726},[643,4157,4158,4161,4163,4165,4168],{"class":645,"line":1957},[643,4159,4160],{"class":735},"      class",[643,4162,1567],{"class":726},[643,4164,739],{"class":726},[643,4166,4167],{"class":653},"todo-input",[643,4169,885],{"class":726},[643,4171,4172,4175,4177,4179,4182],{"class":645,"line":1965},[643,4173,4174],{"class":735},"      placeholder",[643,4176,1567],{"class":726},[643,4178,739],{"class":726},[643,4180,4181],{"class":653},"What needs to be done?",[643,4183,885],{"class":726},[643,4185,4186,4189,4191,4193,4196],{"class":645,"line":1995},[643,4187,4188],{"class":735},"      autocomplete",[643,4190,1567],{"class":726},[643,4192,739],{"class":726},[643,4194,4195],{"class":653},"off",[643,4197,885],{"class":726},[643,4199,4200],{"class":645,"line":2010},[643,4201,4202],{"class":726},"    \u002F>\n",[643,4204,4205,4207],{"class":645,"line":2016},[643,4206,4125],{"class":726},[643,4208,4209],{"class":1046},"button\n",[643,4211,4212,4214,4216,4218,4221],{"class":645,"line":2048},[643,4213,4147],{"class":735},[643,4215,1567],{"class":726},[643,4217,739],{"class":726},[643,4219,4220],{"class":653},"submit",[643,4222,885],{"class":726},[643,4224,4225,4227,4229,4231,4234],{"class":645,"line":2080},[643,4226,4160],{"class":735},[643,4228,1567],{"class":726},[643,4230,739],{"class":726},[643,4232,4233],{"class":653},"btn-add",[643,4235,885],{"class":726},[643,4237,4238,4241,4243,4245,4248],{"class":645,"line":2090},[643,4239,4240],{"class":735},"      :disabled",[643,4242,1567],{"class":726},[643,4244,739],{"class":726},[643,4246,4247],{"class":653},"!newTodo.trim()",[643,4249,885],{"class":726},[643,4251,4252],{"class":645,"line":2115},[643,4253,4254],{"class":726},"    >\n",[643,4256,4257],{"class":645,"line":2124},[643,4258,4259],{"class":1038},"      Add\n",[643,4261,4262,4265,4268],{"class":645,"line":2130},[643,4263,4264],{"class":726},"    \u003C\u002F",[643,4266,4267],{"class":1046},"button",[643,4269,3858],{"class":726},[643,4271,4272,4275,4278],{"class":645,"line":2141},[643,4273,4274],{"class":726},"  \u003C\u002F",[643,4276,4277],{"class":1046},"form",[643,4279,3858],{"class":726},[643,4281,4282,4284,4286],{"class":645,"line":2147},[643,4283,4062],{"class":726},[643,4285,4077],{"class":1046},[643,4287,3858],{"class":726},[643,4289,4290],{"class":645,"line":2169},[643,4291,1307],{"emptyLinePlaceholder":1306},[643,4293,4294,4296,4299,4302],{"class":645,"line":2174},[643,4295,1801],{"class":726},[643,4297,4298],{"class":1046},"style",[643,4300,4301],{"class":735}," scoped",[643,4303,3858],{"class":726},[643,4305,4306,4308,4310],{"class":645,"line":2179},[643,4307,497],{"class":726},[643,4309,4099],{"class":649},[643,4311,801],{"class":726},[643,4313,4314,4318,4320,4323],{"class":645,"line":2184},[643,4315,4317],{"class":4316},"sqsOY","  display",[643,4319,742],{"class":726},[643,4321,4322],{"class":1038}," flex",[643,4324,4325],{"class":726},";\n",[643,4327,4328,4331,4333,4337],{"class":645,"line":2189},[643,4329,4330],{"class":4316},"  gap",[643,4332,742],{"class":726},[643,4334,4336],{"class":4335},"sbssI"," 8px",[643,4338,4325],{"class":726},[643,4340,4341,4344,4346,4349],{"class":645,"line":2195},[643,4342,4343],{"class":4316},"  margin-bottom",[643,4345,742],{"class":726},[643,4347,4348],{"class":4335}," 24px",[643,4350,4325],{"class":726},[643,4352,4353],{"class":645,"line":2200},[643,4354,957],{"class":726},[643,4356,4357],{"class":645,"line":2206},[643,4358,1307],{"emptyLinePlaceholder":1306},[643,4360,4361,4363,4365],{"class":645,"line":2212},[643,4362,497],{"class":726},[643,4364,4167],{"class":649},[643,4366,801],{"class":726},[643,4368,4369,4372,4374,4377],{"class":645,"line":2218},[643,4370,4371],{"class":4316},"  flex",[643,4373,742],{"class":726},[643,4375,4376],{"class":4335}," 1",[643,4378,4325],{"class":726},[643,4380,4381,4384,4386,4389,4392],{"class":645,"line":2223},[643,4382,4383],{"class":4316},"  padding",[643,4385,742],{"class":726},[643,4387,4388],{"class":4335}," 12px",[643,4390,4391],{"class":4335}," 16px",[643,4393,4325],{"class":726},[643,4395,4396,4399,4401,4403],{"class":645,"line":2244},[643,4397,4398],{"class":4316},"  font-size",[643,4400,742],{"class":726},[643,4402,4391],{"class":4335},[643,4404,4325],{"class":726},[643,4406,4407,4410,4412,4415,4418,4421,4423,4426],{"class":645,"line":2268},[643,4408,4409],{"class":4316},"  border",[643,4411,742],{"class":726},[643,4413,4414],{"class":4335}," 2px",[643,4416,4417],{"class":1038}," solid ",[643,4419,4420],{"class":666},"var",[643,4422,1039],{"class":726},[643,4424,4425],{"class":1038},"--color-border",[643,4427,4428],{"class":726},");\n",[643,4430,4431,4434,4436,4439,4441,4444],{"class":645,"line":2302},[643,4432,4433],{"class":4316},"  border-radius",[643,4435,742],{"class":726},[643,4437,4438],{"class":666}," var",[643,4440,1039],{"class":726},[643,4442,4443],{"class":1038},"--radius",[643,4445,4428],{"class":726},[643,4447,4448,4451,4453,4455,4457,4460],{"class":645,"line":2321},[643,4449,4450],{"class":4316},"  background",[643,4452,742],{"class":726},[643,4454,4438],{"class":666},[643,4456,1039],{"class":726},[643,4458,4459],{"class":1038},"--color-bg",[643,4461,4428],{"class":726},[643,4463,4464,4467,4469,4471,4473,4476],{"class":645,"line":2326},[643,4465,4466],{"class":4316},"  color",[643,4468,742],{"class":726},[643,4470,4438],{"class":666},[643,4472,1039],{"class":726},[643,4474,4475],{"class":1038},"--color-text",[643,4477,4428],{"class":726},[643,4479,4480,4483,4485,4488],{"class":645,"line":2331},[643,4481,4482],{"class":4316},"  outline",[643,4484,742],{"class":726},[643,4486,4487],{"class":1038}," none",[643,4489,4325],{"class":726},[643,4491,4492,4495,4497,4500,4503],{"class":645,"line":2336},[643,4493,4494],{"class":4316},"  transition",[643,4496,742],{"class":726},[643,4498,4499],{"class":1038}," border-color ",[643,4501,4502],{"class":4335},"0.2s",[643,4504,4325],{"class":726},[643,4506,4507],{"class":645,"line":2342},[643,4508,957],{"class":726},[643,4510,4511],{"class":645,"line":2347},[643,4512,1307],{"emptyLinePlaceholder":1306},[643,4514,4515,4517,4519,4521,4524],{"class":645,"line":2352},[643,4516,497],{"class":726},[643,4518,4167],{"class":649},[643,4520,742],{"class":726},[643,4522,4523],{"class":735},"focus",[643,4525,801],{"class":726},[643,4527,4528,4531,4533,4535,4537,4540],{"class":645,"line":2357},[643,4529,4530],{"class":4316},"  border-color",[643,4532,742],{"class":726},[643,4534,4438],{"class":666},[643,4536,1039],{"class":726},[643,4538,4539],{"class":1038},"--color-primary",[643,4541,4428],{"class":726},[643,4543,4544],{"class":645,"line":2363},[643,4545,957],{"class":726},[643,4547,4548],{"class":645,"line":2369},[643,4549,1307],{"emptyLinePlaceholder":1306},[643,4551,4552,4554,4556,4559,4562],{"class":645,"line":2374},[643,4553,497],{"class":726},[643,4555,4167],{"class":649},[643,4557,4558],{"class":726},"::",[643,4560,4561],{"class":735},"placeholder",[643,4563,801],{"class":726},[643,4565,4566,4568,4570,4572,4574,4577],{"class":645,"line":2423},[643,4567,4466],{"class":4316},[643,4569,742],{"class":726},[643,4571,4438],{"class":666},[643,4573,1039],{"class":726},[643,4575,4576],{"class":1038},"--color-text-muted",[643,4578,4428],{"class":726},[643,4580,4581],{"class":645,"line":2466},[643,4582,957],{"class":726},[643,4584,4585],{"class":645,"line":2471},[643,4586,1307],{"emptyLinePlaceholder":1306},[643,4588,4589,4591,4593],{"class":645,"line":2502},[643,4590,497],{"class":726},[643,4592,4233],{"class":649},[643,4594,801],{"class":726},[643,4596,4597,4599,4601,4603,4605],{"class":645,"line":2532},[643,4598,4383],{"class":4316},[643,4600,742],{"class":726},[643,4602,4388],{"class":4335},[643,4604,4348],{"class":4335},[643,4606,4325],{"class":726},[643,4608,4609,4611,4613,4615],{"class":645,"line":2537},[643,4610,4398],{"class":4316},[643,4612,742],{"class":726},[643,4614,4391],{"class":4335},[643,4616,4325],{"class":726},[643,4618,4619,4622,4624,4627],{"class":645,"line":2542},[643,4620,4621],{"class":4316},"  font-weight",[643,4623,742],{"class":726},[643,4625,4626],{"class":4335}," 600",[643,4628,4325],{"class":726},[643,4630,4631,4633,4635,4637],{"class":645,"line":2548},[643,4632,4409],{"class":4316},[643,4634,742],{"class":726},[643,4636,4487],{"class":1038},[643,4638,4325],{"class":726},[643,4640,4641,4643,4645,4647,4649,4651],{"class":645,"line":2554},[643,4642,4433],{"class":4316},[643,4644,742],{"class":726},[643,4646,4438],{"class":666},[643,4648,1039],{"class":726},[643,4650,4443],{"class":1038},[643,4652,4428],{"class":726},[643,4654,4655,4657,4659,4661,4663,4665],{"class":645,"line":2559},[643,4656,4450],{"class":4316},[643,4658,742],{"class":726},[643,4660,4438],{"class":666},[643,4662,1039],{"class":726},[643,4664,4539],{"class":1038},[643,4666,4428],{"class":726},[643,4668,4669,4671,4673,4676],{"class":645,"line":2579},[643,4670,4466],{"class":4316},[643,4672,742],{"class":726},[643,4674,4675],{"class":1038}," white",[643,4677,4325],{"class":726},[643,4679,4680,4683,4685,4688],{"class":645,"line":2597},[643,4681,4682],{"class":4316},"  cursor",[643,4684,742],{"class":726},[643,4686,4687],{"class":1038}," pointer",[643,4689,4325],{"class":726},[643,4691,4692,4694,4696,4699,4701,4703,4706,4709],{"class":645,"line":2612},[643,4693,4494],{"class":4316},[643,4695,742],{"class":726},[643,4697,4698],{"class":1038}," opacity ",[643,4700,4502],{"class":4335},[643,4702,1180],{"class":726},[643,4704,4705],{"class":1038}," transform ",[643,4707,4708],{"class":4335},"0.1s",[643,4710,4325],{"class":726},[643,4712,4713],{"class":645,"line":2625},[643,4714,957],{"class":726},[643,4716,4717],{"class":645,"line":2638},[643,4718,1307],{"emptyLinePlaceholder":1306},[643,4720,4721,4723,4725,4727,4730,4732,4735,4738,4741,4743],{"class":645,"line":2649},[643,4722,497],{"class":726},[643,4724,4233],{"class":649},[643,4726,742],{"class":726},[643,4728,4729],{"class":735},"hover",[643,4731,742],{"class":726},[643,4733,4734],{"class":735},"not",[643,4736,4737],{"class":726},"(:",[643,4739,4740],{"class":735},"disabled",[643,4742,2261],{"class":726},[643,4744,801],{"class":726},[643,4746,4747,4750,4752,4755],{"class":645,"line":2657},[643,4748,4749],{"class":4316},"  opacity",[643,4751,742],{"class":726},[643,4753,4754],{"class":4335}," 0.9",[643,4756,4325],{"class":726},[643,4758,4759],{"class":645,"line":2668},[643,4760,957],{"class":726},[643,4762,4763],{"class":645,"line":2673},[643,4764,1307],{"emptyLinePlaceholder":1306},[643,4766,4767,4769,4771,4773,4775,4777,4779,4781,4783,4785],{"class":645,"line":2681},[643,4768,497],{"class":726},[643,4770,4233],{"class":649},[643,4772,742],{"class":726},[643,4774,1474],{"class":735},[643,4776,742],{"class":726},[643,4778,4734],{"class":735},[643,4780,4737],{"class":726},[643,4782,4740],{"class":735},[643,4784,2261],{"class":726},[643,4786,801],{"class":726},[643,4788,4789,4792,4794,4797,4799,4802],{"class":645,"line":2686},[643,4790,4791],{"class":4316},"  transform",[643,4793,742],{"class":726},[643,4795,4796],{"class":666}," scale",[643,4798,1039],{"class":726},[643,4800,4801],{"class":4335},"0.97",[643,4803,4428],{"class":726},[643,4805,4806],{"class":645,"line":2691},[643,4807,957],{"class":726},[643,4809,4810],{"class":645,"line":2697},[643,4811,1307],{"emptyLinePlaceholder":1306},[643,4813,4814,4816,4818,4820,4822],{"class":645,"line":2702},[643,4815,497],{"class":726},[643,4817,4233],{"class":649},[643,4819,742],{"class":726},[643,4821,4740],{"class":735},[643,4823,801],{"class":726},[643,4825,4826,4828,4830,4833],{"class":645,"line":2707},[643,4827,4749],{"class":4316},[643,4829,742],{"class":726},[643,4831,4832],{"class":4335}," 0.4",[643,4834,4325],{"class":726},[643,4836,4837,4839,4841,4844],{"class":645,"line":2712},[643,4838,4682],{"class":4316},[643,4840,742],{"class":726},[643,4842,4843],{"class":1038}," not-allowed",[643,4845,4325],{"class":726},[643,4847,4848],{"class":645,"line":2718},[643,4849,957],{"class":726},[643,4851,4852,4854,4856],{"class":645,"line":2724},[643,4853,4062],{"class":726},[643,4855,4298],{"class":1046},[643,4857,3858],{"class":726},[466,4859,1490],{"id":4860},"giải-thích-chi-tiết-1",[432,4862,4863],{},[436,4864,4865,742],{},[487,4866,4867],{},"v-model=\"newTodo\"",[471,4869,4870,4883,4893],{},[474,4871,4872,4875,4876,4879,4880,4882],{},[436,4873,4874],{},"Two-way binding",": Khi user gõ vào input → ",[487,4877,4878],{},"newTodo.value"," tự động cập nhật. Khi ",[487,4881,4878],{}," thay đổi (reset sau submit) → input tự động clear.",[474,4884,4885,4886,4889,4890,497],{},"Là syntactic sugar cho ",[487,4887,4888],{},":value=\"newTodo\""," + ",[487,4891,4892],{},"@input=\"newTodo = $event.target.value\"",[474,4894,4895,4896,490,4899,490,4902,977],{},"Chỉ dùng với form element (",[487,4897,4898],{},"\u003Cinput>",[487,4900,4901],{},"\u003Ctextarea>",[487,4903,4904],{},"\u003Cselect>",[432,4906,4907],{},[436,4908,4909,742],{},[487,4910,4911],{},"@submit.prevent",[471,4913,4914,4923,4929],{},[474,4915,4916,4919,4920,497],{},[487,4917,4918],{},".prevent"," là event modifier — tương đương ",[487,4921,4922],{},"event.preventDefault()",[474,4924,4925,4926,4928],{},"Nếu không có ",[487,4927,4918],{},", form sẽ reload page (hành vi mặc định của HTML form submit).",[474,4930,4931,4932,490,4935,490,4938,490,4941,490,4944,497],{},"Các modifier Vue khác: ",[487,4933,4934],{},".stop",[487,4936,4937],{},".once",[487,4939,4940],{},".capture",[487,4942,4943],{},".self",[487,4945,4946],{},".passive",[432,4948,4949],{},[436,4950,4951,742],{},[487,4952,4953],{},":disabled=\"!newTodo.trim()\"",[471,4955,4956,4967,4970],{},[474,4957,4958,485,4961,4963,4964,497],{},[436,4959,4960],{},"Dynamic attribute binding",[487,4962,742],{}," là shorthand cho ",[487,4965,4966],{},"v-bind:",[474,4968,4969],{},"Nút Add bị disable khi input rỗng (sau trim).",[474,4971,4972,4973,4976],{},"Tại sao dùng ",[487,4974,4975],{},".trim()"," trong template? Check real-time — ngay khi user gõ space rồi xóa, nút enable\u002Fdisable đúng.",[432,4978,4979],{},[436,4980,4981,742],{},[487,4982,4983],{},"\u003Cstyle scoped>",[471,4985,4986,4992,4999,5002],{},[474,4987,4988,4991],{},[436,4989,4990],{},"Scoped CSS",": style trong component này chỉ apply cho template của component này.",[474,4993,4994,4995,4998],{},"Nuxt\u002FVue tự động add ",[487,4996,4997],{},"data-v-xxxxxx"," attribute vào element và CSS selector để isolate.",[474,5000,5001],{},"Không lo style leak sang component khác.",[474,5003,5004,5005,5008,5009,497],{},"MUỐN override: dùng ",[487,5006,5007],{},":deep(.selector)"," hoặc ",[487,5010,5011],{},":global(.selector)",[432,5013,5014],{},[436,5015,5016,742],{},[487,5017,5018],{},"autocomplete=\"off\"",[471,5020,5021],{},[474,5022,5023],{},"Tắt autocomplete của browser cho input này — tránh dropdown suggestion không liên quan chèn vào UI.",[626,5025],{},[466,5027,5029],{"id":5028},"_42-todoitemvue-một-dòng-todo","4.2 TodoItem.vue — Một dòng todo",[432,5031,1288,5032,742],{},[487,5033,5034],{},"app\u002Fcomponents\u002FTodoItem.vue",[616,5036,5038],{"className":3830,"code":5037,"language":934,"meta":624,"style":624},"\u003Cscript setup lang=\"ts\">\nimport type { Todo } from '~\u002Ftypes\u002Ftodo'\n\n\u002F**\n * TodoItem — component cho một dòng todo.\n *\n * Giao tiếp với parent qua:\n * - defineProps: nhận dữ liệu todo từ parent.\n * - defineEmits: emit event toggle, remove, edit lên parent.\n *\n * Có 2 chế độ:\n * - View mode: hiển thị title + checkbox + nút xóa.\n * - Edit mode: input text để chỉnh sửa title.\n *\u002F\n\n\u002F** Props — todo object từ parent (TodoList) *\u002F\nconst props = defineProps\u003C{\n  todo: Todo\n}>()\n\n\u002F** Emits — typed event để parent lắng nghe *\u002F\nconst emit = defineEmits\u003C{\n  toggle: [id: string]\n  remove: [id: string]\n  edit: [id: string, title: string]\n}>()\n\n\u002F** Local state cho edit mode *\u002F\nconst isEditing = ref(false)\nconst editText = ref('')\n\n\u002F**\n * Bắt đầu edit mode.\n * Lưu title hiện tại vào editText để user có thể chỉnh sửa.\n *\u002F\nconst startEdit = () => {\n  isEditing.value = true\n  editText.value = props.todo.title\n}\n\n\u002F**\n * Lưu kết quả chỉnh sửa.\n * Nếu title rỗng sau trim → không làm gì.\n * Gọi emit edit để cập nhật ở composable.\n *\u002F\nconst saveEdit = () => {\n  const trimmed = editText.value.trim()\n  if (!trimmed) {\n    \u002F\u002F Nếu rỗng sau trim — xóa todo\n    emit('remove', props.todo.id)\n    isEditing.value = false\n    return\n  }\n\n  \u002F\u002F Chỉ emit nếu title thực sự thay đổi\n  if (trimmed !== props.todo.title) {\n    emit('edit', props.todo.id, trimmed)\n  }\n  isEditing.value = false\n}\n\n\u002F** Hủy edit mode — quay lại view mode, không lưu thay đổi *\u002F\nconst cancelEdit = () => {\n  isEditing.value = false\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv\n    class=\"todo-item\"\n    :class=\"{\n      completed: todo.completed,\n      editing: isEditing\n    }\"\n  >\n    \u003C!-- Checkbox: toggle completed -->\n    \u003Cinput\n      type=\"checkbox\"\n      class=\"todo-checkbox\"\n      :checked=\"todo.completed\"\n      @change=\"emit('toggle', todo.id)\"\n    \u002F>\n\n    \u003C!-- VIEW MODE: hiển thị title -->\n    \u003Clabel\n      v-if=\"!isEditing\"\n      class=\"todo-title\"\n      @dblclick=\"startEdit\"\n    >\n      {{ todo.title }}\n    \u003C\u002Flabel>\n\n    \u003C!-- EDIT MODE: input chỉnh sửa -->\n    \u003Cinput\n      v-else\n      ref=\"editInputRef\"\n      v-model=\"editText\"\n      type=\"text\"\n      class=\"edit-input\"\n      @keyup.enter=\"saveEdit\"\n      @keyup.escape=\"cancelEdit\"\n      @blur=\"saveEdit\"\n    \u002F>\n\n    \u003C!-- Nút xóa -->\n    \u003Cbutton\n      class=\"btn-delete\"\n      @click=\"emit('remove', todo.id)\"\n      aria-label=\"Delete todo\"\n    >\n      ×\n    \u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n.todo-item {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  padding: 12px 16px;\n  border-bottom: 1px solid var(--color-border);\n  transition: background-color 0.2s;\n}\n\n.todo-item:hover {\n  background: var(--color-bg-hover);\n}\n\n.todo-item.completed .todo-title {\n  text-decoration: line-through;\n  color: var(--color-text-muted);\n}\n\n.todo-item.editing {\n  background: var(--color-bg-active);\n}\n\n.todo-checkbox {\n  width: 20px;\n  height: 20px;\n  accent-color: var(--color-primary);\n  cursor: pointer;\n  flex-shrink: 0;\n}\n\n.todo-title {\n  flex: 1;\n  font-size: 16px;\n  cursor: pointer;\n  user-select: none;\n  word-break: break-word;\n}\n\n.edit-input {\n  flex: 1;\n  padding: 8px 12px;\n  font-size: 16px;\n  border: 2px solid var(--color-primary);\n  border-radius: var(--radius);\n  background: var(--color-bg);\n  color: var(--color-text);\n  outline: none;\n}\n\n.btn-delete {\n  width: 32px;\n  height: 32px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 20px;\n  font-weight: 300;\n  border: none;\n  border-radius: 50%;\n  background: transparent;\n  color: var(--color-text-muted);\n  cursor: pointer;\n  transition: all 0.2s;\n  flex-shrink: 0;\n  opacity: 0;\n}\n\n.todo-item:hover .btn-delete {\n  opacity: 1;\n}\n\n.btn-delete:hover {\n  background: var(--color-danger);\n  color: white;\n}\n\u003C\u002Fstyle>\n",[487,5039,5040,5060,5080,5084,5088,5093,5097,5102,5107,5112,5116,5121,5126,5131,5135,5139,5144,5159,5169,5176,5180,5185,5199,5217,5234,5259,5265,5269,5274,5292,5309,5313,5317,5322,5327,5331,5346,5360,5383,5387,5391,5395,5400,5405,5410,5414,5429,5450,5464,5469,5497,5511,5516,5520,5524,5529,5553,5584,5588,5600,5604,5608,5613,5628,5640,5644,5652,5656,5664,5671,5684,5695,5700,5705,5711,5715,5720,5726,5739,5752,5766,5780,5784,5788,5793,5800,5814,5827,5841,5845,5850,5859,5863,5868,5874,5879,5893,5906,5918,5931,5945,5959,5972,5976,5980,5985,5991,6004,6018,6032,6036,6041,6049,6058,6066,6070,6080,6088,6098,6110,6120,6132,6152,6165,6169,6173,6185,6200,6204,6208,6225,6237,6251,6255,6259,6272,6287,6291,6295,6303,6315,6326,6341,6351,6363,6367,6371,6379,6389,6399,6409,6420,6432,6436,6440,6448,6458,6470,6480,6498,6512,6526,6540,6550,6554,6558,6566,6577,6587,6597,6607,6618,6628,6639,6649,6660,6671,6685,6695,6708,6718,6728,6732,6736,6752,6762,6766,6770,6782,6798,6809,6814],{"__ignoreMap":624},[643,5041,5042,5044,5046,5048,5050,5052,5054,5056,5058],{"class":645,"line":646},[643,5043,1801],{"class":726},[643,5045,3840],{"class":1046},[643,5047,3843],{"class":735},[643,5049,3846],{"class":735},[643,5051,1567],{"class":726},[643,5053,739],{"class":726},[643,5055,3853],{"class":653},[643,5057,739],{"class":726},[643,5059,3858],{"class":726},[643,5061,5062,5064,5066,5068,5070,5072,5074,5076,5078],{"class":645,"line":663},[643,5063,968],{"class":1029},[643,5065,1453],{"class":1029},[643,5067,1070],{"class":726},[643,5069,1337],{"class":1038},[643,5071,1665],{"class":726},[643,5073,1668],{"class":1029},[643,5075,1052],{"class":726},[643,5077,1673],{"class":653},[643,5079,1486],{"class":726},[643,5081,5082],{"class":645,"line":672},[643,5083,1307],{"emptyLinePlaceholder":1306},[643,5085,5086],{"class":645,"line":681},[643,5087,1312],{"class":1023},[643,5089,5090],{"class":645,"line":789},[643,5091,5092],{"class":1023}," * TodoItem — component cho một dòng todo.\n",[643,5094,5095],{"class":645,"line":804},[643,5096,1693],{"class":1023},[643,5098,5099],{"class":645,"line":826},[643,5100,5101],{"class":1023}," * Giao tiếp với parent qua:\n",[643,5103,5104],{"class":645,"line":847},[643,5105,5106],{"class":1023}," * - defineProps: nhận dữ liệu todo từ parent.\n",[643,5108,5109],{"class":645,"line":868},[643,5110,5111],{"class":1023}," * - defineEmits: emit event toggle, remove, edit lên parent.\n",[643,5113,5114],{"class":645,"line":888},[643,5115,1693],{"class":1023},[643,5117,5118],{"class":645,"line":894},[643,5119,5120],{"class":1023}," * Có 2 chế độ:\n",[643,5122,5123],{"class":645,"line":908},[643,5124,5125],{"class":1023}," * - View mode: hiển thị title + checkbox + nút xóa.\n",[643,5127,5128],{"class":645,"line":929},[643,5129,5130],{"class":1023}," * - Edit mode: input text để chỉnh sửa title.\n",[643,5132,5133],{"class":645,"line":948},[643,5134,1327],{"class":1023},[643,5136,5137],{"class":645,"line":954},[643,5138,1307],{"emptyLinePlaceholder":1306},[643,5140,5141],{"class":645,"line":1394},[643,5142,5143],{"class":1023},"\u002F** Props — todo object từ parent (TodoList) *\u002F\n",[643,5145,5146,5148,5151,5153,5156],{"class":645,"line":1399},[643,5147,1561],{"class":735},[643,5149,5150],{"class":1038}," props ",[643,5152,1567],{"class":726},[643,5154,5155],{"class":666}," defineProps",[643,5157,5158],{"class":726},"\u003C{\n",[643,5160,5161,5164,5166],{"class":645,"line":1405},[643,5162,5163],{"class":1046},"  todo",[643,5165,742],{"class":726},[643,5167,5168],{"class":649}," Todo\n",[643,5170,5171,5174],{"class":645,"line":1416},[643,5172,5173],{"class":726},"}>",[643,5175,1578],{"class":1038},[643,5177,5178],{"class":645,"line":1421},[643,5179,1307],{"emptyLinePlaceholder":1306},[643,5181,5182],{"class":645,"line":1426},[643,5183,5184],{"class":1023},"\u002F** Emits — typed event để parent lắng nghe *\u002F\n",[643,5186,5187,5189,5192,5194,5197],{"class":645,"line":1431},[643,5188,1561],{"class":735},[643,5190,5191],{"class":1038}," emit ",[643,5193,1567],{"class":726},[643,5195,5196],{"class":666}," defineEmits",[643,5198,5158],{"class":726},[643,5200,5201,5204,5206,5208,5210,5212,5214],{"class":645,"line":1437},[643,5202,5203],{"class":1046},"  toggle",[643,5205,742],{"class":726},[643,5207,1104],{"class":1038},[643,5209,2957],{"class":649},[643,5211,742],{"class":726},[643,5213,2758],{"class":649},[643,5215,5216],{"class":1038},"]\n",[643,5218,5219,5222,5224,5226,5228,5230,5232],{"class":645,"line":1443},[643,5220,5221],{"class":1046},"  remove",[643,5223,742],{"class":726},[643,5225,1104],{"class":1038},[643,5227,2957],{"class":649},[643,5229,742],{"class":726},[643,5231,2758],{"class":649},[643,5233,5216],{"class":1038},[643,5235,5236,5239,5241,5243,5245,5247,5249,5251,5253,5255,5257],{"class":645,"line":1448},[643,5237,5238],{"class":1046},"  edit",[643,5240,742],{"class":726},[643,5242,1104],{"class":1038},[643,5244,2957],{"class":649},[643,5246,742],{"class":726},[643,5248,2758],{"class":649},[643,5250,1180],{"class":726},[643,5252,2778],{"class":649},[643,5254,742],{"class":726},[643,5256,2758],{"class":649},[643,5258,5216],{"class":1038},[643,5260,5261,5263],{"class":645,"line":1835},[643,5262,5173],{"class":726},[643,5264,1578],{"class":1038},[643,5266,5267],{"class":645,"line":1840},[643,5268,1307],{"emptyLinePlaceholder":1306},[643,5270,5271],{"class":645,"line":1846},[643,5272,5273],{"class":1023},"\u002F** Local state cho edit mode *\u002F\n",[643,5275,5276,5278,5281,5283,5285,5287,5290],{"class":645,"line":1852},[643,5277,1561],{"class":735},[643,5279,5280],{"class":1038}," isEditing ",[643,5282,1567],{"class":726},[643,5284,3916],{"class":666},[643,5286,1039],{"class":1038},[643,5288,5289],{"class":1078},"false",[643,5291,1217],{"class":1038},[643,5293,5294,5296,5299,5301,5303,5305,5307],{"class":645,"line":1857},[643,5295,1561],{"class":735},[643,5297,5298],{"class":1038}," editText ",[643,5300,1567],{"class":726},[643,5302,3916],{"class":666},[643,5304,1039],{"class":1038},[643,5306,3921],{"class":726},[643,5308,1217],{"class":1038},[643,5310,5311],{"class":645,"line":1899},[643,5312,1307],{"emptyLinePlaceholder":1306},[643,5314,5315],{"class":645,"line":1904},[643,5316,1312],{"class":1023},[643,5318,5319],{"class":645,"line":1909},[643,5320,5321],{"class":1023}," * Bắt đầu edit mode.\n",[643,5323,5324],{"class":645,"line":1915},[643,5325,5326],{"class":1023}," * Lưu title hiện tại vào editText để user có thể chỉnh sửa.\n",[643,5328,5329],{"class":645,"line":1920},[643,5330,1327],{"class":1023},[643,5332,5333,5335,5338,5340,5342,5344],{"class":645,"line":1925},[643,5334,1561],{"class":735},[643,5336,5337],{"class":1038}," startEdit ",[643,5339,1567],{"class":726},[643,5341,1722],{"class":726},[643,5343,1725],{"class":735},[643,5345,801],{"class":726},[643,5347,5348,5351,5353,5355,5357],{"class":645,"line":1931},[643,5349,5350],{"class":1038},"  isEditing",[643,5352,497],{"class":726},[643,5354,2056],{"class":1038},[643,5356,1459],{"class":726},[643,5358,5359],{"class":1078}," true\n",[643,5361,5362,5365,5367,5369,5371,5374,5376,5378,5380],{"class":645,"line":1957},[643,5363,5364],{"class":1038},"  editText",[643,5366,497],{"class":726},[643,5368,2056],{"class":1038},[643,5370,1459],{"class":726},[643,5372,5373],{"class":1038}," props",[643,5375,497],{"class":726},[643,5377,2071],{"class":1038},[643,5379,497],{"class":726},[643,5381,5382],{"class":1038},"title\n",[643,5384,5385],{"class":645,"line":1965},[643,5386,957],{"class":726},[643,5388,5389],{"class":645,"line":1995},[643,5390,1307],{"emptyLinePlaceholder":1306},[643,5392,5393],{"class":645,"line":2010},[643,5394,1312],{"class":1023},[643,5396,5397],{"class":645,"line":2016},[643,5398,5399],{"class":1023}," * Lưu kết quả chỉnh sửa.\n",[643,5401,5402],{"class":645,"line":2048},[643,5403,5404],{"class":1023}," * Nếu title rỗng sau trim → không làm gì.\n",[643,5406,5407],{"class":645,"line":2080},[643,5408,5409],{"class":1023}," * Gọi emit edit để cập nhật ở composable.\n",[643,5411,5412],{"class":645,"line":2090},[643,5413,1327],{"class":1023},[643,5415,5416,5418,5421,5423,5425,5427],{"class":645,"line":2115},[643,5417,1561],{"class":735},[643,5419,5420],{"class":1038}," saveEdit ",[643,5422,1567],{"class":726},[643,5424,1722],{"class":726},[643,5426,1725],{"class":735},[643,5428,801],{"class":726},[643,5430,5431,5433,5435,5437,5440,5442,5444,5446,5448],{"class":645,"line":2124},[643,5432,1790],{"class":735},[643,5434,2773],{"class":1038},[643,5436,1459],{"class":726},[643,5438,5439],{"class":1038}," editText",[643,5441,497],{"class":726},[643,5443,2056],{"class":1038},[643,5445,497],{"class":726},[643,5447,2783],{"class":666},[643,5449,1578],{"class":1046},[643,5451,5452,5454,5456,5458,5460,5462],{"class":645,"line":2130},[643,5453,1934],{"class":1029},[643,5455,1937],{"class":1046},[643,5457,2796],{"class":726},[643,5459,2799],{"class":1038},[643,5461,1952],{"class":1046},[643,5463,727],{"class":726},[643,5465,5466],{"class":645,"line":2141},[643,5467,5468],{"class":1023},"    \u002F\u002F Nếu rỗng sau trim — xóa todo\n",[643,5470,5471,5474,5476,5478,5481,5483,5485,5487,5489,5491,5493,5495],{"class":645,"line":2147},[643,5472,5473],{"class":666},"    emit",[643,5475,1039],{"class":1046},[643,5477,1058],{"class":726},[643,5479,5480],{"class":653},"remove",[643,5482,1058],{"class":726},[643,5484,1180],{"class":726},[643,5486,5373],{"class":1038},[643,5488,497],{"class":726},[643,5490,2071],{"class":1038},[643,5492,497],{"class":726},[643,5494,2957],{"class":1038},[643,5496,1217],{"class":1046},[643,5498,5499,5502,5504,5506,5508],{"class":645,"line":2169},[643,5500,5501],{"class":1038},"    isEditing",[643,5503,497],{"class":726},[643,5505,2056],{"class":1038},[643,5507,1459],{"class":726},[643,5509,5510],{"class":1078}," false\n",[643,5512,5513],{"class":645,"line":2174},[643,5514,5515],{"class":1029},"    return\n",[643,5517,5518],{"class":645,"line":2179},[643,5519,951],{"class":726},[643,5521,5522],{"class":645,"line":2184},[643,5523,1307],{"emptyLinePlaceholder":1306},[643,5525,5526],{"class":645,"line":2189},[643,5527,5528],{"class":1023},"  \u002F\u002F Chỉ emit nếu title thực sự thay đổi\n",[643,5530,5531,5533,5535,5537,5539,5541,5543,5545,5547,5549,5551],{"class":645,"line":2195},[643,5532,1934],{"class":1029},[643,5534,1937],{"class":1046},[643,5536,2799],{"class":1038},[643,5538,3144],{"class":726},[643,5540,5373],{"class":1038},[643,5542,497],{"class":726},[643,5544,2071],{"class":1038},[643,5546,497],{"class":726},[643,5548,2753],{"class":1038},[643,5550,1952],{"class":1046},[643,5552,727],{"class":726},[643,5554,5555,5557,5559,5561,5564,5566,5568,5570,5572,5574,5576,5578,5580,5582],{"class":645,"line":2200},[643,5556,5473],{"class":666},[643,5558,1039],{"class":1046},[643,5560,1058],{"class":726},[643,5562,5563],{"class":653},"edit",[643,5565,1058],{"class":726},[643,5567,1180],{"class":726},[643,5569,5373],{"class":1038},[643,5571,497],{"class":726},[643,5573,2071],{"class":1038},[643,5575,497],{"class":726},[643,5577,2957],{"class":1038},[643,5579,1180],{"class":726},[643,5581,2773],{"class":1038},[643,5583,1217],{"class":1046},[643,5585,5586],{"class":645,"line":2206},[643,5587,951],{"class":726},[643,5589,5590,5592,5594,5596,5598],{"class":645,"line":2212},[643,5591,5350],{"class":1038},[643,5593,497],{"class":726},[643,5595,2056],{"class":1038},[643,5597,1459],{"class":726},[643,5599,5510],{"class":1078},[643,5601,5602],{"class":645,"line":2218},[643,5603,957],{"class":726},[643,5605,5606],{"class":645,"line":2223},[643,5607,1307],{"emptyLinePlaceholder":1306},[643,5609,5610],{"class":645,"line":2244},[643,5611,5612],{"class":1023},"\u002F** Hủy edit mode — quay lại view mode, không lưu thay đổi *\u002F\n",[643,5614,5615,5617,5620,5622,5624,5626],{"class":645,"line":2268},[643,5616,1561],{"class":735},[643,5618,5619],{"class":1038}," cancelEdit ",[643,5621,1567],{"class":726},[643,5623,1722],{"class":726},[643,5625,1725],{"class":735},[643,5627,801],{"class":726},[643,5629,5630,5632,5634,5636,5638],{"class":645,"line":2302},[643,5631,5350],{"class":1038},[643,5633,497],{"class":726},[643,5635,2056],{"class":1038},[643,5637,1459],{"class":726},[643,5639,5510],{"class":1078},[643,5641,5642],{"class":645,"line":2321},[643,5643,957],{"class":726},[643,5645,5646,5648,5650],{"class":645,"line":2326},[643,5647,4062],{"class":726},[643,5649,3840],{"class":1046},[643,5651,3858],{"class":726},[643,5653,5654],{"class":645,"line":2331},[643,5655,1307],{"emptyLinePlaceholder":1306},[643,5657,5658,5660,5662],{"class":645,"line":2336},[643,5659,1801],{"class":726},[643,5661,4077],{"class":1046},[643,5663,3858],{"class":726},[643,5665,5666,5668],{"class":645,"line":2342},[643,5667,4084],{"class":726},[643,5669,5670],{"class":1046},"div\n",[643,5672,5673,5675,5677,5679,5682],{"class":645,"line":2347},[643,5674,4092],{"class":735},[643,5676,1567],{"class":726},[643,5678,739],{"class":726},[643,5680,5681],{"class":653},"todo-item",[643,5683,885],{"class":726},[643,5685,5686,5689,5691,5693],{"class":645,"line":2352},[643,5687,5688],{"class":735},"    :class",[643,5690,1567],{"class":726},[643,5692,739],{"class":726},[643,5694,727],{"class":653},[643,5696,5697],{"class":645,"line":2357},[643,5698,5699],{"class":653},"      completed: todo.completed,\n",[643,5701,5702],{"class":645,"line":2363},[643,5703,5704],{"class":653},"      editing: isEditing\n",[643,5706,5707,5709],{"class":645,"line":2369},[643,5708,2133],{"class":653},[643,5710,885],{"class":726},[643,5712,5713],{"class":645,"line":2374},[643,5714,4120],{"class":726},[643,5716,5717],{"class":645,"line":2423},[643,5718,5719],{"class":1023},"    \u003C!-- Checkbox: toggle completed -->\n",[643,5721,5722,5724],{"class":645,"line":2466},[643,5723,4125],{"class":726},[643,5725,4128],{"class":1046},[643,5727,5728,5730,5732,5734,5737],{"class":645,"line":2471},[643,5729,4147],{"class":735},[643,5731,1567],{"class":726},[643,5733,739],{"class":726},[643,5735,5736],{"class":653},"checkbox",[643,5738,885],{"class":726},[643,5740,5741,5743,5745,5747,5750],{"class":645,"line":2502},[643,5742,4160],{"class":735},[643,5744,1567],{"class":726},[643,5746,739],{"class":726},[643,5748,5749],{"class":653},"todo-checkbox",[643,5751,885],{"class":726},[643,5753,5754,5757,5759,5761,5764],{"class":645,"line":2532},[643,5755,5756],{"class":735},"      :checked",[643,5758,1567],{"class":726},[643,5760,739],{"class":726},[643,5762,5763],{"class":653},"todo.completed",[643,5765,885],{"class":726},[643,5767,5768,5771,5773,5775,5778],{"class":645,"line":2537},[643,5769,5770],{"class":735},"      @change",[643,5772,1567],{"class":726},[643,5774,739],{"class":726},[643,5776,5777],{"class":653},"emit('toggle', todo.id)",[643,5779,885],{"class":726},[643,5781,5782],{"class":645,"line":2542},[643,5783,4202],{"class":726},[643,5785,5786],{"class":645,"line":2548},[643,5787,1307],{"emptyLinePlaceholder":1306},[643,5789,5790],{"class":645,"line":2554},[643,5791,5792],{"class":1023},"    \u003C!-- VIEW MODE: hiển thị title -->\n",[643,5794,5795,5797],{"class":645,"line":2559},[643,5796,4125],{"class":726},[643,5798,5799],{"class":1046},"label\n",[643,5801,5802,5805,5807,5809,5812],{"class":645,"line":2579},[643,5803,5804],{"class":735},"      v-if",[643,5806,1567],{"class":726},[643,5808,739],{"class":726},[643,5810,5811],{"class":653},"!isEditing",[643,5813,885],{"class":726},[643,5815,5816,5818,5820,5822,5825],{"class":645,"line":2597},[643,5817,4160],{"class":735},[643,5819,1567],{"class":726},[643,5821,739],{"class":726},[643,5823,5824],{"class":653},"todo-title",[643,5826,885],{"class":726},[643,5828,5829,5832,5834,5836,5839],{"class":645,"line":2612},[643,5830,5831],{"class":735},"      @dblclick",[643,5833,1567],{"class":726},[643,5835,739],{"class":726},[643,5837,5838],{"class":653},"startEdit",[643,5840,885],{"class":726},[643,5842,5843],{"class":645,"line":2625},[643,5844,4254],{"class":726},[643,5846,5847],{"class":645,"line":2638},[643,5848,5849],{"class":1038},"      {{ todo.title }}\n",[643,5851,5852,5854,5857],{"class":645,"line":2649},[643,5853,4264],{"class":726},[643,5855,5856],{"class":1046},"label",[643,5858,3858],{"class":726},[643,5860,5861],{"class":645,"line":2657},[643,5862,1307],{"emptyLinePlaceholder":1306},[643,5864,5865],{"class":645,"line":2668},[643,5866,5867],{"class":1023},"    \u003C!-- EDIT MODE: input chỉnh sửa -->\n",[643,5869,5870,5872],{"class":645,"line":2673},[643,5871,4125],{"class":726},[643,5873,4128],{"class":1046},[643,5875,5876],{"class":645,"line":2681},[643,5877,5878],{"class":735},"      v-else\n",[643,5880,5881,5884,5886,5888,5891],{"class":645,"line":2686},[643,5882,5883],{"class":735},"      ref",[643,5885,1567],{"class":726},[643,5887,739],{"class":726},[643,5889,5890],{"class":653},"editInputRef",[643,5892,885],{"class":726},[643,5894,5895,5897,5899,5901,5904],{"class":645,"line":2691},[643,5896,4133],{"class":735},[643,5898,1567],{"class":726},[643,5900,739],{"class":726},[643,5902,5903],{"class":653},"editText",[643,5905,885],{"class":726},[643,5907,5908,5910,5912,5914,5916],{"class":645,"line":2697},[643,5909,4147],{"class":735},[643,5911,1567],{"class":726},[643,5913,739],{"class":726},[643,5915,621],{"class":653},[643,5917,885],{"class":726},[643,5919,5920,5922,5924,5926,5929],{"class":645,"line":2702},[643,5921,4160],{"class":735},[643,5923,1567],{"class":726},[643,5925,739],{"class":726},[643,5927,5928],{"class":653},"edit-input",[643,5930,885],{"class":726},[643,5932,5933,5936,5938,5940,5943],{"class":645,"line":2707},[643,5934,5935],{"class":735},"      @keyup.enter",[643,5937,1567],{"class":726},[643,5939,739],{"class":726},[643,5941,5942],{"class":653},"saveEdit",[643,5944,885],{"class":726},[643,5946,5947,5950,5952,5954,5957],{"class":645,"line":2712},[643,5948,5949],{"class":735},"      @keyup.escape",[643,5951,1567],{"class":726},[643,5953,739],{"class":726},[643,5955,5956],{"class":653},"cancelEdit",[643,5958,885],{"class":726},[643,5960,5961,5964,5966,5968,5970],{"class":645,"line":2718},[643,5962,5963],{"class":735},"      @blur",[643,5965,1567],{"class":726},[643,5967,739],{"class":726},[643,5969,5942],{"class":653},[643,5971,885],{"class":726},[643,5973,5974],{"class":645,"line":2724},[643,5975,4202],{"class":726},[643,5977,5978],{"class":645,"line":2730},[643,5979,1307],{"emptyLinePlaceholder":1306},[643,5981,5982],{"class":645,"line":2736},[643,5983,5984],{"class":1023},"    \u003C!-- Nút xóa -->\n",[643,5986,5987,5989],{"class":645,"line":2741},[643,5988,4125],{"class":726},[643,5990,4209],{"class":1046},[643,5992,5993,5995,5997,5999,6002],{"class":645,"line":2767},[643,5994,4160],{"class":735},[643,5996,1567],{"class":726},[643,5998,739],{"class":726},[643,6000,6001],{"class":653},"btn-delete",[643,6003,885],{"class":726},[643,6005,6006,6009,6011,6013,6016],{"class":645,"line":2788},[643,6007,6008],{"class":735},"      @click",[643,6010,1567],{"class":726},[643,6012,739],{"class":726},[643,6014,6015],{"class":653},"emit('remove', todo.id)",[643,6017,885],{"class":726},[643,6019,6020,6023,6025,6027,6030],{"class":645,"line":2810},[643,6021,6022],{"class":735},"      aria-label",[643,6024,1567],{"class":726},[643,6026,739],{"class":726},[643,6028,6029],{"class":653},"Delete todo",[643,6031,885],{"class":726},[643,6033,6034],{"class":645,"line":2815},[643,6035,4254],{"class":726},[643,6037,6038],{"class":645,"line":2831},[643,6039,6040],{"class":1038},"      ×\n",[643,6042,6043,6045,6047],{"class":645,"line":2849},[643,6044,4264],{"class":726},[643,6046,4267],{"class":1046},[643,6048,3858],{"class":726},[643,6050,6051,6053,6056],{"class":645,"line":2860},[643,6052,4274],{"class":726},[643,6054,6055],{"class":1046},"div",[643,6057,3858],{"class":726},[643,6059,6060,6062,6064],{"class":645,"line":2873},[643,6061,4062],{"class":726},[643,6063,4077],{"class":1046},[643,6065,3858],{"class":726},[643,6067,6068],{"class":645,"line":2887},[643,6069,1307],{"emptyLinePlaceholder":1306},[643,6071,6072,6074,6076,6078],{"class":645,"line":2892},[643,6073,1801],{"class":726},[643,6075,4298],{"class":1046},[643,6077,4301],{"class":735},[643,6079,3858],{"class":726},[643,6081,6082,6084,6086],{"class":645,"line":2913},[643,6083,497],{"class":726},[643,6085,5681],{"class":649},[643,6087,801],{"class":726},[643,6089,6090,6092,6094,6096],{"class":645,"line":2918},[643,6091,4317],{"class":4316},[643,6093,742],{"class":726},[643,6095,4322],{"class":1038},[643,6097,4325],{"class":726},[643,6099,6100,6103,6105,6108],{"class":645,"line":2923},[643,6101,6102],{"class":4316},"  align-items",[643,6104,742],{"class":726},[643,6106,6107],{"class":1038}," center",[643,6109,4325],{"class":726},[643,6111,6112,6114,6116,6118],{"class":645,"line":2928},[643,6113,4330],{"class":4316},[643,6115,742],{"class":726},[643,6117,4388],{"class":4335},[643,6119,4325],{"class":726},[643,6121,6122,6124,6126,6128,6130],{"class":645,"line":2934},[643,6123,4383],{"class":4316},[643,6125,742],{"class":726},[643,6127,4388],{"class":4335},[643,6129,4391],{"class":4335},[643,6131,4325],{"class":726},[643,6133,6134,6137,6139,6142,6144,6146,6148,6150],{"class":645,"line":2940},[643,6135,6136],{"class":4316},"  border-bottom",[643,6138,742],{"class":726},[643,6140,6141],{"class":4335}," 1px",[643,6143,4417],{"class":1038},[643,6145,4420],{"class":666},[643,6147,1039],{"class":726},[643,6149,4425],{"class":1038},[643,6151,4428],{"class":726},[643,6153,6154,6156,6158,6161,6163],{"class":645,"line":2945},[643,6155,4494],{"class":4316},[643,6157,742],{"class":726},[643,6159,6160],{"class":1038}," background-color ",[643,6162,4502],{"class":4335},[643,6164,4325],{"class":726},[643,6166,6167],{"class":645,"line":2970},[643,6168,957],{"class":726},[643,6170,6171],{"class":645,"line":3010},[643,6172,1307],{"emptyLinePlaceholder":1306},[643,6174,6175,6177,6179,6181,6183],{"class":645,"line":3023},[643,6176,497],{"class":726},[643,6178,5681],{"class":649},[643,6180,742],{"class":726},[643,6182,4729],{"class":735},[643,6184,801],{"class":726},[643,6186,6187,6189,6191,6193,6195,6198],{"class":645,"line":3044},[643,6188,4450],{"class":4316},[643,6190,742],{"class":726},[643,6192,4438],{"class":666},[643,6194,1039],{"class":726},[643,6196,6197],{"class":1038},"--color-bg-hover",[643,6199,4428],{"class":726},[643,6201,6202],{"class":645,"line":3049},[643,6203,957],{"class":726},[643,6205,6206],{"class":645,"line":3054},[643,6207,1307],{"emptyLinePlaceholder":1306},[643,6209,6210,6212,6214,6216,6218,6221,6223],{"class":645,"line":3059},[643,6211,497],{"class":726},[643,6213,5681],{"class":649},[643,6215,497],{"class":726},[643,6217,1483],{"class":649},[643,6219,6220],{"class":726}," .",[643,6222,5824],{"class":649},[643,6224,801],{"class":726},[643,6226,6227,6230,6232,6235],{"class":645,"line":3064},[643,6228,6229],{"class":4316},"  text-decoration",[643,6231,742],{"class":726},[643,6233,6234],{"class":1038}," line-through",[643,6236,4325],{"class":726},[643,6238,6239,6241,6243,6245,6247,6249],{"class":645,"line":3070},[643,6240,4466],{"class":4316},[643,6242,742],{"class":726},[643,6244,4438],{"class":666},[643,6246,1039],{"class":726},[643,6248,4576],{"class":1038},[643,6250,4428],{"class":726},[643,6252,6253],{"class":645,"line":3076},[643,6254,957],{"class":726},[643,6256,6257],{"class":645,"line":3082},[643,6258,1307],{"emptyLinePlaceholder":1306},[643,6260,6261,6263,6265,6267,6270],{"class":645,"line":3087},[643,6262,497],{"class":726},[643,6264,5681],{"class":649},[643,6266,497],{"class":726},[643,6268,6269],{"class":649},"editing",[643,6271,801],{"class":726},[643,6273,6274,6276,6278,6280,6282,6285],{"class":645,"line":3111},[643,6275,4450],{"class":4316},[643,6277,742],{"class":726},[643,6279,4438],{"class":666},[643,6281,1039],{"class":726},[643,6283,6284],{"class":1038},"--color-bg-active",[643,6286,4428],{"class":726},[643,6288,6289],{"class":645,"line":3151},[643,6290,957],{"class":726},[643,6292,6293],{"class":645,"line":3156},[643,6294,1307],{"emptyLinePlaceholder":1306},[643,6296,6297,6299,6301],{"class":645,"line":3161},[643,6298,497],{"class":726},[643,6300,5749],{"class":649},[643,6302,801],{"class":726},[643,6304,6305,6308,6310,6313],{"class":645,"line":3166},[643,6306,6307],{"class":4316},"  width",[643,6309,742],{"class":726},[643,6311,6312],{"class":4335}," 20px",[643,6314,4325],{"class":726},[643,6316,6317,6320,6322,6324],{"class":645,"line":3172},[643,6318,6319],{"class":4316},"  height",[643,6321,742],{"class":726},[643,6323,6312],{"class":4335},[643,6325,4325],{"class":726},[643,6327,6328,6331,6333,6335,6337,6339],{"class":645,"line":3178},[643,6329,6330],{"class":4316},"  accent-color",[643,6332,742],{"class":726},[643,6334,4438],{"class":666},[643,6336,1039],{"class":726},[643,6338,4539],{"class":1038},[643,6340,4428],{"class":726},[643,6342,6343,6345,6347,6349],{"class":645,"line":3183},[643,6344,4682],{"class":4316},[643,6346,742],{"class":726},[643,6348,4687],{"class":1038},[643,6350,4325],{"class":726},[643,6352,6353,6356,6358,6361],{"class":645,"line":3215},[643,6354,6355],{"class":4316},"  flex-shrink",[643,6357,742],{"class":726},[643,6359,6360],{"class":4335}," 0",[643,6362,4325],{"class":726},[643,6364,6365],{"class":645,"line":3232},[643,6366,957],{"class":726},[643,6368,6369],{"class":645,"line":3247},[643,6370,1307],{"emptyLinePlaceholder":1306},[643,6372,6373,6375,6377],{"class":645,"line":3253},[643,6374,497],{"class":726},[643,6376,5824],{"class":649},[643,6378,801],{"class":726},[643,6380,6381,6383,6385,6387],{"class":645,"line":3265},[643,6382,4371],{"class":4316},[643,6384,742],{"class":726},[643,6386,4376],{"class":4335},[643,6388,4325],{"class":726},[643,6390,6391,6393,6395,6397],{"class":645,"line":3271},[643,6392,4398],{"class":4316},[643,6394,742],{"class":726},[643,6396,4391],{"class":4335},[643,6398,4325],{"class":726},[643,6400,6401,6403,6405,6407],{"class":645,"line":3276},[643,6402,4682],{"class":4316},[643,6404,742],{"class":726},[643,6406,4687],{"class":1038},[643,6408,4325],{"class":726},[643,6410,6411,6414,6416,6418],{"class":645,"line":3281},[643,6412,6413],{"class":4316},"  user-select",[643,6415,742],{"class":726},[643,6417,4487],{"class":1038},[643,6419,4325],{"class":726},[643,6421,6422,6425,6427,6430],{"class":645,"line":3318},[643,6423,6424],{"class":4316},"  word-break",[643,6426,742],{"class":726},[643,6428,6429],{"class":1038}," break-word",[643,6431,4325],{"class":726},[643,6433,6434],{"class":645,"line":3331},[643,6435,957],{"class":726},[643,6437,6438],{"class":645,"line":3345},[643,6439,1307],{"emptyLinePlaceholder":1306},[643,6441,6442,6444,6446],{"class":645,"line":3350},[643,6443,497],{"class":726},[643,6445,5928],{"class":649},[643,6447,801],{"class":726},[643,6449,6450,6452,6454,6456],{"class":645,"line":3355},[643,6451,4371],{"class":4316},[643,6453,742],{"class":726},[643,6455,4376],{"class":4335},[643,6457,4325],{"class":726},[643,6459,6460,6462,6464,6466,6468],{"class":645,"line":3360},[643,6461,4383],{"class":4316},[643,6463,742],{"class":726},[643,6465,4336],{"class":4335},[643,6467,4388],{"class":4335},[643,6469,4325],{"class":726},[643,6471,6472,6474,6476,6478],{"class":645,"line":3365},[643,6473,4398],{"class":4316},[643,6475,742],{"class":726},[643,6477,4391],{"class":4335},[643,6479,4325],{"class":726},[643,6481,6482,6484,6486,6488,6490,6492,6494,6496],{"class":645,"line":3371},[643,6483,4409],{"class":4316},[643,6485,742],{"class":726},[643,6487,4414],{"class":4335},[643,6489,4417],{"class":1038},[643,6491,4420],{"class":666},[643,6493,1039],{"class":726},[643,6495,4539],{"class":1038},[643,6497,4428],{"class":726},[643,6499,6500,6502,6504,6506,6508,6510],{"class":645,"line":3377},[643,6501,4433],{"class":4316},[643,6503,742],{"class":726},[643,6505,4438],{"class":666},[643,6507,1039],{"class":726},[643,6509,4443],{"class":1038},[643,6511,4428],{"class":726},[643,6513,6514,6516,6518,6520,6522,6524],{"class":645,"line":3382},[643,6515,4450],{"class":4316},[643,6517,742],{"class":726},[643,6519,4438],{"class":666},[643,6521,1039],{"class":726},[643,6523,4459],{"class":1038},[643,6525,4428],{"class":726},[643,6527,6528,6530,6532,6534,6536,6538],{"class":645,"line":3398},[643,6529,4466],{"class":4316},[643,6531,742],{"class":726},[643,6533,4438],{"class":666},[643,6535,1039],{"class":726},[643,6537,4475],{"class":1038},[643,6539,4428],{"class":726},[643,6541,6542,6544,6546,6548],{"class":645,"line":3435},[643,6543,4482],{"class":4316},[643,6545,742],{"class":726},[643,6547,4487],{"class":1038},[643,6549,4325],{"class":726},[643,6551,6552],{"class":645,"line":3440},[643,6553,957],{"class":726},[643,6555,6556],{"class":645,"line":3445},[643,6557,1307],{"emptyLinePlaceholder":1306},[643,6559,6560,6562,6564],{"class":645,"line":3450},[643,6561,497],{"class":726},[643,6563,6001],{"class":649},[643,6565,801],{"class":726},[643,6567,6568,6570,6572,6575],{"class":645,"line":3456},[643,6569,6307],{"class":4316},[643,6571,742],{"class":726},[643,6573,6574],{"class":4335}," 32px",[643,6576,4325],{"class":726},[643,6578,6579,6581,6583,6585],{"class":645,"line":3461},[643,6580,6319],{"class":4316},[643,6582,742],{"class":726},[643,6584,6574],{"class":4335},[643,6586,4325],{"class":726},[643,6588,6589,6591,6593,6595],{"class":645,"line":3466},[643,6590,4317],{"class":4316},[643,6592,742],{"class":726},[643,6594,4322],{"class":1038},[643,6596,4325],{"class":726},[643,6598,6599,6601,6603,6605],{"class":645,"line":3474},[643,6600,6102],{"class":4316},[643,6602,742],{"class":726},[643,6604,6107],{"class":1038},[643,6606,4325],{"class":726},[643,6608,6609,6612,6614,6616],{"class":645,"line":3480},[643,6610,6611],{"class":4316},"  justify-content",[643,6613,742],{"class":726},[643,6615,6107],{"class":1038},[643,6617,4325],{"class":726},[643,6619,6620,6622,6624,6626],{"class":645,"line":3487},[643,6621,4398],{"class":4316},[643,6623,742],{"class":726},[643,6625,6312],{"class":4335},[643,6627,4325],{"class":726},[643,6629,6630,6632,6634,6637],{"class":645,"line":3495},[643,6631,4621],{"class":4316},[643,6633,742],{"class":726},[643,6635,6636],{"class":4335}," 300",[643,6638,4325],{"class":726},[643,6640,6641,6643,6645,6647],{"class":645,"line":3500},[643,6642,4409],{"class":4316},[643,6644,742],{"class":726},[643,6646,4487],{"class":1038},[643,6648,4325],{"class":726},[643,6650,6651,6653,6655,6658],{"class":645,"line":3506},[643,6652,4433],{"class":4316},[643,6654,742],{"class":726},[643,6656,6657],{"class":4335}," 50%",[643,6659,4325],{"class":726},[643,6661,6662,6664,6666,6669],{"class":645,"line":3514},[643,6663,4450],{"class":4316},[643,6665,742],{"class":726},[643,6667,6668],{"class":1038}," transparent",[643,6670,4325],{"class":726},[643,6672,6673,6675,6677,6679,6681,6683],{"class":645,"line":3522},[643,6674,4466],{"class":4316},[643,6676,742],{"class":726},[643,6678,4438],{"class":666},[643,6680,1039],{"class":726},[643,6682,4576],{"class":1038},[643,6684,4428],{"class":726},[643,6686,6687,6689,6691,6693],{"class":645,"line":3530},[643,6688,4682],{"class":4316},[643,6690,742],{"class":726},[643,6692,4687],{"class":1038},[643,6694,4325],{"class":726},[643,6696,6697,6699,6701,6704,6706],{"class":645,"line":3538},[643,6698,4494],{"class":4316},[643,6700,742],{"class":726},[643,6702,6703],{"class":1038}," all ",[643,6705,4502],{"class":4335},[643,6707,4325],{"class":726},[643,6709,6710,6712,6714,6716],{"class":645,"line":3546},[643,6711,6355],{"class":4316},[643,6713,742],{"class":726},[643,6715,6360],{"class":4335},[643,6717,4325],{"class":726},[643,6719,6720,6722,6724,6726],{"class":645,"line":3551},[643,6721,4749],{"class":4316},[643,6723,742],{"class":726},[643,6725,6360],{"class":4335},[643,6727,4325],{"class":726},[643,6729,6730],{"class":645,"line":3557},[643,6731,957],{"class":726},[643,6733,6734],{"class":645,"line":3565},[643,6735,1307],{"emptyLinePlaceholder":1306},[643,6737,6738,6740,6742,6744,6746,6748,6750],{"class":645,"line":3573},[643,6739,497],{"class":726},[643,6741,5681],{"class":649},[643,6743,742],{"class":726},[643,6745,4729],{"class":735},[643,6747,6220],{"class":726},[643,6749,6001],{"class":649},[643,6751,801],{"class":726},[643,6753,6754,6756,6758,6760],{"class":645,"line":3581},[643,6755,4749],{"class":4316},[643,6757,742],{"class":726},[643,6759,4376],{"class":4335},[643,6761,4325],{"class":726},[643,6763,6764],{"class":645,"line":3589},[643,6765,957],{"class":726},[643,6767,6768],{"class":645,"line":3595},[643,6769,1307],{"emptyLinePlaceholder":1306},[643,6771,6772,6774,6776,6778,6780],{"class":645,"line":3600},[643,6773,497],{"class":726},[643,6775,6001],{"class":649},[643,6777,742],{"class":726},[643,6779,4729],{"class":735},[643,6781,801],{"class":726},[643,6783,6785,6787,6789,6791,6793,6796],{"class":645,"line":6784},189,[643,6786,4450],{"class":4316},[643,6788,742],{"class":726},[643,6790,4438],{"class":666},[643,6792,1039],{"class":726},[643,6794,6795],{"class":1038},"--color-danger",[643,6797,4428],{"class":726},[643,6799,6801,6803,6805,6807],{"class":645,"line":6800},190,[643,6802,4466],{"class":4316},[643,6804,742],{"class":726},[643,6806,4675],{"class":1038},[643,6808,4325],{"class":726},[643,6810,6812],{"class":645,"line":6811},191,[643,6813,957],{"class":726},[643,6815,6817,6819,6821],{"class":645,"line":6816},192,[643,6818,4062],{"class":726},[643,6820,4298],{"class":1046},[643,6822,3858],{"class":726},[466,6824,1490],{"id":6825},"giải-thích-chi-tiết-2",[432,6827,6828],{},[436,6829,6830,742],{},[487,6831,6832],{},"defineProps\u003C{ todo: Todo }>()",[471,6834,6835,6841,6847],{},[474,6836,6837,6840],{},[436,6838,6839],{},"Typed props"," — TypeScript sẽ check kiểu khi parent truyền prop.",[474,6842,6843,6844,6846],{},"Không cần import ",[487,6845,553],{}," — compiler macro của Vue, auto-available.",[474,6848,6849,6850,497],{},"Runtime validation: chỉ check type ở compile time. Muốn runtime validation dùng object syntax ",[487,6851,6852],{},"defineProps({ todo: { type: Object, required: true } })",[432,6854,6855],{},[436,6856,6857,742],{},[487,6858,6859],{},"defineEmits\u003C{ toggle: [id: string]; remove: [id: string]; edit: [id: string, title: string] }>()",[471,6861,6862,6871,6877],{},[474,6863,6864,6867,6868,497],{},[436,6865,6866],{},"Typed emits"," — TypeScript sẽ check argument khi ",[487,6869,6870],{},"emit('toggle', ...)",[474,6872,6873,6874,497],{},"Cú pháp: ",[487,6875,6876],{},"{ eventName: [arg1Type, arg2Type, ...] }",[474,6878,6879],{},"Cũng là compiler macro — không cần import.",[432,6881,6882],{},[436,6883,6884,554,6887,742],{},[487,6885,6886],{},"v-if=\"!isEditing\"",[487,6888,576],{},[471,6890,6891,6897,6902,6913],{},[474,6892,6893,6896],{},[436,6894,6895],{},"Conditional rendering",": Vue chỉ render một trong hai nhánh.",[474,6898,6899,6901],{},[487,6900,573],{}," thực sự remove\u002Fadd element khỏi DOM.",[474,6903,6904,6905,6908,6909,6912],{},"Khác với ",[487,6906,6907],{},"v-show"," — chỉ toggle ",[487,6910,6911],{},"display: none",", element vẫn trong DOM.",[474,6914,6915,6916,6918],{},"Dùng ",[487,6917,573],{}," ở đây vì edit mode ít khi được dùng — tối ưu DOM.",[432,6920,6921],{},[436,6922,6923,742],{},[487,6924,6925],{},"@dblclick=\"startEdit\"",[471,6927,6928,6931],{},[474,6929,6930],{},"Bắt sự kiện double-click để vào edit mode.",[474,6932,6933],{},"Pattern UX quen thuộc từ TodoMVC — double-click để edit.",[432,6935,6936],{},[436,6937,6938,742],{},[487,6939,6940],{},"@keyup.enter=\"saveEdit\"",[471,6942,6943,6949,6955,6982],{},[474,6944,6945,6948],{},[436,6946,6947],{},"Keyboard shortcut"," — nhấn Enter để lưu.",[474,6950,6951,6954],{},[487,6952,6953],{},".enter"," là key modifier — chỉ trigger khi phím Enter được nhấn.",[474,6956,6957,6958,490,6961,490,6964,490,6967,490,6970,490,6973,490,6976,490,6979,497],{},"Các key modifier khác: ",[487,6959,6960],{},".esc",[487,6962,6963],{},".space",[487,6965,6966],{},".tab",[487,6968,6969],{},".delete",[487,6971,6972],{},".up",[487,6974,6975],{},".down",[487,6977,6978],{},".left",[487,6980,6981],{},".right",[474,6983,6984,6985,6988],{},"Có thể chain: ",[487,6986,6987],{},"@keyup.ctrl.enter=\"...\""," — Ctrl+Enter.",[432,6990,6991],{},[436,6992,6993,742],{},[487,6994,6995],{},"@keyup.escape=\"cancelEdit\"",[471,6997,6998,7001],{},[474,6999,7000],{},"Nhấn Escape để hủy edit, quay lại view mode.",[474,7002,7003],{},"Không lưu thay đổi khi hủy.",[432,7005,7006],{},[436,7007,7008,742],{},[487,7009,7010],{},"@blur=\"saveEdit\"",[471,7012,7013,7016],{},[474,7014,7015],{},"Khi user click ra ngoài input (focus lost) → tự động lưu.",[474,7017,7018],{},"Kết hợp với Enter tạo UX tốt: Enter hoặc click ra ngoài đều lưu, Escape để hủy.",[432,7020,7021],{},[436,7022,7023,742],{},[487,7024,7025],{},":class=\"{ completed: todo.completed, editing: isEditing }\"",[471,7027,7028,7033,7039,7045],{},[474,7029,7030,497],{},[436,7031,7032],{},"Object syntax cho dynamic class",[474,7034,7035,7038],{},[487,7036,7037],{},"{ className: booleanExpression }"," — class được add khi expression = true.",[474,7040,7041,7042,497],{},"Có thể kết hợp với static class: ",[487,7043,7044],{},"class=\"todo-item\" :class=\"{ completed: ... }\"",[474,7046,7047,7048,497],{},"Array syntax: ",[487,7049,7050],{},":class=\"['base-class', isActive && 'active']\"",[432,7052,7053],{},[436,7054,7055,7056,742],{},"CSS ",[487,7057,7058],{},".todo-item:hover .btn-delete { opacity: 1 }",[471,7060,7061,7064,7067],{},[474,7062,7063],{},"Nút xóa chỉ hiển thị khi hover vào dòng todo.",[474,7065,7066],{},"Giữ UI sạch sẽ, không bị clutter bởi quá nhiều nút xóa.",[474,7068,7069,7072,7073,7076],{},[487,7070,7071],{},"opacity: 0"," mặc định → ",[487,7074,7075],{},"opacity: 1"," khi hover cha.",[626,7078],{},[466,7080,7082],{"id":7081},"_43-todofiltervue-bộ-lọc-todo","4.3 TodoFilter.vue — Bộ lọc todo",[432,7084,1288,7085,742],{},[487,7086,7087],{},"app\u002Fcomponents\u002FTodoFilter.vue",[616,7089,7091],{"className":3830,"code":7090,"language":934,"meta":624,"style":624},"\u003Cscript setup lang=\"ts\">\nimport type { TodoFilter } from '~\u002Ftypes\u002Ftodo'\n\n\u002F**\n * TodoFilter — bộ lọc all \u002F active \u002F completed.\n *\n * Sử dụng defineModel — Vue 3.4+ syntactic sugar cho v-model trong component.\n * defineModel tạo một ref đồng bộ 2 chiều với parent qua v-model.\n *\u002F\n\n\u002F**\n * defineModel\u003CTodoFilter>({ required: true })\n *\n * - Tạo ra một ref mà khi thay đổi sẽ tự động emit update:modelValue.\n * - Parent dùng: \u003CTodoFilter v-model=\"filter\" \u002F>\n * - Không cần defineProps + defineEmits thủ công nữa.\n * - required: true — bắt buộc parent phải truyền v-model.\n *\u002F\nconst filter = defineModel\u003CTodoFilter>({ required: true })\n\n\u002F** Danh sách các filter option *\u002F\nconst filters: { label: string; value: TodoFilter }[] = [\n  { label: 'All', value: 'all' },\n  { label: 'Active', value: 'active' },\n  { label: 'Completed', value: 'completed' }\n]\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"todo-filters\">\n    \u003Cbutton\n      v-for=\"f in filters\"\n      :key=\"f.value\"\n      class=\"filter-btn\"\n      :class=\"{ active: filter === f.value }\"\n      @click=\"filter = f.value\"\n    >\n      {{ f.label }}\n    \u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n.todo-filters {\n  display: flex;\n  gap: 4px;\n  justify-content: center;\n  margin-bottom: 16px;\n}\n\n.filter-btn {\n  padding: 6px 16px;\n  font-size: 14px;\n  font-weight: 500;\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius);\n  background: transparent;\n  color: var(--color-text-muted);\n  cursor: pointer;\n  transition: all 0.2s;\n}\n\n.filter-btn:hover {\n  color: var(--color-text);\n  border-color: var(--color-text-muted);\n}\n\n.filter-btn.active {\n  background: var(--color-primary);\n  color: white;\n  border-color: var(--color-primary);\n}\n\u003C\u002Fstyle>\n",[487,7092,7093,7113,7133,7137,7141,7146,7150,7155,7160,7164,7168,7172,7177,7181,7186,7191,7196,7201,7205,7239,7243,7248,7284,7314,7343,7372,7376,7384,7388,7396,7416,7422,7436,7450,7463,7477,7490,7494,7499,7507,7515,7523,7527,7537,7545,7555,7566,7576,7586,7590,7594,7602,7615,7626,7637,7655,7669,7679,7693,7703,7715,7719,7723,7735,7749,7763,7767,7771,7783,7797,7807,7821,7825],{"__ignoreMap":624},[643,7094,7095,7097,7099,7101,7103,7105,7107,7109,7111],{"class":645,"line":646},[643,7096,1801],{"class":726},[643,7098,3840],{"class":1046},[643,7100,3843],{"class":735},[643,7102,3846],{"class":735},[643,7104,1567],{"class":726},[643,7106,739],{"class":726},[643,7108,3853],{"class":653},[643,7110,739],{"class":726},[643,7112,3858],{"class":726},[643,7114,7115,7117,7119,7121,7123,7125,7127,7129,7131],{"class":645,"line":663},[643,7116,968],{"class":1029},[643,7118,1453],{"class":1029},[643,7120,1070],{"class":726},[643,7122,1456],{"class":1038},[643,7124,1665],{"class":726},[643,7126,1668],{"class":1029},[643,7128,1052],{"class":726},[643,7130,1673],{"class":653},[643,7132,1486],{"class":726},[643,7134,7135],{"class":645,"line":672},[643,7136,1307],{"emptyLinePlaceholder":1306},[643,7138,7139],{"class":645,"line":681},[643,7140,1312],{"class":1023},[643,7142,7143],{"class":645,"line":789},[643,7144,7145],{"class":1023}," * TodoFilter — bộ lọc all \u002F active \u002F completed.\n",[643,7147,7148],{"class":645,"line":804},[643,7149,1693],{"class":1023},[643,7151,7152],{"class":645,"line":826},[643,7153,7154],{"class":1023}," * Sử dụng defineModel — Vue 3.4+ syntactic sugar cho v-model trong component.\n",[643,7156,7157],{"class":645,"line":847},[643,7158,7159],{"class":1023}," * defineModel tạo một ref đồng bộ 2 chiều với parent qua v-model.\n",[643,7161,7162],{"class":645,"line":868},[643,7163,1327],{"class":1023},[643,7165,7166],{"class":645,"line":888},[643,7167,1307],{"emptyLinePlaceholder":1306},[643,7169,7170],{"class":645,"line":894},[643,7171,1312],{"class":1023},[643,7173,7174],{"class":645,"line":908},[643,7175,7176],{"class":1023}," * defineModel\u003CTodoFilter>({ required: true })\n",[643,7178,7179],{"class":645,"line":929},[643,7180,1693],{"class":1023},[643,7182,7183],{"class":645,"line":948},[643,7184,7185],{"class":1023}," * - Tạo ra một ref mà khi thay đổi sẽ tự động emit update:modelValue.\n",[643,7187,7188],{"class":645,"line":954},[643,7189,7190],{"class":1023}," * - Parent dùng: \u003CTodoFilter v-model=\"filter\" \u002F>\n",[643,7192,7193],{"class":645,"line":1394},[643,7194,7195],{"class":1023}," * - Không cần defineProps + defineEmits thủ công nữa.\n",[643,7197,7198],{"class":645,"line":1399},[643,7199,7200],{"class":1023}," * - required: true — bắt buộc parent phải truyền v-model.\n",[643,7202,7203],{"class":645,"line":1405},[643,7204,1327],{"class":1023},[643,7206,7207,7209,7212,7214,7217,7219,7221,7223,7225,7228,7231,7233,7235,7237],{"class":645,"line":1416},[643,7208,1561],{"class":735},[643,7210,7211],{"class":1038}," filter ",[643,7213,1567],{"class":726},[643,7215,7216],{"class":666}," defineModel",[643,7218,1801],{"class":726},[643,7220,1871],{"class":649},[643,7222,1810],{"class":726},[643,7224,1039],{"class":1038},[643,7226,7227],{"class":726},"{",[643,7229,7230],{"class":1046}," required",[643,7232,742],{"class":726},[643,7234,1079],{"class":1078},[643,7236,1665],{"class":726},[643,7238,1217],{"class":1038},[643,7240,7241],{"class":645,"line":1421},[643,7242,1307],{"emptyLinePlaceholder":1306},[643,7244,7245],{"class":645,"line":1426},[643,7246,7247],{"class":1023},"\u002F** Danh sách các filter option *\u002F\n",[643,7249,7250,7252,7255,7257,7259,7262,7264,7266,7269,7272,7274,7276,7278,7280,7282],{"class":645,"line":1431},[643,7251,1561],{"class":735},[643,7253,7254],{"class":1038}," filters",[643,7256,742],{"class":726},[643,7258,1070],{"class":726},[643,7260,7261],{"class":1046}," label",[643,7263,742],{"class":726},[643,7265,2758],{"class":649},[643,7267,7268],{"class":726},";",[643,7270,7271],{"class":1046}," value",[643,7273,742],{"class":726},[643,7275,1456],{"class":649},[643,7277,1665],{"class":726},[643,7279,2029],{"class":1038},[643,7281,1567],{"class":726},[643,7283,1160],{"class":1038},[643,7285,7286,7289,7291,7293,7295,7298,7300,7302,7304,7306,7308,7310,7312],{"class":645,"line":1437},[643,7287,7288],{"class":726},"  {",[643,7290,7261],{"class":1046},[643,7292,742],{"class":726},[643,7294,1052],{"class":726},[643,7296,7297],{"class":653},"All",[643,7299,1058],{"class":726},[643,7301,1180],{"class":726},[643,7303,7271],{"class":1046},[643,7305,742],{"class":726},[643,7307,1052],{"class":726},[643,7309,1464],{"class":653},[643,7311,1058],{"class":726},[643,7313,1082],{"class":726},[643,7315,7316,7318,7320,7322,7324,7327,7329,7331,7333,7335,7337,7339,7341],{"class":645,"line":1443},[643,7317,7288],{"class":726},[643,7319,7261],{"class":1046},[643,7321,742],{"class":726},[643,7323,1052],{"class":726},[643,7325,7326],{"class":653},"Active",[643,7328,1058],{"class":726},[643,7330,1180],{"class":726},[643,7332,7271],{"class":1046},[643,7334,742],{"class":726},[643,7336,1052],{"class":726},[643,7338,1474],{"class":653},[643,7340,1058],{"class":726},[643,7342,1082],{"class":726},[643,7344,7345,7347,7349,7351,7353,7356,7358,7360,7362,7364,7366,7368,7370],{"class":645,"line":1448},[643,7346,7288],{"class":726},[643,7348,7261],{"class":1046},[643,7350,742],{"class":726},[643,7352,1052],{"class":726},[643,7354,7355],{"class":653},"Completed",[643,7357,1058],{"class":726},[643,7359,1180],{"class":726},[643,7361,7271],{"class":1046},[643,7363,742],{"class":726},[643,7365,1052],{"class":726},[643,7367,1483],{"class":653},[643,7369,1058],{"class":726},[643,7371,1195],{"class":726},[643,7373,7374],{"class":645,"line":1835},[643,7375,5216],{"class":1038},[643,7377,7378,7380,7382],{"class":645,"line":1840},[643,7379,4062],{"class":726},[643,7381,3840],{"class":1046},[643,7383,3858],{"class":726},[643,7385,7386],{"class":645,"line":1846},[643,7387,1307],{"emptyLinePlaceholder":1306},[643,7389,7390,7392,7394],{"class":645,"line":1852},[643,7391,1801],{"class":726},[643,7393,4077],{"class":1046},[643,7395,3858],{"class":726},[643,7397,7398,7400,7402,7405,7407,7409,7412,7414],{"class":645,"line":1857},[643,7399,4084],{"class":726},[643,7401,6055],{"class":1046},[643,7403,7404],{"class":735}," class",[643,7406,1567],{"class":726},[643,7408,739],{"class":726},[643,7410,7411],{"class":653},"todo-filters",[643,7413,739],{"class":726},[643,7415,3858],{"class":726},[643,7417,7418,7420],{"class":645,"line":1899},[643,7419,4125],{"class":726},[643,7421,4209],{"class":1046},[643,7423,7424,7427,7429,7431,7434],{"class":645,"line":1904},[643,7425,7426],{"class":735},"      v-for",[643,7428,1567],{"class":726},[643,7430,739],{"class":726},[643,7432,7433],{"class":653},"f in filters",[643,7435,885],{"class":726},[643,7437,7438,7441,7443,7445,7448],{"class":645,"line":1909},[643,7439,7440],{"class":735},"      :key",[643,7442,1567],{"class":726},[643,7444,739],{"class":726},[643,7446,7447],{"class":653},"f.value",[643,7449,885],{"class":726},[643,7451,7452,7454,7456,7458,7461],{"class":645,"line":1915},[643,7453,4160],{"class":735},[643,7455,1567],{"class":726},[643,7457,739],{"class":726},[643,7459,7460],{"class":653},"filter-btn",[643,7462,885],{"class":726},[643,7464,7465,7468,7470,7472,7475],{"class":645,"line":1920},[643,7466,7467],{"class":735},"      :class",[643,7469,1567],{"class":726},[643,7471,739],{"class":726},[643,7473,7474],{"class":653},"{ active: filter === f.value }",[643,7476,885],{"class":726},[643,7478,7479,7481,7483,7485,7488],{"class":645,"line":1925},[643,7480,6008],{"class":735},[643,7482,1567],{"class":726},[643,7484,739],{"class":726},[643,7486,7487],{"class":653},"filter = f.value",[643,7489,885],{"class":726},[643,7491,7492],{"class":645,"line":1931},[643,7493,4254],{"class":726},[643,7495,7496],{"class":645,"line":1957},[643,7497,7498],{"class":1038},"      {{ f.label }}\n",[643,7500,7501,7503,7505],{"class":645,"line":1965},[643,7502,4264],{"class":726},[643,7504,4267],{"class":1046},[643,7506,3858],{"class":726},[643,7508,7509,7511,7513],{"class":645,"line":1995},[643,7510,4274],{"class":726},[643,7512,6055],{"class":1046},[643,7514,3858],{"class":726},[643,7516,7517,7519,7521],{"class":645,"line":2010},[643,7518,4062],{"class":726},[643,7520,4077],{"class":1046},[643,7522,3858],{"class":726},[643,7524,7525],{"class":645,"line":2016},[643,7526,1307],{"emptyLinePlaceholder":1306},[643,7528,7529,7531,7533,7535],{"class":645,"line":2048},[643,7530,1801],{"class":726},[643,7532,4298],{"class":1046},[643,7534,4301],{"class":735},[643,7536,3858],{"class":726},[643,7538,7539,7541,7543],{"class":645,"line":2080},[643,7540,497],{"class":726},[643,7542,7411],{"class":649},[643,7544,801],{"class":726},[643,7546,7547,7549,7551,7553],{"class":645,"line":2090},[643,7548,4317],{"class":4316},[643,7550,742],{"class":726},[643,7552,4322],{"class":1038},[643,7554,4325],{"class":726},[643,7556,7557,7559,7561,7564],{"class":645,"line":2115},[643,7558,4330],{"class":4316},[643,7560,742],{"class":726},[643,7562,7563],{"class":4335}," 4px",[643,7565,4325],{"class":726},[643,7567,7568,7570,7572,7574],{"class":645,"line":2124},[643,7569,6611],{"class":4316},[643,7571,742],{"class":726},[643,7573,6107],{"class":1038},[643,7575,4325],{"class":726},[643,7577,7578,7580,7582,7584],{"class":645,"line":2130},[643,7579,4343],{"class":4316},[643,7581,742],{"class":726},[643,7583,4391],{"class":4335},[643,7585,4325],{"class":726},[643,7587,7588],{"class":645,"line":2141},[643,7589,957],{"class":726},[643,7591,7592],{"class":645,"line":2147},[643,7593,1307],{"emptyLinePlaceholder":1306},[643,7595,7596,7598,7600],{"class":645,"line":2169},[643,7597,497],{"class":726},[643,7599,7460],{"class":649},[643,7601,801],{"class":726},[643,7603,7604,7606,7608,7611,7613],{"class":645,"line":2174},[643,7605,4383],{"class":4316},[643,7607,742],{"class":726},[643,7609,7610],{"class":4335}," 6px",[643,7612,4391],{"class":4335},[643,7614,4325],{"class":726},[643,7616,7617,7619,7621,7624],{"class":645,"line":2179},[643,7618,4398],{"class":4316},[643,7620,742],{"class":726},[643,7622,7623],{"class":4335}," 14px",[643,7625,4325],{"class":726},[643,7627,7628,7630,7632,7635],{"class":645,"line":2184},[643,7629,4621],{"class":4316},[643,7631,742],{"class":726},[643,7633,7634],{"class":4335}," 500",[643,7636,4325],{"class":726},[643,7638,7639,7641,7643,7645,7647,7649,7651,7653],{"class":645,"line":2189},[643,7640,4409],{"class":4316},[643,7642,742],{"class":726},[643,7644,6141],{"class":4335},[643,7646,4417],{"class":1038},[643,7648,4420],{"class":666},[643,7650,1039],{"class":726},[643,7652,4425],{"class":1038},[643,7654,4428],{"class":726},[643,7656,7657,7659,7661,7663,7665,7667],{"class":645,"line":2195},[643,7658,4433],{"class":4316},[643,7660,742],{"class":726},[643,7662,4438],{"class":666},[643,7664,1039],{"class":726},[643,7666,4443],{"class":1038},[643,7668,4428],{"class":726},[643,7670,7671,7673,7675,7677],{"class":645,"line":2200},[643,7672,4450],{"class":4316},[643,7674,742],{"class":726},[643,7676,6668],{"class":1038},[643,7678,4325],{"class":726},[643,7680,7681,7683,7685,7687,7689,7691],{"class":645,"line":2206},[643,7682,4466],{"class":4316},[643,7684,742],{"class":726},[643,7686,4438],{"class":666},[643,7688,1039],{"class":726},[643,7690,4576],{"class":1038},[643,7692,4428],{"class":726},[643,7694,7695,7697,7699,7701],{"class":645,"line":2212},[643,7696,4682],{"class":4316},[643,7698,742],{"class":726},[643,7700,4687],{"class":1038},[643,7702,4325],{"class":726},[643,7704,7705,7707,7709,7711,7713],{"class":645,"line":2218},[643,7706,4494],{"class":4316},[643,7708,742],{"class":726},[643,7710,6703],{"class":1038},[643,7712,4502],{"class":4335},[643,7714,4325],{"class":726},[643,7716,7717],{"class":645,"line":2223},[643,7718,957],{"class":726},[643,7720,7721],{"class":645,"line":2244},[643,7722,1307],{"emptyLinePlaceholder":1306},[643,7724,7725,7727,7729,7731,7733],{"class":645,"line":2268},[643,7726,497],{"class":726},[643,7728,7460],{"class":649},[643,7730,742],{"class":726},[643,7732,4729],{"class":735},[643,7734,801],{"class":726},[643,7736,7737,7739,7741,7743,7745,7747],{"class":645,"line":2302},[643,7738,4466],{"class":4316},[643,7740,742],{"class":726},[643,7742,4438],{"class":666},[643,7744,1039],{"class":726},[643,7746,4475],{"class":1038},[643,7748,4428],{"class":726},[643,7750,7751,7753,7755,7757,7759,7761],{"class":645,"line":2321},[643,7752,4530],{"class":4316},[643,7754,742],{"class":726},[643,7756,4438],{"class":666},[643,7758,1039],{"class":726},[643,7760,4576],{"class":1038},[643,7762,4428],{"class":726},[643,7764,7765],{"class":645,"line":2326},[643,7766,957],{"class":726},[643,7768,7769],{"class":645,"line":2331},[643,7770,1307],{"emptyLinePlaceholder":1306},[643,7772,7773,7775,7777,7779,7781],{"class":645,"line":2336},[643,7774,497],{"class":726},[643,7776,7460],{"class":649},[643,7778,497],{"class":726},[643,7780,1474],{"class":649},[643,7782,801],{"class":726},[643,7784,7785,7787,7789,7791,7793,7795],{"class":645,"line":2342},[643,7786,4450],{"class":4316},[643,7788,742],{"class":726},[643,7790,4438],{"class":666},[643,7792,1039],{"class":726},[643,7794,4539],{"class":1038},[643,7796,4428],{"class":726},[643,7798,7799,7801,7803,7805],{"class":645,"line":2347},[643,7800,4466],{"class":4316},[643,7802,742],{"class":726},[643,7804,4675],{"class":1038},[643,7806,4325],{"class":726},[643,7808,7809,7811,7813,7815,7817,7819],{"class":645,"line":2352},[643,7810,4530],{"class":4316},[643,7812,742],{"class":726},[643,7814,4438],{"class":666},[643,7816,1039],{"class":726},[643,7818,4539],{"class":1038},[643,7820,4428],{"class":726},[643,7822,7823],{"class":645,"line":2357},[643,7824,957],{"class":726},[643,7826,7827,7829,7831],{"class":645,"line":2363},[643,7828,4062],{"class":726},[643,7830,4298],{"class":1046},[643,7832,3858],{"class":726},[466,7834,1490],{"id":7835},"giải-thích-chi-tiết-3",[432,7837,7838],{},[436,7839,7840,742],{},[487,7841,7842],{},"defineModel\u003CTodoFilter>({ required: true })",[471,7844,7845,7855,7879,7952],{},[474,7846,7847,7848,7851,7852,7854],{},"Giới thiệu từ Vue 3.4 — là ",[436,7849,7850],{},"syntactic sugar"," cho ",[487,7853,545],{}," trong component.",[474,7856,7857,7858],{},"Tự động tạo:\n",[471,7859,7860,7869],{},[474,7861,7862,7863,7866,7867,977],{},"Prop ",[487,7864,7865],{},"modelValue"," (kiểu ",[487,7868,1871],{},[474,7870,7871,7872,7875,7876,977],{},"Emit ",[487,7873,7874],{},"update:modelValue"," (tự động gọi khi gán ",[487,7877,7878],{},"filter = ...",[474,7880,7881,7882],{},"Trước Vue 3.4, bạn phải viết:\n",[616,7883,7885],{"className":1014,"code":7884,"language":1016,"meta":624,"style":624},"const props = defineProps\u003C{ modelValue: TodoFilter }>()\nconst emit = defineEmits\u003C{ 'update:modelValue': [value: TodoFilter] }>()\n\u002F\u002F Gán: emit('update:modelValue', newValue)\n",[487,7886,7887,7912,7947],{"__ignoreMap":624},[643,7888,7889,7891,7893,7895,7897,7900,7903,7905,7907,7910],{"class":645,"line":646},[643,7890,1561],{"class":735},[643,7892,5150],{"class":1038},[643,7894,1567],{"class":726},[643,7896,5155],{"class":666},[643,7898,7899],{"class":726},"\u003C{",[643,7901,7902],{"class":1046}," modelValue",[643,7904,742],{"class":726},[643,7906,1456],{"class":649},[643,7908,7909],{"class":726}," }>",[643,7911,1578],{"class":1038},[643,7913,7914,7916,7918,7920,7922,7924,7926,7928,7930,7932,7934,7936,7938,7940,7943,7945],{"class":645,"line":663},[643,7915,1561],{"class":735},[643,7917,5191],{"class":1038},[643,7919,1567],{"class":726},[643,7921,5196],{"class":666},[643,7923,7899],{"class":726},[643,7925,1052],{"class":726},[643,7927,7874],{"class":653},[643,7929,1058],{"class":726},[643,7931,742],{"class":726},[643,7933,1104],{"class":1038},[643,7935,2056],{"class":649},[643,7937,742],{"class":726},[643,7939,1456],{"class":649},[643,7941,7942],{"class":1038},"] ",[643,7944,5173],{"class":726},[643,7946,1578],{"class":1038},[643,7948,7949],{"class":645,"line":672},[643,7950,7951],{"class":1023},"\u002F\u002F Gán: emit('update:modelValue', newValue)\n",[474,7953,7954,7955,7957,7958,7960],{},"Với ",[487,7956,565],{},", chỉ cần gán trực tiếp ",[487,7959,7487],{}," — Vue tự động emit.",[449,7962,7963,7973],{"color":1220,"icon":1221},[432,7964,1222,7965],{},[436,7966,7967,1497,7969,4889,7971,742],{},[487,7968,565],{},[487,7970,553],{},[487,7972,557],{},[471,7974,7975,7980,7987],{},[474,7976,7977,7979],{},[487,7978,565],{}," = ít code hơn, sạch hơn cho v-model pattern.",[474,7981,7982,4889,7984,7986],{},[487,7983,553],{},[487,7985,557],{}," = linh hoạt hơn, nhiều emit event khác nhau.",[474,7988,7989,7990,7992,7993,497],{},"Với component chỉ cần 1 v-model, dùng ",[487,7991,565],{},". Với component cần nhiều emit event khác (toggle, remove, edit) — dùng ",[487,7994,557],{},[432,7996,7997],{},[436,7998,7999,742],{},[487,8000,8001],{},"v-for=\"f in filters\" :key=\"f.value\"",[471,8003,8004,8010,8022],{},[474,8005,8006,8009],{},[487,8007,8008],{},"v-for"," lặp qua array để render danh sách filter button.",[474,8011,8012,485,8015,8018,8019,8021],{},[487,8013,8014],{},":key",[436,8016,8017],{},"bắt buộc"," khi dùng ",[487,8020,8008],{},". Giúp Virtual DOM có thể track identity của từng element → tối ưu re-render.",[474,8023,8024,8025,977],{},"Tại sao không dùng index làm key? Khi array thay đổi thứ tự, index không còn trỏ đến đúng element → DOM update sai. Dùng unique value (",[487,8026,7447],{},[432,8028,8029],{},[436,8030,8031,742],{},[487,8032,8033],{},"@click=\"filter = f.value\"",[471,8035,8036,8039,8047],{},[474,8037,8038],{},"Khi click button, gán giá trị filter.",[474,8040,8041,8043,8044,8046],{},[487,8042,565],{}," tự động emit ",[487,8045,7874],{}," → parent nhận giá trị mới.",[474,8048,1598,8049,8052],{},[487,8050,8051],{},"emit('update:modelValue', f.value)"," — defineModel lo phần đó.",[626,8054],{},[466,8056,8058],{"id":8057},"_44-todolistvue-danh-sách-todo","4.4 TodoList.vue — Danh sách todo",[432,8060,1288,8061,742],{},[487,8062,8063],{},"app\u002Fcomponents\u002FTodoList.vue",[616,8065,8067],{"className":3830,"code":8066,"language":934,"meta":624,"style":624},"\u003Cscript setup lang=\"ts\">\n\u002F**\n * TodoList — component danh sách todo.\n *\n * Chức năng:\n * - Lọc todo theo filter (all\u002Factive\u002Fcompleted).\n * - Render list qua TodoItem.\n * - Hiển thị empty state khi không có todo.\n * - Footer: item count + clear completed.\n * - TransitionGroup animation khi thêm\u002Fxóa item.\n *\u002F\n\nconst {\n  todos,\n  filteredTodos,\n  activeCount,\n  completedCount,\n  toggleTodo,\n  removeTodo,\n  editTodo,\n  clearCompleted\n} = useTodos()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"todo-list-wrapper\">\n    \u003C!-- Bộ lọc -->\n    \u003CTodoFilter v-model=\"filter\" \u002F>\n\n    \u003C!-- Empty state -->\n    \u003Cdiv\n      v-if=\"filteredTodos.length === 0\"\n      class=\"empty-state\"\n    >\n      \u003Cp v-if=\"todos.length === 0\">\n        No todos yet! Add one above.\n      \u003C\u002Fp>\n      \u003Cp v-else>\n        No {{ filter === 'active' ? 'active' : 'completed' }} todos.\n      \u003C\u002Fp>\n    \u003C\u002Fdiv>\n\n    \u003C!-- Danh sách todo với animation -->\n    \u003CTransitionGroup\n      v-else\n      name=\"todo-list\"\n      tag=\"ul\"\n      class=\"todo-list\"\n    >\n      \u003Cli\n        v-for=\"todo in filteredTodos\"\n        :key=\"todo.id\"\n      >\n        \u003CTodoItem\n          :todo=\"todo\"\n          @toggle=\"toggleTodo\"\n          @remove=\"removeTodo\"\n          @edit=\"editTodo\"\n        \u002F>\n      \u003C\u002Fli>\n    \u003C\u002FTransitionGroup>\n\n    \u003C!-- Footer -->\n    \u003Cfooter\n      v-if=\"todos.length > 0\"\n      class=\"todo-footer\"\n    >\n      \u003Cspan class=\"items-left\">\n        {{ activeCount }} {{ activeCount === 1 ? 'item' : 'items' }} left\n      \u003C\u002Fspan>\n\n      \u003Cbutton\n        v-if=\"completedCount > 0\"\n        class=\"btn-clear\"\n        @click=\"clearCompleted\"\n      >\n        Clear completed ({{ completedCount }})\n      \u003C\u002Fbutton>\n    \u003C\u002Ffooter>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n.todo-list-wrapper {\n  background: var(--color-bg);\n  border-radius: var(--radius);\n  box-shadow: var(--shadow);\n  overflow: hidden;\n}\n\n.todo-list {\n  list-style: none;\n  margin: 0;\n  padding: 0;\n}\n\n.empty-state {\n  padding: 48px 24px;\n  text-align: center;\n  color: var(--color-text-muted);\n  font-size: 16px;\n}\n\n.todo-footer {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  padding: 12px 16px;\n  font-size: 14px;\n  color: var(--color-text-muted);\n  border-top: 1px solid var(--color-border);\n}\n\n.items-left {\n  font-weight: 500;\n}\n\n.btn-clear {\n  padding: 4px 12px;\n  font-size: 14px;\n  border: none;\n  border-radius: var(--radius);\n  background: transparent;\n  color: var(--color-text-muted);\n  cursor: pointer;\n  transition: all 0.2s;\n}\n\n.btn-clear:hover {\n  background: var(--color-danger);\n  color: white;\n}\n\n\u002F* ============================================================\n   TransitionGroup animations — thêm\u002Fxóa todo item\n   ============================================================ *\u002F\n\n\u002F* Animation khi item enter (được thêm vào) *\u002F\n.todo-list-enter-active {\n  transition: all 0.4s ease;\n}\n\n\u002F* Trạng thái bắt đầu khi enter *\u002F\n.todo-list-enter-from {\n  opacity: 0;\n  transform: translateX(-20px);\n}\n\n\u002F* Animation khi item leave (bị xóa) *\u002F\n.todo-list-leave-active {\n  transition: all 0.3s ease;\n  position: absolute; \u002F* Tránh chiếm không gian khi đang leave *\u002F\n}\n\n\u002F* Trạng thái kết thúc khi leave *\u002F\n.todo-list-leave-to {\n  opacity: 0;\n  transform: translateX(30px);\n}\n\n\u002F* Move transition — khi các item còn lại tự động di chuyển lên *\u002F\n.todo-list-move {\n  transition: transform 0.3s ease;\n}\n\u003C\u002Fstyle>\n",[487,8068,8069,8089,8093,8098,8102,8107,8112,8117,8122,8127,8132,8136,8140,8146,8153,8160,8167,8174,8181,8188,8195,8200,8210,8218,8222,8230,8249,8254,8274,8278,8283,8289,8302,8315,8319,8340,8345,8354,8365,8370,8378,8386,8390,8395,8402,8406,8420,8433,8445,8449,8456,8470,8484,8489,8497,8510,8524,8538,8552,8557,8565,8573,8577,8582,8589,8602,8615,8619,8638,8643,8651,8655,8661,8675,8689,8703,8707,8712,8720,8729,8737,8745,8749,8759,8767,8781,8795,8811,8823,8827,8831,8839,8850,8861,8871,8875,8879,8887,8900,8911,8925,8935,8939,8943,8951,8961,8972,8982,8994,9004,9018,9037,9041,9045,9053,9063,9067,9071,9079,9091,9101,9111,9125,9135,9149,9159,9171,9175,9179,9191,9205,9215,9219,9223,9228,9233,9238,9242,9247,9256,9272,9276,9280,9285,9294,9304,9320,9324,9328,9333,9342,9357,9372,9376,9380,9385,9394,9404,9419,9423,9427,9432,9441,9455,9459],{"__ignoreMap":624},[643,8070,8071,8073,8075,8077,8079,8081,8083,8085,8087],{"class":645,"line":646},[643,8072,1801],{"class":726},[643,8074,3840],{"class":1046},[643,8076,3843],{"class":735},[643,8078,3846],{"class":735},[643,8080,1567],{"class":726},[643,8082,739],{"class":726},[643,8084,3853],{"class":653},[643,8086,739],{"class":726},[643,8088,3858],{"class":726},[643,8090,8091],{"class":645,"line":663},[643,8092,1312],{"class":1023},[643,8094,8095],{"class":645,"line":672},[643,8096,8097],{"class":1023}," * TodoList — component danh sách todo.\n",[643,8099,8100],{"class":645,"line":681},[643,8101,1693],{"class":1023},[643,8103,8104],{"class":645,"line":789},[643,8105,8106],{"class":1023}," * Chức năng:\n",[643,8108,8109],{"class":645,"line":804},[643,8110,8111],{"class":1023}," * - Lọc todo theo filter (all\u002Factive\u002Fcompleted).\n",[643,8113,8114],{"class":645,"line":826},[643,8115,8116],{"class":1023}," * - Render list qua TodoItem.\n",[643,8118,8119],{"class":645,"line":847},[643,8120,8121],{"class":1023}," * - Hiển thị empty state khi không có todo.\n",[643,8123,8124],{"class":645,"line":868},[643,8125,8126],{"class":1023}," * - Footer: item count + clear completed.\n",[643,8128,8129],{"class":645,"line":888},[643,8130,8131],{"class":1023}," * - TransitionGroup animation khi thêm\u002Fxóa item.\n",[643,8133,8134],{"class":645,"line":894},[643,8135,1327],{"class":1023},[643,8137,8138],{"class":645,"line":908},[643,8139,1307],{"emptyLinePlaceholder":1306},[643,8141,8142,8144],{"class":645,"line":929},[643,8143,1561],{"class":735},[643,8145,801],{"class":726},[643,8147,8148,8151],{"class":645,"line":948},[643,8149,8150],{"class":1038},"  todos",[643,8152,753],{"class":726},[643,8154,8155,8158],{"class":645,"line":954},[643,8156,8157],{"class":1038},"  filteredTodos",[643,8159,753],{"class":726},[643,8161,8162,8165],{"class":645,"line":1394},[643,8163,8164],{"class":1038},"  activeCount",[643,8166,753],{"class":726},[643,8168,8169,8172],{"class":645,"line":1399},[643,8170,8171],{"class":1038},"  completedCount",[643,8173,753],{"class":726},[643,8175,8176,8179],{"class":645,"line":1405},[643,8177,8178],{"class":1038},"  toggleTodo",[643,8180,753],{"class":726},[643,8182,8183,8186],{"class":645,"line":1416},[643,8184,8185],{"class":1038},"  removeTodo",[643,8187,753],{"class":726},[643,8189,8190,8193],{"class":645,"line":1421},[643,8191,8192],{"class":1038},"  editTodo",[643,8194,753],{"class":726},[643,8196,8197],{"class":645,"line":1426},[643,8198,8199],{"class":1038},"  clearCompleted\n",[643,8201,8202,8204,8206,8208],{"class":645,"line":1431},[643,8203,1214],{"class":726},[643,8205,1459],{"class":726},[643,8207,3939],{"class":666},[643,8209,1578],{"class":1038},[643,8211,8212,8214,8216],{"class":645,"line":1437},[643,8213,4062],{"class":726},[643,8215,3840],{"class":1046},[643,8217,3858],{"class":726},[643,8219,8220],{"class":645,"line":1443},[643,8221,1307],{"emptyLinePlaceholder":1306},[643,8223,8224,8226,8228],{"class":645,"line":1448},[643,8225,1801],{"class":726},[643,8227,4077],{"class":1046},[643,8229,3858],{"class":726},[643,8231,8232,8234,8236,8238,8240,8242,8245,8247],{"class":645,"line":1835},[643,8233,4084],{"class":726},[643,8235,6055],{"class":1046},[643,8237,7404],{"class":735},[643,8239,1567],{"class":726},[643,8241,739],{"class":726},[643,8243,8244],{"class":653},"todo-list-wrapper",[643,8246,739],{"class":726},[643,8248,3858],{"class":726},[643,8250,8251],{"class":645,"line":1840},[643,8252,8253],{"class":1023},"    \u003C!-- Bộ lọc -->\n",[643,8255,8256,8258,8260,8263,8265,8267,8269,8271],{"class":645,"line":1846},[643,8257,4125],{"class":726},[643,8259,1871],{"class":1046},[643,8261,8262],{"class":735}," v-model",[643,8264,1567],{"class":726},[643,8266,739],{"class":726},[643,8268,2402],{"class":653},[643,8270,739],{"class":726},[643,8272,8273],{"class":726}," \u002F>\n",[643,8275,8276],{"class":645,"line":1852},[643,8277,1307],{"emptyLinePlaceholder":1306},[643,8279,8280],{"class":645,"line":1857},[643,8281,8282],{"class":1023},"    \u003C!-- Empty state -->\n",[643,8284,8285,8287],{"class":645,"line":1899},[643,8286,4125],{"class":726},[643,8288,5670],{"class":1046},[643,8290,8291,8293,8295,8297,8300],{"class":645,"line":1904},[643,8292,5804],{"class":735},[643,8294,1567],{"class":726},[643,8296,739],{"class":726},[643,8298,8299],{"class":653},"filteredTodos.length === 0",[643,8301,885],{"class":726},[643,8303,8304,8306,8308,8310,8313],{"class":645,"line":1909},[643,8305,4160],{"class":735},[643,8307,1567],{"class":726},[643,8309,739],{"class":726},[643,8311,8312],{"class":653},"empty-state",[643,8314,885],{"class":726},[643,8316,8317],{"class":645,"line":1915},[643,8318,4254],{"class":726},[643,8320,8321,8324,8326,8329,8331,8333,8336,8338],{"class":645,"line":1920},[643,8322,8323],{"class":726},"      \u003C",[643,8325,432],{"class":1046},[643,8327,8328],{"class":735}," v-if",[643,8330,1567],{"class":726},[643,8332,739],{"class":726},[643,8334,8335],{"class":653},"todos.length === 0",[643,8337,739],{"class":726},[643,8339,3858],{"class":726},[643,8341,8342],{"class":645,"line":1925},[643,8343,8344],{"class":1038},"        No todos yet! Add one above.\n",[643,8346,8347,8350,8352],{"class":645,"line":1931},[643,8348,8349],{"class":726},"      \u003C\u002F",[643,8351,432],{"class":1046},[643,8353,3858],{"class":726},[643,8355,8356,8358,8360,8363],{"class":645,"line":1957},[643,8357,8323],{"class":726},[643,8359,432],{"class":1046},[643,8361,8362],{"class":735}," v-else",[643,8364,3858],{"class":726},[643,8366,8367],{"class":645,"line":1965},[643,8368,8369],{"class":1038},"        No {{ filter === 'active' ? 'active' : 'completed' }} todos.\n",[643,8371,8372,8374,8376],{"class":645,"line":1995},[643,8373,8349],{"class":726},[643,8375,432],{"class":1046},[643,8377,3858],{"class":726},[643,8379,8380,8382,8384],{"class":645,"line":2010},[643,8381,4264],{"class":726},[643,8383,6055],{"class":1046},[643,8385,3858],{"class":726},[643,8387,8388],{"class":645,"line":2016},[643,8389,1307],{"emptyLinePlaceholder":1306},[643,8391,8392],{"class":645,"line":2048},[643,8393,8394],{"class":1023},"    \u003C!-- Danh sách todo với animation -->\n",[643,8396,8397,8399],{"class":645,"line":2080},[643,8398,4125],{"class":726},[643,8400,8401],{"class":1046},"TransitionGroup\n",[643,8403,8404],{"class":645,"line":2090},[643,8405,5878],{"class":735},[643,8407,8408,8411,8413,8415,8418],{"class":645,"line":2115},[643,8409,8410],{"class":735},"      name",[643,8412,1567],{"class":726},[643,8414,739],{"class":726},[643,8416,8417],{"class":653},"todo-list",[643,8419,885],{"class":726},[643,8421,8422,8425,8427,8429,8431],{"class":645,"line":2124},[643,8423,8424],{"class":735},"      tag",[643,8426,1567],{"class":726},[643,8428,739],{"class":726},[643,8430,471],{"class":653},[643,8432,885],{"class":726},[643,8434,8435,8437,8439,8441,8443],{"class":645,"line":2130},[643,8436,4160],{"class":735},[643,8438,1567],{"class":726},[643,8440,739],{"class":726},[643,8442,8417],{"class":653},[643,8444,885],{"class":726},[643,8446,8447],{"class":645,"line":2141},[643,8448,4254],{"class":726},[643,8450,8451,8453],{"class":645,"line":2147},[643,8452,8323],{"class":726},[643,8454,8455],{"class":1046},"li\n",[643,8457,8458,8461,8463,8465,8468],{"class":645,"line":2169},[643,8459,8460],{"class":735},"        v-for",[643,8462,1567],{"class":726},[643,8464,739],{"class":726},[643,8466,8467],{"class":653},"todo in filteredTodos",[643,8469,885],{"class":726},[643,8471,8472,8475,8477,8479,8482],{"class":645,"line":2174},[643,8473,8474],{"class":735},"        :key",[643,8476,1567],{"class":726},[643,8478,739],{"class":726},[643,8480,8481],{"class":653},"todo.id",[643,8483,885],{"class":726},[643,8485,8486],{"class":645,"line":2179},[643,8487,8488],{"class":726},"      >\n",[643,8490,8491,8494],{"class":645,"line":2184},[643,8492,8493],{"class":726},"        \u003C",[643,8495,8496],{"class":1046},"TodoItem\n",[643,8498,8499,8502,8504,8506,8508],{"class":645,"line":2189},[643,8500,8501],{"class":735},"          :todo",[643,8503,1567],{"class":726},[643,8505,739],{"class":726},[643,8507,2071],{"class":653},[643,8509,885],{"class":726},[643,8511,8512,8515,8517,8519,8522],{"class":645,"line":2195},[643,8513,8514],{"class":735},"          @toggle",[643,8516,1567],{"class":726},[643,8518,739],{"class":726},[643,8520,8521],{"class":653},"toggleTodo",[643,8523,885],{"class":726},[643,8525,8526,8529,8531,8533,8536],{"class":645,"line":2200},[643,8527,8528],{"class":735},"          @remove",[643,8530,1567],{"class":726},[643,8532,739],{"class":726},[643,8534,8535],{"class":653},"removeTodo",[643,8537,885],{"class":726},[643,8539,8540,8543,8545,8547,8550],{"class":645,"line":2206},[643,8541,8542],{"class":735},"          @edit",[643,8544,1567],{"class":726},[643,8546,739],{"class":726},[643,8548,8549],{"class":653},"editTodo",[643,8551,885],{"class":726},[643,8553,8554],{"class":645,"line":2212},[643,8555,8556],{"class":726},"        \u002F>\n",[643,8558,8559,8561,8563],{"class":645,"line":2218},[643,8560,8349],{"class":726},[643,8562,474],{"class":1046},[643,8564,3858],{"class":726},[643,8566,8567,8569,8571],{"class":645,"line":2223},[643,8568,4264],{"class":726},[643,8570,584],{"class":1046},[643,8572,3858],{"class":726},[643,8574,8575],{"class":645,"line":2244},[643,8576,1307],{"emptyLinePlaceholder":1306},[643,8578,8579],{"class":645,"line":2268},[643,8580,8581],{"class":1023},"    \u003C!-- Footer -->\n",[643,8583,8584,8586],{"class":645,"line":2302},[643,8585,4125],{"class":726},[643,8587,8588],{"class":1046},"footer\n",[643,8590,8591,8593,8595,8597,8600],{"class":645,"line":2321},[643,8592,5804],{"class":735},[643,8594,1567],{"class":726},[643,8596,739],{"class":726},[643,8598,8599],{"class":653},"todos.length > 0",[643,8601,885],{"class":726},[643,8603,8604,8606,8608,8610,8613],{"class":645,"line":2326},[643,8605,4160],{"class":735},[643,8607,1567],{"class":726},[643,8609,739],{"class":726},[643,8611,8612],{"class":653},"todo-footer",[643,8614,885],{"class":726},[643,8616,8617],{"class":645,"line":2331},[643,8618,4254],{"class":726},[643,8620,8621,8623,8625,8627,8629,8631,8634,8636],{"class":645,"line":2336},[643,8622,8323],{"class":726},[643,8624,643],{"class":1046},[643,8626,7404],{"class":735},[643,8628,1567],{"class":726},[643,8630,739],{"class":726},[643,8632,8633],{"class":653},"items-left",[643,8635,739],{"class":726},[643,8637,3858],{"class":726},[643,8639,8640],{"class":645,"line":2342},[643,8641,8642],{"class":1038},"        {{ activeCount }} {{ activeCount === 1 ? 'item' : 'items' }} left\n",[643,8644,8645,8647,8649],{"class":645,"line":2347},[643,8646,8349],{"class":726},[643,8648,643],{"class":1046},[643,8650,3858],{"class":726},[643,8652,8653],{"class":645,"line":2352},[643,8654,1307],{"emptyLinePlaceholder":1306},[643,8656,8657,8659],{"class":645,"line":2357},[643,8658,8323],{"class":726},[643,8660,4209],{"class":1046},[643,8662,8663,8666,8668,8670,8673],{"class":645,"line":2363},[643,8664,8665],{"class":735},"        v-if",[643,8667,1567],{"class":726},[643,8669,739],{"class":726},[643,8671,8672],{"class":653},"completedCount > 0",[643,8674,885],{"class":726},[643,8676,8677,8680,8682,8684,8687],{"class":645,"line":2369},[643,8678,8679],{"class":735},"        class",[643,8681,1567],{"class":726},[643,8683,739],{"class":726},[643,8685,8686],{"class":653},"btn-clear",[643,8688,885],{"class":726},[643,8690,8691,8694,8696,8698,8701],{"class":645,"line":2374},[643,8692,8693],{"class":735},"        @click",[643,8695,1567],{"class":726},[643,8697,739],{"class":726},[643,8699,8700],{"class":653},"clearCompleted",[643,8702,885],{"class":726},[643,8704,8705],{"class":645,"line":2423},[643,8706,8488],{"class":726},[643,8708,8709],{"class":645,"line":2466},[643,8710,8711],{"class":1038},"        Clear completed ({{ completedCount }})\n",[643,8713,8714,8716,8718],{"class":645,"line":2471},[643,8715,8349],{"class":726},[643,8717,4267],{"class":1046},[643,8719,3858],{"class":726},[643,8721,8722,8724,8727],{"class":645,"line":2502},[643,8723,4264],{"class":726},[643,8725,8726],{"class":1046},"footer",[643,8728,3858],{"class":726},[643,8730,8731,8733,8735],{"class":645,"line":2532},[643,8732,4274],{"class":726},[643,8734,6055],{"class":1046},[643,8736,3858],{"class":726},[643,8738,8739,8741,8743],{"class":645,"line":2537},[643,8740,4062],{"class":726},[643,8742,4077],{"class":1046},[643,8744,3858],{"class":726},[643,8746,8747],{"class":645,"line":2542},[643,8748,1307],{"emptyLinePlaceholder":1306},[643,8750,8751,8753,8755,8757],{"class":645,"line":2548},[643,8752,1801],{"class":726},[643,8754,4298],{"class":1046},[643,8756,4301],{"class":735},[643,8758,3858],{"class":726},[643,8760,8761,8763,8765],{"class":645,"line":2554},[643,8762,497],{"class":726},[643,8764,8244],{"class":649},[643,8766,801],{"class":726},[643,8768,8769,8771,8773,8775,8777,8779],{"class":645,"line":2559},[643,8770,4450],{"class":4316},[643,8772,742],{"class":726},[643,8774,4438],{"class":666},[643,8776,1039],{"class":726},[643,8778,4459],{"class":1038},[643,8780,4428],{"class":726},[643,8782,8783,8785,8787,8789,8791,8793],{"class":645,"line":2579},[643,8784,4433],{"class":4316},[643,8786,742],{"class":726},[643,8788,4438],{"class":666},[643,8790,1039],{"class":726},[643,8792,4443],{"class":1038},[643,8794,4428],{"class":726},[643,8796,8797,8800,8802,8804,8806,8809],{"class":645,"line":2597},[643,8798,8799],{"class":4316},"  box-shadow",[643,8801,742],{"class":726},[643,8803,4438],{"class":666},[643,8805,1039],{"class":726},[643,8807,8808],{"class":1038},"--shadow",[643,8810,4428],{"class":726},[643,8812,8813,8816,8818,8821],{"class":645,"line":2612},[643,8814,8815],{"class":4316},"  overflow",[643,8817,742],{"class":726},[643,8819,8820],{"class":1038}," hidden",[643,8822,4325],{"class":726},[643,8824,8825],{"class":645,"line":2625},[643,8826,957],{"class":726},[643,8828,8829],{"class":645,"line":2638},[643,8830,1307],{"emptyLinePlaceholder":1306},[643,8832,8833,8835,8837],{"class":645,"line":2649},[643,8834,497],{"class":726},[643,8836,8417],{"class":649},[643,8838,801],{"class":726},[643,8840,8841,8844,8846,8848],{"class":645,"line":2657},[643,8842,8843],{"class":4316},"  list-style",[643,8845,742],{"class":726},[643,8847,4487],{"class":1038},[643,8849,4325],{"class":726},[643,8851,8852,8855,8857,8859],{"class":645,"line":2668},[643,8853,8854],{"class":4316},"  margin",[643,8856,742],{"class":726},[643,8858,6360],{"class":4335},[643,8860,4325],{"class":726},[643,8862,8863,8865,8867,8869],{"class":645,"line":2673},[643,8864,4383],{"class":4316},[643,8866,742],{"class":726},[643,8868,6360],{"class":4335},[643,8870,4325],{"class":726},[643,8872,8873],{"class":645,"line":2681},[643,8874,957],{"class":726},[643,8876,8877],{"class":645,"line":2686},[643,8878,1307],{"emptyLinePlaceholder":1306},[643,8880,8881,8883,8885],{"class":645,"line":2691},[643,8882,497],{"class":726},[643,8884,8312],{"class":649},[643,8886,801],{"class":726},[643,8888,8889,8891,8893,8896,8898],{"class":645,"line":2697},[643,8890,4383],{"class":4316},[643,8892,742],{"class":726},[643,8894,8895],{"class":4335}," 48px",[643,8897,4348],{"class":4335},[643,8899,4325],{"class":726},[643,8901,8902,8905,8907,8909],{"class":645,"line":2702},[643,8903,8904],{"class":4316},"  text-align",[643,8906,742],{"class":726},[643,8908,6107],{"class":1038},[643,8910,4325],{"class":726},[643,8912,8913,8915,8917,8919,8921,8923],{"class":645,"line":2707},[643,8914,4466],{"class":4316},[643,8916,742],{"class":726},[643,8918,4438],{"class":666},[643,8920,1039],{"class":726},[643,8922,4576],{"class":1038},[643,8924,4428],{"class":726},[643,8926,8927,8929,8931,8933],{"class":645,"line":2712},[643,8928,4398],{"class":4316},[643,8930,742],{"class":726},[643,8932,4391],{"class":4335},[643,8934,4325],{"class":726},[643,8936,8937],{"class":645,"line":2718},[643,8938,957],{"class":726},[643,8940,8941],{"class":645,"line":2724},[643,8942,1307],{"emptyLinePlaceholder":1306},[643,8944,8945,8947,8949],{"class":645,"line":2730},[643,8946,497],{"class":726},[643,8948,8612],{"class":649},[643,8950,801],{"class":726},[643,8952,8953,8955,8957,8959],{"class":645,"line":2736},[643,8954,4317],{"class":4316},[643,8956,742],{"class":726},[643,8958,4322],{"class":1038},[643,8960,4325],{"class":726},[643,8962,8963,8965,8967,8970],{"class":645,"line":2741},[643,8964,6611],{"class":4316},[643,8966,742],{"class":726},[643,8968,8969],{"class":1038}," space-between",[643,8971,4325],{"class":726},[643,8973,8974,8976,8978,8980],{"class":645,"line":2767},[643,8975,6102],{"class":4316},[643,8977,742],{"class":726},[643,8979,6107],{"class":1038},[643,8981,4325],{"class":726},[643,8983,8984,8986,8988,8990,8992],{"class":645,"line":2788},[643,8985,4383],{"class":4316},[643,8987,742],{"class":726},[643,8989,4388],{"class":4335},[643,8991,4391],{"class":4335},[643,8993,4325],{"class":726},[643,8995,8996,8998,9000,9002],{"class":645,"line":2810},[643,8997,4398],{"class":4316},[643,8999,742],{"class":726},[643,9001,7623],{"class":4335},[643,9003,4325],{"class":726},[643,9005,9006,9008,9010,9012,9014,9016],{"class":645,"line":2815},[643,9007,4466],{"class":4316},[643,9009,742],{"class":726},[643,9011,4438],{"class":666},[643,9013,1039],{"class":726},[643,9015,4576],{"class":1038},[643,9017,4428],{"class":726},[643,9019,9020,9023,9025,9027,9029,9031,9033,9035],{"class":645,"line":2831},[643,9021,9022],{"class":4316},"  border-top",[643,9024,742],{"class":726},[643,9026,6141],{"class":4335},[643,9028,4417],{"class":1038},[643,9030,4420],{"class":666},[643,9032,1039],{"class":726},[643,9034,4425],{"class":1038},[643,9036,4428],{"class":726},[643,9038,9039],{"class":645,"line":2849},[643,9040,957],{"class":726},[643,9042,9043],{"class":645,"line":2860},[643,9044,1307],{"emptyLinePlaceholder":1306},[643,9046,9047,9049,9051],{"class":645,"line":2873},[643,9048,497],{"class":726},[643,9050,8633],{"class":649},[643,9052,801],{"class":726},[643,9054,9055,9057,9059,9061],{"class":645,"line":2887},[643,9056,4621],{"class":4316},[643,9058,742],{"class":726},[643,9060,7634],{"class":4335},[643,9062,4325],{"class":726},[643,9064,9065],{"class":645,"line":2892},[643,9066,957],{"class":726},[643,9068,9069],{"class":645,"line":2913},[643,9070,1307],{"emptyLinePlaceholder":1306},[643,9072,9073,9075,9077],{"class":645,"line":2918},[643,9074,497],{"class":726},[643,9076,8686],{"class":649},[643,9078,801],{"class":726},[643,9080,9081,9083,9085,9087,9089],{"class":645,"line":2923},[643,9082,4383],{"class":4316},[643,9084,742],{"class":726},[643,9086,7563],{"class":4335},[643,9088,4388],{"class":4335},[643,9090,4325],{"class":726},[643,9092,9093,9095,9097,9099],{"class":645,"line":2928},[643,9094,4398],{"class":4316},[643,9096,742],{"class":726},[643,9098,7623],{"class":4335},[643,9100,4325],{"class":726},[643,9102,9103,9105,9107,9109],{"class":645,"line":2934},[643,9104,4409],{"class":4316},[643,9106,742],{"class":726},[643,9108,4487],{"class":1038},[643,9110,4325],{"class":726},[643,9112,9113,9115,9117,9119,9121,9123],{"class":645,"line":2940},[643,9114,4433],{"class":4316},[643,9116,742],{"class":726},[643,9118,4438],{"class":666},[643,9120,1039],{"class":726},[643,9122,4443],{"class":1038},[643,9124,4428],{"class":726},[643,9126,9127,9129,9131,9133],{"class":645,"line":2945},[643,9128,4450],{"class":4316},[643,9130,742],{"class":726},[643,9132,6668],{"class":1038},[643,9134,4325],{"class":726},[643,9136,9137,9139,9141,9143,9145,9147],{"class":645,"line":2970},[643,9138,4466],{"class":4316},[643,9140,742],{"class":726},[643,9142,4438],{"class":666},[643,9144,1039],{"class":726},[643,9146,4576],{"class":1038},[643,9148,4428],{"class":726},[643,9150,9151,9153,9155,9157],{"class":645,"line":3010},[643,9152,4682],{"class":4316},[643,9154,742],{"class":726},[643,9156,4687],{"class":1038},[643,9158,4325],{"class":726},[643,9160,9161,9163,9165,9167,9169],{"class":645,"line":3023},[643,9162,4494],{"class":4316},[643,9164,742],{"class":726},[643,9166,6703],{"class":1038},[643,9168,4502],{"class":4335},[643,9170,4325],{"class":726},[643,9172,9173],{"class":645,"line":3044},[643,9174,957],{"class":726},[643,9176,9177],{"class":645,"line":3049},[643,9178,1307],{"emptyLinePlaceholder":1306},[643,9180,9181,9183,9185,9187,9189],{"class":645,"line":3054},[643,9182,497],{"class":726},[643,9184,8686],{"class":649},[643,9186,742],{"class":726},[643,9188,4729],{"class":735},[643,9190,801],{"class":726},[643,9192,9193,9195,9197,9199,9201,9203],{"class":645,"line":3059},[643,9194,4450],{"class":4316},[643,9196,742],{"class":726},[643,9198,4438],{"class":666},[643,9200,1039],{"class":726},[643,9202,6795],{"class":1038},[643,9204,4428],{"class":726},[643,9206,9207,9209,9211,9213],{"class":645,"line":3064},[643,9208,4466],{"class":4316},[643,9210,742],{"class":726},[643,9212,4675],{"class":1038},[643,9214,4325],{"class":726},[643,9216,9217],{"class":645,"line":3070},[643,9218,957],{"class":726},[643,9220,9221],{"class":645,"line":3076},[643,9222,1307],{"emptyLinePlaceholder":1306},[643,9224,9225],{"class":645,"line":3082},[643,9226,9227],{"class":1023},"\u002F* ============================================================\n",[643,9229,9230],{"class":645,"line":3087},[643,9231,9232],{"class":1023},"   TransitionGroup animations — thêm\u002Fxóa todo item\n",[643,9234,9235],{"class":645,"line":3111},[643,9236,9237],{"class":1023},"   ============================================================ *\u002F\n",[643,9239,9240],{"class":645,"line":3151},[643,9241,1307],{"emptyLinePlaceholder":1306},[643,9243,9244],{"class":645,"line":3156},[643,9245,9246],{"class":1023},"\u002F* Animation khi item enter (được thêm vào) *\u002F\n",[643,9248,9249,9251,9254],{"class":645,"line":3161},[643,9250,497],{"class":726},[643,9252,9253],{"class":649},"todo-list-enter-active",[643,9255,801],{"class":726},[643,9257,9258,9260,9262,9264,9267,9270],{"class":645,"line":3166},[643,9259,4494],{"class":4316},[643,9261,742],{"class":726},[643,9263,6703],{"class":1038},[643,9265,9266],{"class":4335},"0.4s",[643,9268,9269],{"class":1038}," ease",[643,9271,4325],{"class":726},[643,9273,9274],{"class":645,"line":3172},[643,9275,957],{"class":726},[643,9277,9278],{"class":645,"line":3178},[643,9279,1307],{"emptyLinePlaceholder":1306},[643,9281,9282],{"class":645,"line":3183},[643,9283,9284],{"class":1023},"\u002F* Trạng thái bắt đầu khi enter *\u002F\n",[643,9286,9287,9289,9292],{"class":645,"line":3215},[643,9288,497],{"class":726},[643,9290,9291],{"class":649},"todo-list-enter-from",[643,9293,801],{"class":726},[643,9295,9296,9298,9300,9302],{"class":645,"line":3232},[643,9297,4749],{"class":4316},[643,9299,742],{"class":726},[643,9301,6360],{"class":4335},[643,9303,4325],{"class":726},[643,9305,9306,9308,9310,9313,9315,9318],{"class":645,"line":3247},[643,9307,4791],{"class":4316},[643,9309,742],{"class":726},[643,9311,9312],{"class":666}," translateX",[643,9314,1039],{"class":726},[643,9316,9317],{"class":4335},"-20px",[643,9319,4428],{"class":726},[643,9321,9322],{"class":645,"line":3253},[643,9323,957],{"class":726},[643,9325,9326],{"class":645,"line":3265},[643,9327,1307],{"emptyLinePlaceholder":1306},[643,9329,9330],{"class":645,"line":3271},[643,9331,9332],{"class":1023},"\u002F* Animation khi item leave (bị xóa) *\u002F\n",[643,9334,9335,9337,9340],{"class":645,"line":3276},[643,9336,497],{"class":726},[643,9338,9339],{"class":649},"todo-list-leave-active",[643,9341,801],{"class":726},[643,9343,9344,9346,9348,9350,9353,9355],{"class":645,"line":3281},[643,9345,4494],{"class":4316},[643,9347,742],{"class":726},[643,9349,6703],{"class":1038},[643,9351,9352],{"class":4335},"0.3s",[643,9354,9269],{"class":1038},[643,9356,4325],{"class":726},[643,9358,9359,9362,9364,9367,9369],{"class":645,"line":3318},[643,9360,9361],{"class":4316},"  position",[643,9363,742],{"class":726},[643,9365,9366],{"class":1038}," absolute",[643,9368,7268],{"class":726},[643,9370,9371],{"class":1023}," \u002F* Tránh chiếm không gian khi đang leave *\u002F\n",[643,9373,9374],{"class":645,"line":3331},[643,9375,957],{"class":726},[643,9377,9378],{"class":645,"line":3345},[643,9379,1307],{"emptyLinePlaceholder":1306},[643,9381,9382],{"class":645,"line":3350},[643,9383,9384],{"class":1023},"\u002F* Trạng thái kết thúc khi leave *\u002F\n",[643,9386,9387,9389,9392],{"class":645,"line":3355},[643,9388,497],{"class":726},[643,9390,9391],{"class":649},"todo-list-leave-to",[643,9393,801],{"class":726},[643,9395,9396,9398,9400,9402],{"class":645,"line":3360},[643,9397,4749],{"class":4316},[643,9399,742],{"class":726},[643,9401,6360],{"class":4335},[643,9403,4325],{"class":726},[643,9405,9406,9408,9410,9412,9414,9417],{"class":645,"line":3365},[643,9407,4791],{"class":4316},[643,9409,742],{"class":726},[643,9411,9312],{"class":666},[643,9413,1039],{"class":726},[643,9415,9416],{"class":4335},"30px",[643,9418,4428],{"class":726},[643,9420,9421],{"class":645,"line":3371},[643,9422,957],{"class":726},[643,9424,9425],{"class":645,"line":3377},[643,9426,1307],{"emptyLinePlaceholder":1306},[643,9428,9429],{"class":645,"line":3382},[643,9430,9431],{"class":1023},"\u002F* Move transition — khi các item còn lại tự động di chuyển lên *\u002F\n",[643,9433,9434,9436,9439],{"class":645,"line":3398},[643,9435,497],{"class":726},[643,9437,9438],{"class":649},"todo-list-move",[643,9440,801],{"class":726},[643,9442,9443,9445,9447,9449,9451,9453],{"class":645,"line":3435},[643,9444,4494],{"class":4316},[643,9446,742],{"class":726},[643,9448,4705],{"class":1038},[643,9450,9352],{"class":4335},[643,9452,9269],{"class":1038},[643,9454,4325],{"class":726},[643,9456,9457],{"class":645,"line":3440},[643,9458,957],{"class":726},[643,9460,9461,9463,9465],{"class":645,"line":3445},[643,9462,4062],{"class":726},[643,9464,4298],{"class":1046},[643,9466,3858],{"class":726},[466,9468,1490],{"id":9469},"giải-thích-chi-tiết-4",[432,9471,9472],{},[436,9473,9474,742],{},[487,9475,9476],{},"v-if=\"filteredTodos.length === 0\"",[471,9478,9479,9482,9485],{},[474,9480,9481],{},"Hiển thị empty state khi không có todo nào thỏa filter.",[474,9483,9484],{},"Hai empty state khác nhau: \"Chưa có todo nào\" vs \"Không có todo active\u002Fcompleted\".",[474,9486,9487,9488,9490],{},"Logic: ",[487,9489,8335],{}," → chưa có todo nào. Ngược lại → có todo nhưng không match filter.",[432,9492,9493],{},[436,9494,9495,742],{},[487,9496,9497],{},"v-for=\"todo in filteredTodos\" :key=\"todo.id\"",[471,9499,9500,9509,9512,9515],{},[474,9501,1222,9502,9508],{},[436,9503,9504,9507],{},[487,9505,9506],{},"key"," là bắt buộc"," — không có key, Vue sẽ gặp lỗi hoặc hành vi không đoán trước.",[474,9510,9511],{},"Key giúp Vue track identity của từng element trong Virtual DOM.",[474,9513,9514],{},"Khi array thay đổi (thêm\u002Fxóa\u002Ffilter), Vue chỉ re-render element có key thay đổi.",[474,9516,9517,485,9520,9522,9523,497],{},[436,9518,9519],{},"Luôn dùng unique, stable identifier",[487,9521,8481],{}," là hoàn hảo. Không dùng ",[487,9524,9525],{},"index",[432,9527,9528,742],{},[436,9529,9530],{},[487,9531,9532],{},"\u003CTransitionGroup>",[471,9534,9535,9542,9548,9579],{},[474,9536,9537,9538,9541],{},"Là component Vue built-in để animate ",[436,9539,9540],{},"list"," — khi item được thêm, xóa, hoặc di chuyển.",[474,9543,6904,9544,9547],{},[487,9545,9546],{},"\u003CTransition>"," — chỉ animate 1 element.",[474,9549,9550,9553,9554],{},[487,9551,9552],{},"name=\"todo-list\""," — prefix cho CSS class transition. Vue tự động add\u002Fremove các class:\n",[471,9555,9556,9565,9574],{},[474,9557,9558,490,9560,490,9562,497],{},[487,9559,9291],{},[487,9561,9253],{},[487,9563,9564],{},"todo-list-enter-to",[474,9566,9567,490,9570,490,9572,497],{},[487,9568,9569],{},"todo-list-leave-from",[487,9571,9339],{},[487,9573,9391],{},[474,9575,9576,9578],{},[487,9577,9438],{}," — khi các item còn lại di chuyển để lấp khoảng trống.",[474,9580,9581,9584,9585,9588,9589,977],{},[487,9582,9583],{},"tag=\"ul\""," — render thành ",[487,9586,9587],{},"\u003Cul>"," element (mặc định là ",[487,9590,9591],{},"\u003Cspan>",[449,9593,9594,9599],{"color":1220,"icon":1221},[432,9595,1222,9596],{},[436,9597,9598],{},"TransitionGroup CSS các class cần biết:",[9600,9601,9602,9615],"table",{},[9603,9604,9605],"thead",{},[9606,9607,9608,9612],"tr",{},[9609,9610,9611],"th",{},"Class",[9609,9613,9614],{},"Thời điểm",[9616,9617,9618,9629,9643,9653,9663,9675,9685],"tbody",{},[9606,9619,9620,9626],{},[9621,9622,9623],"td",{},[487,9624,9625],{},"*-enter-from",[9621,9627,9628],{},"Trạng thái bắt đầu khi element được thêm (frame 0)",[9606,9630,9631,9636],{},[9621,9632,9633],{},[487,9634,9635],{},"*-enter-active",[9621,9637,9638,9639,9642],{},"Trong suốt quá trình enter — đặt ",[487,9640,9641],{},"transition"," ở đây",[9606,9644,9645,9650],{},[9621,9646,9647],{},[487,9648,9649],{},"*-enter-to",[9621,9651,9652],{},"Trạng thái kết thúc enter — remove sau khi xong",[9606,9654,9655,9660],{},[9621,9656,9657],{},[487,9658,9659],{},"*-leave-from",[9621,9661,9662],{},"Trạng thái bắt đầu khi bị xóa",[9606,9664,9665,9670],{},[9621,9666,9667],{},[487,9668,9669],{},"*-leave-active",[9621,9671,9672,9673,9642],{},"Trong suốt quá trình leave — đặt ",[487,9674,9641],{},[9606,9676,9677,9682],{},[9621,9678,9679],{},[487,9680,9681],{},"*-leave-to",[9621,9683,9684],{},"Trạng thái kết thúc leave",[9606,9686,9687,9692],{},[9621,9688,9689],{},[487,9690,9691],{},"*-move",[9621,9693,9694,9695,9642],{},"Khi các element còn lại di chuyển — đặt ",[487,9696,9641],{},[432,9698,9699,742],{},[436,9700,9701],{},"Footer logic",[471,9703,9704,9710,9716],{},[474,9705,9706,9709],{},[487,9707,9708],{},"\u003Cfooter v-if=\"todos.length > 0\">"," — chỉ hiển thị footer khi có ít nhất 1 todo.",[474,9711,9712,9715],{},[487,9713,9714],{},"\u003Cspan>{{ activeCount }} items left\u003C\u002Fspan>"," — hiển thị số lượng todo chưa hoàn thành.",[474,9717,9718,9721],{},[487,9719,9720],{},"\u003Cbutton v-if=\"completedCount > 0\">"," — nút clear completed chỉ hiện khi có ít nhất 1 todo completed.",[626,9723],{},[427,9725,9727,9728],{"id":9726},"_5-pages-indexvue","5. Pages — ",[487,9729,9730],{},"index.vue",[432,9732,1288,9733,742],{},[487,9734,9735],{},"app\u002Fpages\u002Findex.vue",[616,9737,9739],{"className":3830,"code":9738,"language":934,"meta":624,"style":624},"\u003Cscript setup lang=\"ts\">\n\u002F**\n * index.vue — trang chủ của Todo App.\n *\n * File-based routing: `app\u002Fpages\u002Findex.vue` → route `\u002F`.\n * Không cần khai báo route — Nuxt tự detect.\n *\n * Sử dụng:\n * - useHead: set document head (title, meta).\n * - useSeoMeta: type-safe SEO meta tags.\n *\u002F\n\nuseHead({\n  title: 'Todo App — Nuxt 4',\n  htmlAttrs: { lang: 'en' },\n  link: [\n    { rel: 'icon', type: 'image\u002Fsvg+xml', href: '\u002Ffavicon.svg' }\n  ]\n})\n\nuseSeoMeta({\n  title: 'Todo App — Built with Nuxt 4',\n  description: 'A simple, beautiful todo app built with Nuxt 4. Features: add, edit, filter, localStorage persistence.',\n  ogTitle: 'Todo App — Nuxt 4',\n  ogDescription: 'A simple todo app built with Nuxt 4',\n  ogType: 'website',\n  twitterCard: 'summary'\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"todo-app-container\">\n    \u003Cheader class=\"app-header\">\n      \u003Ch1 class=\"app-title\">Todo App\u003C\u002Fh1>\n      \u003Cp class=\"app-subtitle\">Built with Nuxt 4\u003C\u002Fp>\n    \u003C\u002Fheader>\n\n    \u003Cmain class=\"app-main\">\n      \u003CTodoForm \u002F>\n      \u003CTodoList \u002F>\n    \u003C\u002Fmain>\n\n    \u003Cfooter class=\"app-footer\">\n      \u003Cp>\n        Double-click to edit a todo &middot;\n        Press Enter to save &middot;\n        Press Escape to cancel\n      \u003C\u002Fp>\n    \u003C\u002Ffooter>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n.todo-app-container {\n  max-width: 560px;\n  margin: 60px auto;\n  padding: 0 20px;\n}\n\n.app-header {\n  text-align: center;\n  margin-bottom: 32px;\n}\n\n.app-title {\n  font-size: 48px;\n  font-weight: 300;\n  color: var(--color-primary);\n  margin: 0 0 8px;\n  letter-spacing: -1px;\n}\n\n.app-subtitle {\n  font-size: 14px;\n  color: var(--color-text-muted);\n  margin: 0;\n}\n\n.app-main {\n  margin-bottom: 24px;\n}\n\n.app-footer {\n  text-align: center;\n  color: var(--color-text-muted);\n  font-size: 13px;\n  line-height: 1.6;\n}\n\u003C\u002Fstyle>\n",[487,9740,9741,9761,9765,9770,9774,9779,9784,9788,9792,9797,9802,9806,9810,9818,9833,9855,9864,9910,9915,9921,9925,9933,9948,9964,9979,9995,10011,10025,10031,10039,10043,10051,10070,10090,10118,10146,10154,10158,10178,10187,10196,10204,10208,10227,10235,10240,10245,10250,10258,10266,10274,10282,10286,10296,10304,10316,10330,10342,10346,10350,10358,10368,10378,10382,10386,10394,10404,10414,10428,10442,10454,10458,10462,10470,10480,10494,10504,10508,10512,10520,10530,10534,10538,10546,10556,10570,10581,10593,10597],{"__ignoreMap":624},[643,9742,9743,9745,9747,9749,9751,9753,9755,9757,9759],{"class":645,"line":646},[643,9744,1801],{"class":726},[643,9746,3840],{"class":1046},[643,9748,3843],{"class":735},[643,9750,3846],{"class":735},[643,9752,1567],{"class":726},[643,9754,739],{"class":726},[643,9756,3853],{"class":653},[643,9758,739],{"class":726},[643,9760,3858],{"class":726},[643,9762,9763],{"class":645,"line":663},[643,9764,1312],{"class":1023},[643,9766,9767],{"class":645,"line":672},[643,9768,9769],{"class":1023}," * index.vue — trang chủ của Todo App.\n",[643,9771,9772],{"class":645,"line":681},[643,9773,1693],{"class":1023},[643,9775,9776],{"class":645,"line":789},[643,9777,9778],{"class":1023}," * File-based routing: `app\u002Fpages\u002Findex.vue` → route `\u002F`.\n",[643,9780,9781],{"class":645,"line":804},[643,9782,9783],{"class":1023}," * Không cần khai báo route — Nuxt tự detect.\n",[643,9785,9786],{"class":645,"line":826},[643,9787,1693],{"class":1023},[643,9789,9790],{"class":645,"line":847},[643,9791,3876],{"class":1023},[643,9793,9794],{"class":645,"line":868},[643,9795,9796],{"class":1023}," * - useHead: set document head (title, meta).\n",[643,9798,9799],{"class":645,"line":888},[643,9800,9801],{"class":1023}," * - useSeoMeta: type-safe SEO meta tags.\n",[643,9803,9804],{"class":645,"line":894},[643,9805,1327],{"class":1023},[643,9807,9808],{"class":645,"line":908},[643,9809,1307],{"emptyLinePlaceholder":1306},[643,9811,9812,9814,9816],{"class":645,"line":929},[643,9813,592],{"class":666},[643,9815,1039],{"class":1038},[643,9817,727],{"class":726},[643,9819,9820,9822,9824,9826,9829,9831],{"class":645,"line":948},[643,9821,1368],{"class":1046},[643,9823,742],{"class":726},[643,9825,1052],{"class":726},[643,9827,9828],{"class":653},"Todo App — Nuxt 4",[643,9830,1058],{"class":726},[643,9832,753],{"class":726},[643,9834,9835,9838,9840,9842,9844,9846,9848,9851,9853],{"class":645,"line":954},[643,9836,9837],{"class":1046},"  htmlAttrs",[643,9839,742],{"class":726},[643,9841,1070],{"class":726},[643,9843,3846],{"class":1046},[643,9845,742],{"class":726},[643,9847,1052],{"class":726},[643,9849,9850],{"class":653},"en",[643,9852,1058],{"class":726},[643,9854,1082],{"class":726},[643,9856,9857,9860,9862],{"class":645,"line":1394},[643,9858,9859],{"class":1046},"  link",[643,9861,742],{"class":726},[643,9863,1160],{"class":1038},[643,9865,9866,9869,9872,9874,9876,9879,9881,9883,9885,9887,9889,9892,9894,9896,9899,9901,9903,9906,9908],{"class":645,"line":1399},[643,9867,9868],{"class":726},"    {",[643,9870,9871],{"class":1046}," rel",[643,9873,742],{"class":726},[643,9875,1052],{"class":726},[643,9877,9878],{"class":653},"icon",[643,9880,1058],{"class":726},[643,9882,1180],{"class":726},[643,9884,1453],{"class":1046},[643,9886,742],{"class":726},[643,9888,1052],{"class":726},[643,9890,9891],{"class":653},"image\u002Fsvg+xml",[643,9893,1058],{"class":726},[643,9895,1180],{"class":726},[643,9897,9898],{"class":1046}," href",[643,9900,742],{"class":726},[643,9902,1052],{"class":726},[643,9904,9905],{"class":653},"\u002Ffavicon.svg",[643,9907,1058],{"class":726},[643,9909,1195],{"class":726},[643,9911,9912],{"class":645,"line":1405},[643,9913,9914],{"class":1038},"  ]\n",[643,9916,9917,9919],{"class":645,"line":1416},[643,9918,1214],{"class":726},[643,9920,1217],{"class":1038},[643,9922,9923],{"class":645,"line":1421},[643,9924,1307],{"emptyLinePlaceholder":1306},[643,9926,9927,9929,9931],{"class":645,"line":1426},[643,9928,595],{"class":666},[643,9930,1039],{"class":1038},[643,9932,727],{"class":726},[643,9934,9935,9937,9939,9941,9944,9946],{"class":645,"line":1431},[643,9936,1368],{"class":1046},[643,9938,742],{"class":726},[643,9940,1052],{"class":726},[643,9942,9943],{"class":653},"Todo App — Built with Nuxt 4",[643,9945,1058],{"class":726},[643,9947,753],{"class":726},[643,9949,9950,9953,9955,9957,9960,9962],{"class":645,"line":1437},[643,9951,9952],{"class":1046},"  description",[643,9954,742],{"class":726},[643,9956,1052],{"class":726},[643,9958,9959],{"class":653},"A simple, beautiful todo app built with Nuxt 4. Features: add, edit, filter, localStorage persistence.",[643,9961,1058],{"class":726},[643,9963,753],{"class":726},[643,9965,9966,9969,9971,9973,9975,9977],{"class":645,"line":1443},[643,9967,9968],{"class":1046},"  ogTitle",[643,9970,742],{"class":726},[643,9972,1052],{"class":726},[643,9974,9828],{"class":653},[643,9976,1058],{"class":726},[643,9978,753],{"class":726},[643,9980,9981,9984,9986,9988,9991,9993],{"class":645,"line":1448},[643,9982,9983],{"class":1046},"  ogDescription",[643,9985,742],{"class":726},[643,9987,1052],{"class":726},[643,9989,9990],{"class":653},"A simple todo app built with Nuxt 4",[643,9992,1058],{"class":726},[643,9994,753],{"class":726},[643,9996,9997,10000,10002,10004,10007,10009],{"class":645,"line":1835},[643,9998,9999],{"class":1046},"  ogType",[643,10001,742],{"class":726},[643,10003,1052],{"class":726},[643,10005,10006],{"class":653},"website",[643,10008,1058],{"class":726},[643,10010,753],{"class":726},[643,10012,10013,10016,10018,10020,10023],{"class":645,"line":1840},[643,10014,10015],{"class":1046},"  twitterCard",[643,10017,742],{"class":726},[643,10019,1052],{"class":726},[643,10021,10022],{"class":653},"summary",[643,10024,1486],{"class":726},[643,10026,10027,10029],{"class":645,"line":1846},[643,10028,1214],{"class":726},[643,10030,1217],{"class":1038},[643,10032,10033,10035,10037],{"class":645,"line":1852},[643,10034,4062],{"class":726},[643,10036,3840],{"class":1046},[643,10038,3858],{"class":726},[643,10040,10041],{"class":645,"line":1857},[643,10042,1307],{"emptyLinePlaceholder":1306},[643,10044,10045,10047,10049],{"class":645,"line":1899},[643,10046,1801],{"class":726},[643,10048,4077],{"class":1046},[643,10050,3858],{"class":726},[643,10052,10053,10055,10057,10059,10061,10063,10066,10068],{"class":645,"line":1904},[643,10054,4084],{"class":726},[643,10056,6055],{"class":1046},[643,10058,7404],{"class":735},[643,10060,1567],{"class":726},[643,10062,739],{"class":726},[643,10064,10065],{"class":653},"todo-app-container",[643,10067,739],{"class":726},[643,10069,3858],{"class":726},[643,10071,10072,10074,10077,10079,10081,10083,10086,10088],{"class":645,"line":1909},[643,10073,4125],{"class":726},[643,10075,10076],{"class":1046},"header",[643,10078,7404],{"class":735},[643,10080,1567],{"class":726},[643,10082,739],{"class":726},[643,10084,10085],{"class":653},"app-header",[643,10087,739],{"class":726},[643,10089,3858],{"class":726},[643,10091,10092,10094,10097,10099,10101,10103,10106,10108,10110,10112,10114,10116],{"class":645,"line":1915},[643,10093,8323],{"class":726},[643,10095,10096],{"class":1046},"h1",[643,10098,7404],{"class":735},[643,10100,1567],{"class":726},[643,10102,739],{"class":726},[643,10104,10105],{"class":653},"app-title",[643,10107,739],{"class":726},[643,10109,1810],{"class":726},[643,10111,1146],{"class":1038},[643,10113,4062],{"class":726},[643,10115,10096],{"class":1046},[643,10117,3858],{"class":726},[643,10119,10120,10122,10124,10126,10128,10130,10133,10135,10137,10140,10142,10144],{"class":645,"line":1920},[643,10121,8323],{"class":726},[643,10123,432],{"class":1046},[643,10125,7404],{"class":735},[643,10127,1567],{"class":726},[643,10129,739],{"class":726},[643,10131,10132],{"class":653},"app-subtitle",[643,10134,739],{"class":726},[643,10136,1810],{"class":726},[643,10138,10139],{"class":1038},"Built with Nuxt 4",[643,10141,4062],{"class":726},[643,10143,432],{"class":1046},[643,10145,3858],{"class":726},[643,10147,10148,10150,10152],{"class":645,"line":1925},[643,10149,4264],{"class":726},[643,10151,10076],{"class":1046},[643,10153,3858],{"class":726},[643,10155,10156],{"class":645,"line":1931},[643,10157,1307],{"emptyLinePlaceholder":1306},[643,10159,10160,10162,10165,10167,10169,10171,10174,10176],{"class":645,"line":1957},[643,10161,4125],{"class":726},[643,10163,10164],{"class":1046},"main",[643,10166,7404],{"class":735},[643,10168,1567],{"class":726},[643,10170,739],{"class":726},[643,10172,10173],{"class":653},"app-main",[643,10175,739],{"class":726},[643,10177,3858],{"class":726},[643,10179,10180,10182,10185],{"class":645,"line":1965},[643,10181,8323],{"class":726},[643,10183,10184],{"class":1046},"TodoForm",[643,10186,8273],{"class":726},[643,10188,10189,10191,10194],{"class":645,"line":1995},[643,10190,8323],{"class":726},[643,10192,10193],{"class":1046},"TodoList",[643,10195,8273],{"class":726},[643,10197,10198,10200,10202],{"class":645,"line":2010},[643,10199,4264],{"class":726},[643,10201,10164],{"class":1046},[643,10203,3858],{"class":726},[643,10205,10206],{"class":645,"line":2016},[643,10207,1307],{"emptyLinePlaceholder":1306},[643,10209,10210,10212,10214,10216,10218,10220,10223,10225],{"class":645,"line":2048},[643,10211,4125],{"class":726},[643,10213,8726],{"class":1046},[643,10215,7404],{"class":735},[643,10217,1567],{"class":726},[643,10219,739],{"class":726},[643,10221,10222],{"class":653},"app-footer",[643,10224,739],{"class":726},[643,10226,3858],{"class":726},[643,10228,10229,10231,10233],{"class":645,"line":2080},[643,10230,8323],{"class":726},[643,10232,432],{"class":1046},[643,10234,3858],{"class":726},[643,10236,10237],{"class":645,"line":2090},[643,10238,10239],{"class":1038},"        Double-click to edit a todo &middot;\n",[643,10241,10242],{"class":645,"line":2115},[643,10243,10244],{"class":1038},"        Press Enter to save &middot;\n",[643,10246,10247],{"class":645,"line":2124},[643,10248,10249],{"class":1038},"        Press Escape to cancel\n",[643,10251,10252,10254,10256],{"class":645,"line":2130},[643,10253,8349],{"class":726},[643,10255,432],{"class":1046},[643,10257,3858],{"class":726},[643,10259,10260,10262,10264],{"class":645,"line":2141},[643,10261,4264],{"class":726},[643,10263,8726],{"class":1046},[643,10265,3858],{"class":726},[643,10267,10268,10270,10272],{"class":645,"line":2147},[643,10269,4274],{"class":726},[643,10271,6055],{"class":1046},[643,10273,3858],{"class":726},[643,10275,10276,10278,10280],{"class":645,"line":2169},[643,10277,4062],{"class":726},[643,10279,4077],{"class":1046},[643,10281,3858],{"class":726},[643,10283,10284],{"class":645,"line":2174},[643,10285,1307],{"emptyLinePlaceholder":1306},[643,10287,10288,10290,10292,10294],{"class":645,"line":2179},[643,10289,1801],{"class":726},[643,10291,4298],{"class":1046},[643,10293,4301],{"class":735},[643,10295,3858],{"class":726},[643,10297,10298,10300,10302],{"class":645,"line":2184},[643,10299,497],{"class":726},[643,10301,10065],{"class":649},[643,10303,801],{"class":726},[643,10305,10306,10309,10311,10314],{"class":645,"line":2189},[643,10307,10308],{"class":4316},"  max-width",[643,10310,742],{"class":726},[643,10312,10313],{"class":4335}," 560px",[643,10315,4325],{"class":726},[643,10317,10318,10320,10322,10325,10328],{"class":645,"line":2195},[643,10319,8854],{"class":4316},[643,10321,742],{"class":726},[643,10323,10324],{"class":4335}," 60px",[643,10326,10327],{"class":1038}," auto",[643,10329,4325],{"class":726},[643,10331,10332,10334,10336,10338,10340],{"class":645,"line":2200},[643,10333,4383],{"class":4316},[643,10335,742],{"class":726},[643,10337,6360],{"class":4335},[643,10339,6312],{"class":4335},[643,10341,4325],{"class":726},[643,10343,10344],{"class":645,"line":2206},[643,10345,957],{"class":726},[643,10347,10348],{"class":645,"line":2212},[643,10349,1307],{"emptyLinePlaceholder":1306},[643,10351,10352,10354,10356],{"class":645,"line":2218},[643,10353,497],{"class":726},[643,10355,10085],{"class":649},[643,10357,801],{"class":726},[643,10359,10360,10362,10364,10366],{"class":645,"line":2223},[643,10361,8904],{"class":4316},[643,10363,742],{"class":726},[643,10365,6107],{"class":1038},[643,10367,4325],{"class":726},[643,10369,10370,10372,10374,10376],{"class":645,"line":2244},[643,10371,4343],{"class":4316},[643,10373,742],{"class":726},[643,10375,6574],{"class":4335},[643,10377,4325],{"class":726},[643,10379,10380],{"class":645,"line":2268},[643,10381,957],{"class":726},[643,10383,10384],{"class":645,"line":2302},[643,10385,1307],{"emptyLinePlaceholder":1306},[643,10387,10388,10390,10392],{"class":645,"line":2321},[643,10389,497],{"class":726},[643,10391,10105],{"class":649},[643,10393,801],{"class":726},[643,10395,10396,10398,10400,10402],{"class":645,"line":2326},[643,10397,4398],{"class":4316},[643,10399,742],{"class":726},[643,10401,8895],{"class":4335},[643,10403,4325],{"class":726},[643,10405,10406,10408,10410,10412],{"class":645,"line":2331},[643,10407,4621],{"class":4316},[643,10409,742],{"class":726},[643,10411,6636],{"class":4335},[643,10413,4325],{"class":726},[643,10415,10416,10418,10420,10422,10424,10426],{"class":645,"line":2336},[643,10417,4466],{"class":4316},[643,10419,742],{"class":726},[643,10421,4438],{"class":666},[643,10423,1039],{"class":726},[643,10425,4539],{"class":1038},[643,10427,4428],{"class":726},[643,10429,10430,10432,10434,10436,10438,10440],{"class":645,"line":2342},[643,10431,8854],{"class":4316},[643,10433,742],{"class":726},[643,10435,6360],{"class":4335},[643,10437,6360],{"class":4335},[643,10439,4336],{"class":4335},[643,10441,4325],{"class":726},[643,10443,10444,10447,10449,10452],{"class":645,"line":2347},[643,10445,10446],{"class":4316},"  letter-spacing",[643,10448,742],{"class":726},[643,10450,10451],{"class":4335}," -1px",[643,10453,4325],{"class":726},[643,10455,10456],{"class":645,"line":2352},[643,10457,957],{"class":726},[643,10459,10460],{"class":645,"line":2357},[643,10461,1307],{"emptyLinePlaceholder":1306},[643,10463,10464,10466,10468],{"class":645,"line":2363},[643,10465,497],{"class":726},[643,10467,10132],{"class":649},[643,10469,801],{"class":726},[643,10471,10472,10474,10476,10478],{"class":645,"line":2369},[643,10473,4398],{"class":4316},[643,10475,742],{"class":726},[643,10477,7623],{"class":4335},[643,10479,4325],{"class":726},[643,10481,10482,10484,10486,10488,10490,10492],{"class":645,"line":2374},[643,10483,4466],{"class":4316},[643,10485,742],{"class":726},[643,10487,4438],{"class":666},[643,10489,1039],{"class":726},[643,10491,4576],{"class":1038},[643,10493,4428],{"class":726},[643,10495,10496,10498,10500,10502],{"class":645,"line":2423},[643,10497,8854],{"class":4316},[643,10499,742],{"class":726},[643,10501,6360],{"class":4335},[643,10503,4325],{"class":726},[643,10505,10506],{"class":645,"line":2466},[643,10507,957],{"class":726},[643,10509,10510],{"class":645,"line":2471},[643,10511,1307],{"emptyLinePlaceholder":1306},[643,10513,10514,10516,10518],{"class":645,"line":2502},[643,10515,497],{"class":726},[643,10517,10173],{"class":649},[643,10519,801],{"class":726},[643,10521,10522,10524,10526,10528],{"class":645,"line":2532},[643,10523,4343],{"class":4316},[643,10525,742],{"class":726},[643,10527,4348],{"class":4335},[643,10529,4325],{"class":726},[643,10531,10532],{"class":645,"line":2537},[643,10533,957],{"class":726},[643,10535,10536],{"class":645,"line":2542},[643,10537,1307],{"emptyLinePlaceholder":1306},[643,10539,10540,10542,10544],{"class":645,"line":2548},[643,10541,497],{"class":726},[643,10543,10222],{"class":649},[643,10545,801],{"class":726},[643,10547,10548,10550,10552,10554],{"class":645,"line":2554},[643,10549,8904],{"class":4316},[643,10551,742],{"class":726},[643,10553,6107],{"class":1038},[643,10555,4325],{"class":726},[643,10557,10558,10560,10562,10564,10566,10568],{"class":645,"line":2559},[643,10559,4466],{"class":4316},[643,10561,742],{"class":726},[643,10563,4438],{"class":666},[643,10565,1039],{"class":726},[643,10567,4576],{"class":1038},[643,10569,4428],{"class":726},[643,10571,10572,10574,10576,10579],{"class":645,"line":2579},[643,10573,4398],{"class":4316},[643,10575,742],{"class":726},[643,10577,10578],{"class":4335}," 13px",[643,10580,4325],{"class":726},[643,10582,10583,10586,10588,10591],{"class":645,"line":2597},[643,10584,10585],{"class":4316},"  line-height",[643,10587,742],{"class":726},[643,10589,10590],{"class":4335}," 1.6",[643,10592,4325],{"class":726},[643,10594,10595],{"class":645,"line":2612},[643,10596,957],{"class":726},[643,10598,10599,10601,10603],{"class":645,"line":2625},[643,10600,4062],{"class":726},[643,10602,4298],{"class":1046},[643,10604,3858],{"class":726},[466,10606,1490],{"id":10607},"giải-thích-chi-tiết-5",[432,10609,10610],{},[436,10611,10612,742],{},[487,10613,592],{},[471,10615,10616,10619,10622,10625],{},[474,10617,10618],{},"Nuxt composable để thiết lập document head (title, meta, link, script...).",[474,10620,10621],{},"Hoạt động cả SSR và client — server render head đúng, client hydrate.",[474,10623,10624],{},"Có thể dùng ở mọi component — các head sẽ merge lại.",[474,10626,10627,10630,10631,497],{},[487,10628,10629],{},"htmlAttrs: { lang: 'en' }"," — set ",[487,10632,10633],{},"\u003Chtml lang=\"en\">",[432,10635,10636],{},[436,10637,10638,742],{},[487,10639,595],{},[471,10641,10642,10645,10648,10695],{},[474,10643,10644],{},"Type-safe SEO meta tags — chỉ cho phép các tag SEO hợp lệ.",[474,10646,10647],{},"Tự động generate Open Graph, Twitter Card tags.",[474,10649,10650,10651],{},"Support reactive value (getter function):\n",[616,10652,10654],{"className":1014,"code":10653,"language":1016,"meta":624,"style":624},"useSeoMeta({ title: () => `Todo — ${dynamicTitle.value}` })\n",[487,10655,10656],{"__ignoreMap":624},[643,10657,10658,10660,10662,10664,10666,10668,10670,10672,10675,10678,10681,10684,10686,10688,10691,10693],{"class":645,"line":646},[643,10659,595],{"class":666},[643,10661,1039],{"class":1038},[643,10663,7227],{"class":726},[643,10665,2778],{"class":666},[643,10667,742],{"class":726},[643,10669,1722],{"class":726},[643,10671,1725],{"class":735},[643,10673,10674],{"class":726}," `",[643,10676,10677],{"class":653},"Todo — ",[643,10679,10680],{"class":726},"${",[643,10682,10683],{"class":1038},"dynamicTitle",[643,10685,497],{"class":726},[643,10687,2056],{"class":1038},[643,10689,10690],{"class":726},"}`",[643,10692,1665],{"class":726},[643,10694,1217],{"class":1038},[474,10696,10697,10698,10700],{},"Khác ",[487,10699,592],{}," ở chỗ type-safe — chỉ các meta tag liên quan SEO.",[432,10702,10703,742],{},[436,10704,10705],{},"File-based routing",[471,10707,10708,10715,10723,10731],{},[474,10709,10710,10712,10713,497],{},[487,10711,9735],{}," → route ",[487,10714,969],{},[474,10716,10717,10712,10720,497],{},[487,10718,10719],{},"app\u002Fpages\u002Fabout.vue",[487,10721,10722],{},"\u002Fabout",[474,10724,10725,10712,10728,497],{},[487,10726,10727],{},"app\u002Fpages\u002Fusers\u002F[id].vue",[487,10729,10730],{},"\u002Fusers\u002F:id",[474,10732,10733,1528,10736,10739],{},[436,10734,10735],{},"Không cần",[487,10737,10738],{},"vue-router"," config — Nuxt quản lý hoàn toàn.",[626,10741],{},[427,10743,10745,10746],{"id":10744},"_6-root-layout-appvue","6. Root Layout — ",[487,10747,10748],{},"app.vue",[432,10750,10751,10752,10755],{},"Cập nhật ",[487,10753,10754],{},"app\u002Fapp.vue"," để làm root layout:",[616,10757,10759],{"className":3830,"code":10758,"language":934,"meta":624,"style":624},"\u003Ctemplate>\n  \u003Cdiv class=\"app-root\">\n    \u003CNuxtPage \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cstyle>\n\u002F* Global styles — KHÔNG scoped để apply toàn app *\u002F\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n  margin: 0;\n  padding: 0;\n}\n\nbody {\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,\n    'Helvetica Neue', Arial, sans-serif;\n  background: var(--color-bg-body);\n  color: var(--color-text);\n  line-height: 1.5;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\u003C\u002Fstyle>\n",[487,10760,10761,10769,10788,10797,10805,10813,10817,10825,10830,10837,10848,10859,10871,10881,10891,10895,10899,10906,10937,10959,10974,10988,10999,11011,11023,11027],{"__ignoreMap":624},[643,10762,10763,10765,10767],{"class":645,"line":646},[643,10764,1801],{"class":726},[643,10766,4077],{"class":1046},[643,10768,3858],{"class":726},[643,10770,10771,10773,10775,10777,10779,10781,10784,10786],{"class":645,"line":663},[643,10772,4084],{"class":726},[643,10774,6055],{"class":1046},[643,10776,7404],{"class":735},[643,10778,1567],{"class":726},[643,10780,739],{"class":726},[643,10782,10783],{"class":653},"app-root",[643,10785,739],{"class":726},[643,10787,3858],{"class":726},[643,10789,10790,10792,10795],{"class":645,"line":672},[643,10791,4125],{"class":726},[643,10793,10794],{"class":1046},"NuxtPage",[643,10796,8273],{"class":726},[643,10798,10799,10801,10803],{"class":645,"line":681},[643,10800,4274],{"class":726},[643,10802,6055],{"class":1046},[643,10804,3858],{"class":726},[643,10806,10807,10809,10811],{"class":645,"line":789},[643,10808,4062],{"class":726},[643,10810,4077],{"class":1046},[643,10812,3858],{"class":726},[643,10814,10815],{"class":645,"line":804},[643,10816,1307],{"emptyLinePlaceholder":1306},[643,10818,10819,10821,10823],{"class":645,"line":826},[643,10820,1801],{"class":726},[643,10822,4298],{"class":1046},[643,10824,3858],{"class":726},[643,10826,10827],{"class":645,"line":847},[643,10828,10829],{"class":1023},"\u002F* Global styles — KHÔNG scoped để apply toàn app *\u002F\n",[643,10831,10832,10835],{"class":645,"line":868},[643,10833,10834],{"class":649},"*",[643,10836,753],{"class":726},[643,10838,10839,10841,10843,10846],{"class":645,"line":888},[643,10840,10834],{"class":649},[643,10842,4558],{"class":726},[643,10844,10845],{"class":735},"before",[643,10847,753],{"class":726},[643,10849,10850,10852,10854,10857],{"class":645,"line":894},[643,10851,10834],{"class":649},[643,10853,4558],{"class":726},[643,10855,10856],{"class":735},"after",[643,10858,801],{"class":726},[643,10860,10861,10864,10866,10869],{"class":645,"line":908},[643,10862,10863],{"class":4316},"  box-sizing",[643,10865,742],{"class":726},[643,10867,10868],{"class":1038}," border-box",[643,10870,4325],{"class":726},[643,10872,10873,10875,10877,10879],{"class":645,"line":929},[643,10874,8854],{"class":4316},[643,10876,742],{"class":726},[643,10878,6360],{"class":4335},[643,10880,4325],{"class":726},[643,10882,10883,10885,10887,10889],{"class":645,"line":948},[643,10884,4383],{"class":4316},[643,10886,742],{"class":726},[643,10888,6360],{"class":4335},[643,10890,4325],{"class":726},[643,10892,10893],{"class":645,"line":954},[643,10894,957],{"class":726},[643,10896,10897],{"class":645,"line":1394},[643,10898,1307],{"emptyLinePlaceholder":1306},[643,10900,10901,10904],{"class":645,"line":1399},[643,10902,10903],{"class":649},"body",[643,10905,801],{"class":726},[643,10907,10908,10911,10913,10916,10918,10921,10923,10925,10928,10930,10932,10935],{"class":645,"line":1405},[643,10909,10910],{"class":4316},"  font-family",[643,10912,742],{"class":726},[643,10914,10915],{"class":1038}," -apple-system",[643,10917,1180],{"class":726},[643,10919,10920],{"class":1038}," BlinkMacSystemFont",[643,10922,1180],{"class":726},[643,10924,1052],{"class":726},[643,10926,10927],{"class":653},"Segoe UI",[643,10929,1058],{"class":726},[643,10931,1180],{"class":726},[643,10933,10934],{"class":1038}," Roboto",[643,10936,753],{"class":726},[643,10938,10939,10942,10945,10947,10949,10952,10954,10957],{"class":645,"line":1416},[643,10940,10941],{"class":726},"    '",[643,10943,10944],{"class":653},"Helvetica Neue",[643,10946,1058],{"class":726},[643,10948,1180],{"class":726},[643,10950,10951],{"class":1038}," Arial",[643,10953,1180],{"class":726},[643,10955,10956],{"class":1038}," sans-serif",[643,10958,4325],{"class":726},[643,10960,10961,10963,10965,10967,10969,10972],{"class":645,"line":1421},[643,10962,4450],{"class":4316},[643,10964,742],{"class":726},[643,10966,4438],{"class":666},[643,10968,1039],{"class":726},[643,10970,10971],{"class":1038},"--color-bg-body",[643,10973,4428],{"class":726},[643,10975,10976,10978,10980,10982,10984,10986],{"class":645,"line":1426},[643,10977,4466],{"class":4316},[643,10979,742],{"class":726},[643,10981,4438],{"class":666},[643,10983,1039],{"class":726},[643,10985,4475],{"class":1038},[643,10987,4428],{"class":726},[643,10989,10990,10992,10994,10997],{"class":645,"line":1431},[643,10991,10585],{"class":4316},[643,10993,742],{"class":726},[643,10995,10996],{"class":4335}," 1.5",[643,10998,4325],{"class":726},[643,11000,11001,11004,11006,11009],{"class":645,"line":1437},[643,11002,11003],{"class":649},"  -webkit-font-smoothing",[643,11005,742],{"class":726},[643,11007,11008],{"class":1038}," antialiased",[643,11010,4325],{"class":726},[643,11012,11013,11016,11018,11021],{"class":645,"line":1443},[643,11014,11015],{"class":649},"  -moz-osx-font-smoothing",[643,11017,742],{"class":726},[643,11019,11020],{"class":1038}," grayscale",[643,11022,4325],{"class":726},[643,11024,11025],{"class":645,"line":1448},[643,11026,957],{"class":726},[643,11028,11029,11031,11033],{"class":645,"line":1835},[643,11030,4062],{"class":726},[643,11032,4298],{"class":1046},[643,11034,3858],{"class":726},[432,11036,11037,742],{},[436,11038,11039],{},[487,11040,11041],{},"\u003CNuxtPage \u002F>",[471,11043,11044,11047,11054],{},[474,11045,11046],{},"Là component built-in của Nuxt — render page tương ứng với route hiện tại.",[474,11048,11049,11050,11053],{},"Tương đương ",[487,11051,11052],{},"\u003CRouterView \u002F>"," trong Vue Router nhưng thêm transition + keep-alive.",[474,11055,11056,11057,11060,11061,497],{},"Nếu app có layout: ",[487,11058,11059],{},"\u003CNuxtLayout>"," wrap ",[487,11062,11063],{},"\u003CNuxtPage>",[626,11065],{},[427,11067,11069],{"id":11068},"_7-global-css-theming","7. Global CSS — Theming",[432,11071,1288,11072,742],{},[487,11073,11074],{},"app\u002Fassets\u002Fcss\u002Fmain.css",[616,11076,11080],{"className":11077,"code":11078,"language":11079,"meta":624,"style":624},"language-css shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F* app\u002Fassets\u002Fcss\u002Fmain.css *\u002F\n\n\u002F* ============================================================\n   CSS Custom Properties — Theme variables\n   Thay đổi theme bằng cách override các biến này\n   ============================================================ *\u002F\n\n:root {\n  \u002F* Colors *\u002F\n  --color-primary: #6c5ce7;\n  --color-primary-hover: #5a4bd1;\n  --color-danger: #e74c3c;\n  --color-danger-hover: #c0392b;\n  --color-success: #27ae60;\n\n  \u002F* Background *\u002F\n  --color-bg-body: #f5f6fa;\n  --color-bg: #ffffff;\n  --color-bg-hover: #f8f9fa;\n  --color-bg-active: #f0f0ff;\n\n  \u002F* Text *\u002F\n  --color-text: #2d3436;\n  --color-text-muted: #95a5a6;\n\n  \u002F* Border *\u002F\n  --color-border: #e0e0e0;\n\n  \u002F* Radius *\u002F\n  --radius: 8px;\n\n  \u002F* Shadow *\u002F\n  --shadow: 0 2px 16px rgba(0, 0, 0, 0.08);\n}\n\n\u002F* Dark mode — kích hoạt khi user chọn dark mode ở OS *\u002F\n@media (prefers-color-scheme: dark) {\n  :root {\n    --color-primary: #a29bfe;\n    --color-primary-hover: #8b83e6;\n    --color-danger: #ff7675;\n    --color-danger-hover: #d63031;\n    --color-success: #55efc4;\n\n    --color-bg-body: #1a1a2e;\n    --color-bg: #16213e;\n    --color-bg-hover: #1a2744;\n    --color-bg-active: #1e2d50;\n\n    --color-text: #e0e0e0;\n    --color-text-muted: #7f8c8d;\n\n    --color-border: #2d3436;\n\n    --shadow: 0 2px 16px rgba(0, 0, 0, 0.3);\n  }\n}\n","css",[487,11081,11082,11087,11091,11095,11100,11105,11109,11113,11122,11127,11142,11156,11170,11184,11198,11202,11207,11221,11235,11249,11263,11267,11272,11286,11300,11304,11309,11323,11327,11332,11343,11347,11352,11388,11392,11396,11401,11420,11429,11443,11457,11471,11485,11499,11503,11517,11531,11545,11559,11563,11576,11590,11594,11607,11611,11645,11649],{"__ignoreMap":624},[643,11083,11084],{"class":645,"line":646},[643,11085,11086],{"class":1023},"\u002F* app\u002Fassets\u002Fcss\u002Fmain.css *\u002F\n",[643,11088,11089],{"class":645,"line":663},[643,11090,1307],{"emptyLinePlaceholder":1306},[643,11092,11093],{"class":645,"line":672},[643,11094,9227],{"class":1023},[643,11096,11097],{"class":645,"line":681},[643,11098,11099],{"class":1023},"   CSS Custom Properties — Theme variables\n",[643,11101,11102],{"class":645,"line":789},[643,11103,11104],{"class":1023},"   Thay đổi theme bằng cách override các biến này\n",[643,11106,11107],{"class":645,"line":804},[643,11108,9237],{"class":1023},[643,11110,11111],{"class":645,"line":826},[643,11112,1307],{"emptyLinePlaceholder":1306},[643,11114,11115,11117,11120],{"class":645,"line":847},[643,11116,742],{"class":726},[643,11118,11119],{"class":735},"root",[643,11121,801],{"class":726},[643,11123,11124],{"class":645,"line":868},[643,11125,11126],{"class":1023},"  \u002F* Colors *\u002F\n",[643,11128,11129,11132,11134,11137,11140],{"class":645,"line":888},[643,11130,11131],{"class":1038},"  --color-primary",[643,11133,742],{"class":726},[643,11135,11136],{"class":726}," #",[643,11138,11139],{"class":1038},"6c5ce7",[643,11141,4325],{"class":726},[643,11143,11144,11147,11149,11151,11154],{"class":645,"line":894},[643,11145,11146],{"class":1038},"  --color-primary-hover",[643,11148,742],{"class":726},[643,11150,11136],{"class":726},[643,11152,11153],{"class":1038},"5a4bd1",[643,11155,4325],{"class":726},[643,11157,11158,11161,11163,11165,11168],{"class":645,"line":908},[643,11159,11160],{"class":1038},"  --color-danger",[643,11162,742],{"class":726},[643,11164,11136],{"class":726},[643,11166,11167],{"class":1038},"e74c3c",[643,11169,4325],{"class":726},[643,11171,11172,11175,11177,11179,11182],{"class":645,"line":929},[643,11173,11174],{"class":1038},"  --color-danger-hover",[643,11176,742],{"class":726},[643,11178,11136],{"class":726},[643,11180,11181],{"class":1038},"c0392b",[643,11183,4325],{"class":726},[643,11185,11186,11189,11191,11193,11196],{"class":645,"line":948},[643,11187,11188],{"class":1038},"  --color-success",[643,11190,742],{"class":726},[643,11192,11136],{"class":726},[643,11194,11195],{"class":1038},"27ae60",[643,11197,4325],{"class":726},[643,11199,11200],{"class":645,"line":954},[643,11201,1307],{"emptyLinePlaceholder":1306},[643,11203,11204],{"class":645,"line":1394},[643,11205,11206],{"class":1023},"  \u002F* Background *\u002F\n",[643,11208,11209,11212,11214,11216,11219],{"class":645,"line":1399},[643,11210,11211],{"class":1038},"  --color-bg-body",[643,11213,742],{"class":726},[643,11215,11136],{"class":726},[643,11217,11218],{"class":1038},"f5f6fa",[643,11220,4325],{"class":726},[643,11222,11223,11226,11228,11230,11233],{"class":645,"line":1405},[643,11224,11225],{"class":1038},"  --color-bg",[643,11227,742],{"class":726},[643,11229,11136],{"class":726},[643,11231,11232],{"class":1038},"ffffff",[643,11234,4325],{"class":726},[643,11236,11237,11240,11242,11244,11247],{"class":645,"line":1416},[643,11238,11239],{"class":1038},"  --color-bg-hover",[643,11241,742],{"class":726},[643,11243,11136],{"class":726},[643,11245,11246],{"class":1038},"f8f9fa",[643,11248,4325],{"class":726},[643,11250,11251,11254,11256,11258,11261],{"class":645,"line":1421},[643,11252,11253],{"class":1038},"  --color-bg-active",[643,11255,742],{"class":726},[643,11257,11136],{"class":726},[643,11259,11260],{"class":1038},"f0f0ff",[643,11262,4325],{"class":726},[643,11264,11265],{"class":645,"line":1426},[643,11266,1307],{"emptyLinePlaceholder":1306},[643,11268,11269],{"class":645,"line":1431},[643,11270,11271],{"class":1023},"  \u002F* Text *\u002F\n",[643,11273,11274,11277,11279,11281,11284],{"class":645,"line":1437},[643,11275,11276],{"class":1038},"  --color-text",[643,11278,742],{"class":726},[643,11280,11136],{"class":726},[643,11282,11283],{"class":1038},"2d3436",[643,11285,4325],{"class":726},[643,11287,11288,11291,11293,11295,11298],{"class":645,"line":1443},[643,11289,11290],{"class":1038},"  --color-text-muted",[643,11292,742],{"class":726},[643,11294,11136],{"class":726},[643,11296,11297],{"class":1038},"95a5a6",[643,11299,4325],{"class":726},[643,11301,11302],{"class":645,"line":1448},[643,11303,1307],{"emptyLinePlaceholder":1306},[643,11305,11306],{"class":645,"line":1835},[643,11307,11308],{"class":1023},"  \u002F* Border *\u002F\n",[643,11310,11311,11314,11316,11318,11321],{"class":645,"line":1840},[643,11312,11313],{"class":1038},"  --color-border",[643,11315,742],{"class":726},[643,11317,11136],{"class":726},[643,11319,11320],{"class":1038},"e0e0e0",[643,11322,4325],{"class":726},[643,11324,11325],{"class":645,"line":1846},[643,11326,1307],{"emptyLinePlaceholder":1306},[643,11328,11329],{"class":645,"line":1852},[643,11330,11331],{"class":1023},"  \u002F* Radius *\u002F\n",[643,11333,11334,11337,11339,11341],{"class":645,"line":1857},[643,11335,11336],{"class":1038},"  --radius",[643,11338,742],{"class":726},[643,11340,4336],{"class":4335},[643,11342,4325],{"class":726},[643,11344,11345],{"class":645,"line":1899},[643,11346,1307],{"emptyLinePlaceholder":1306},[643,11348,11349],{"class":645,"line":1904},[643,11350,11351],{"class":1023},"  \u002F* Shadow *\u002F\n",[643,11353,11354,11357,11359,11361,11363,11365,11368,11370,11373,11375,11377,11379,11381,11383,11386],{"class":645,"line":1909},[643,11355,11356],{"class":1038},"  --shadow",[643,11358,742],{"class":726},[643,11360,6360],{"class":4335},[643,11362,4414],{"class":4335},[643,11364,4391],{"class":4335},[643,11366,11367],{"class":666}," rgba",[643,11369,1039],{"class":726},[643,11371,11372],{"class":4335},"0",[643,11374,1180],{"class":726},[643,11376,6360],{"class":4335},[643,11378,1180],{"class":726},[643,11380,6360],{"class":4335},[643,11382,1180],{"class":726},[643,11384,11385],{"class":4335}," 0.08",[643,11387,4428],{"class":726},[643,11389,11390],{"class":645,"line":1915},[643,11391,957],{"class":726},[643,11393,11394],{"class":645,"line":1920},[643,11395,1307],{"emptyLinePlaceholder":1306},[643,11397,11398],{"class":645,"line":1925},[643,11399,11400],{"class":1023},"\u002F* Dark mode — kích hoạt khi user chọn dark mode ở OS *\u002F\n",[643,11402,11403,11406,11408,11411,11413,11416,11418],{"class":645,"line":1931},[643,11404,11405],{"class":1029},"@media",[643,11407,1937],{"class":726},[643,11409,11410],{"class":1038},"prefers-color-scheme",[643,11412,742],{"class":726},[643,11414,11415],{"class":1038}," dark",[643,11417,2261],{"class":726},[643,11419,801],{"class":726},[643,11421,11422,11425,11427],{"class":645,"line":1957},[643,11423,11424],{"class":726},"  :",[643,11426,11119],{"class":735},[643,11428,801],{"class":726},[643,11430,11431,11434,11436,11438,11441],{"class":645,"line":1965},[643,11432,11433],{"class":1038},"    --color-primary",[643,11435,742],{"class":726},[643,11437,11136],{"class":726},[643,11439,11440],{"class":1038},"a29bfe",[643,11442,4325],{"class":726},[643,11444,11445,11448,11450,11452,11455],{"class":645,"line":1995},[643,11446,11447],{"class":1038},"    --color-primary-hover",[643,11449,742],{"class":726},[643,11451,11136],{"class":726},[643,11453,11454],{"class":1038},"8b83e6",[643,11456,4325],{"class":726},[643,11458,11459,11462,11464,11466,11469],{"class":645,"line":2010},[643,11460,11461],{"class":1038},"    --color-danger",[643,11463,742],{"class":726},[643,11465,11136],{"class":726},[643,11467,11468],{"class":1038},"ff7675",[643,11470,4325],{"class":726},[643,11472,11473,11476,11478,11480,11483],{"class":645,"line":2016},[643,11474,11475],{"class":1038},"    --color-danger-hover",[643,11477,742],{"class":726},[643,11479,11136],{"class":726},[643,11481,11482],{"class":1038},"d63031",[643,11484,4325],{"class":726},[643,11486,11487,11490,11492,11494,11497],{"class":645,"line":2048},[643,11488,11489],{"class":1038},"    --color-success",[643,11491,742],{"class":726},[643,11493,11136],{"class":726},[643,11495,11496],{"class":1038},"55efc4",[643,11498,4325],{"class":726},[643,11500,11501],{"class":645,"line":2080},[643,11502,1307],{"emptyLinePlaceholder":1306},[643,11504,11505,11508,11510,11512,11515],{"class":645,"line":2090},[643,11506,11507],{"class":1038},"    --color-bg-body",[643,11509,742],{"class":726},[643,11511,11136],{"class":726},[643,11513,11514],{"class":1038},"1a1a2e",[643,11516,4325],{"class":726},[643,11518,11519,11522,11524,11526,11529],{"class":645,"line":2115},[643,11520,11521],{"class":1038},"    --color-bg",[643,11523,742],{"class":726},[643,11525,11136],{"class":726},[643,11527,11528],{"class":1038},"16213e",[643,11530,4325],{"class":726},[643,11532,11533,11536,11538,11540,11543],{"class":645,"line":2124},[643,11534,11535],{"class":1038},"    --color-bg-hover",[643,11537,742],{"class":726},[643,11539,11136],{"class":726},[643,11541,11542],{"class":1038},"1a2744",[643,11544,4325],{"class":726},[643,11546,11547,11550,11552,11554,11557],{"class":645,"line":2130},[643,11548,11549],{"class":1038},"    --color-bg-active",[643,11551,742],{"class":726},[643,11553,11136],{"class":726},[643,11555,11556],{"class":1038},"1e2d50",[643,11558,4325],{"class":726},[643,11560,11561],{"class":645,"line":2141},[643,11562,1307],{"emptyLinePlaceholder":1306},[643,11564,11565,11568,11570,11572,11574],{"class":645,"line":2147},[643,11566,11567],{"class":1038},"    --color-text",[643,11569,742],{"class":726},[643,11571,11136],{"class":726},[643,11573,11320],{"class":1038},[643,11575,4325],{"class":726},[643,11577,11578,11581,11583,11585,11588],{"class":645,"line":2169},[643,11579,11580],{"class":1038},"    --color-text-muted",[643,11582,742],{"class":726},[643,11584,11136],{"class":726},[643,11586,11587],{"class":1038},"7f8c8d",[643,11589,4325],{"class":726},[643,11591,11592],{"class":645,"line":2174},[643,11593,1307],{"emptyLinePlaceholder":1306},[643,11595,11596,11599,11601,11603,11605],{"class":645,"line":2179},[643,11597,11598],{"class":1038},"    --color-border",[643,11600,742],{"class":726},[643,11602,11136],{"class":726},[643,11604,11283],{"class":1038},[643,11606,4325],{"class":726},[643,11608,11609],{"class":645,"line":2184},[643,11610,1307],{"emptyLinePlaceholder":1306},[643,11612,11613,11616,11618,11620,11622,11624,11626,11628,11630,11632,11634,11636,11638,11640,11643],{"class":645,"line":2189},[643,11614,11615],{"class":1038},"    --shadow",[643,11617,742],{"class":726},[643,11619,6360],{"class":4335},[643,11621,4414],{"class":4335},[643,11623,4391],{"class":4335},[643,11625,11367],{"class":666},[643,11627,1039],{"class":726},[643,11629,11372],{"class":4335},[643,11631,1180],{"class":726},[643,11633,6360],{"class":4335},[643,11635,1180],{"class":726},[643,11637,6360],{"class":4335},[643,11639,1180],{"class":726},[643,11641,11642],{"class":4335}," 0.3",[643,11644,4428],{"class":726},[643,11646,11647],{"class":645,"line":2195},[643,11648,951],{"class":726},[643,11650,11651],{"class":645,"line":2200},[643,11652,957],{"class":726},[466,11654,1490],{"id":11655},"giải-thích-chi-tiết-6",[432,11657,11658,742],{},[436,11659,11660],{},"CSS Custom Properties (variables)",[471,11662,11663,11674,11677],{},[474,11664,11665,11666,11669,11670,11673],{},"Khai báo trong ",[487,11667,11668],{},":root"," (tương đương ",[487,11671,11672],{},"\u003Chtml>",") — có thể dùng ở mọi CSS selector.",[474,11675,11676],{},"Thay đổi theme bằng cách override variables — không cần sửa từng component.",[474,11678,11679],{},"Browser support: tất cả browser hiện đại (IE không hỗ trợ).",[432,11681,11682],{},[436,11683,11684,742],{},[487,11685,11686],{},"@media (prefers-color-scheme: dark)",[471,11688,11689,11692,11695],{},[474,11690,11691],{},"CSS media query — tự động detect user chọn dark mode trong OS settings.",[474,11693,11694],{},"Không cần JavaScript — CSS tự switch.",[474,11696,11697,11698,11701,11702,11704,11705,497],{},"Muốn manual toggle dark mode: add class ",[487,11699,11700],{},".dark"," vào ",[487,11703,11672],{}," và override variables trong ",[487,11706,11707],{},"html.dark",[626,11709],{},[427,11711,11713,11714],{"id":11712},"_8-app-configuration-appconfigts","8. App Configuration — ",[487,11715,11716],{},"app.config.ts",[432,11718,1288,11719,742],{},[487,11720,11721],{},"app\u002Fapp.config.ts",[616,11723,11725],{"className":1014,"code":11724,"language":1016,"meta":624,"style":624},"\u002F\u002F app\u002Fapp.config.ts\n\nexport default defineAppConfig({\n  app: {\n    name: 'Nuxt 4 Todo App',\n    version: '1.0.0'\n  },\n  ui: {\n    primary: 'purple',\n    animations: true\n  }\n})\n",[487,11726,11727,11732,11736,11749,11757,11773,11787,11791,11800,11816,11825,11829],{"__ignoreMap":624},[643,11728,11729],{"class":645,"line":646},[643,11730,11731],{"class":1023},"\u002F\u002F app\u002Fapp.config.ts\n",[643,11733,11734],{"class":645,"line":663},[643,11735,1307],{"emptyLinePlaceholder":1306},[643,11737,11738,11740,11742,11745,11747],{"class":645,"line":672},[643,11739,972],{"class":1029},[643,11741,1032],{"class":1029},[643,11743,11744],{"class":666}," defineAppConfig",[643,11746,1039],{"class":1038},[643,11748,727],{"class":726},[643,11750,11751,11753,11755],{"class":645,"line":681},[643,11752,1121],{"class":1046},[643,11754,742],{"class":726},[643,11756,801],{"class":726},[643,11758,11759,11762,11764,11766,11769,11771],{"class":645,"line":789},[643,11760,11761],{"class":1046},"    name",[643,11763,742],{"class":726},[643,11765,1052],{"class":726},[643,11767,11768],{"class":653},"Nuxt 4 Todo App",[643,11770,1058],{"class":726},[643,11772,753],{"class":726},[643,11774,11775,11778,11780,11782,11785],{"class":645,"line":804},[643,11776,11777],{"class":1046},"    version",[643,11779,742],{"class":726},[643,11781,1052],{"class":726},[643,11783,11784],{"class":653},"1.0.0",[643,11786,1486],{"class":726},[643,11788,11789],{"class":645,"line":826},[643,11790,891],{"class":726},[643,11792,11793,11796,11798],{"class":645,"line":847},[643,11794,11795],{"class":1046},"  ui",[643,11797,742],{"class":726},[643,11799,801],{"class":726},[643,11801,11802,11805,11807,11809,11812,11814],{"class":645,"line":868},[643,11803,11804],{"class":1046},"    primary",[643,11806,742],{"class":726},[643,11808,1052],{"class":726},[643,11810,11811],{"class":653},"purple",[643,11813,1058],{"class":726},[643,11815,753],{"class":726},[643,11817,11818,11821,11823],{"class":645,"line":888},[643,11819,11820],{"class":1046},"    animations",[643,11822,742],{"class":726},[643,11824,5359],{"class":1078},[643,11826,11827],{"class":645,"line":894},[643,11828,951],{"class":726},[643,11830,11831,11833],{"class":645,"line":908},[643,11832,1214],{"class":726},[643,11834,1217],{"class":1038},[449,11836,11837,11845],{"color":451,"icon":452},[432,11838,11839],{},[436,11840,11841,1497,11843,742],{},[487,11842,11716],{},[487,11844,1010],{},[471,11846,11847,11855,11860],{},[474,11848,11849,11851,11852,497],{},[487,11850,11716],{},": reactive config exposed to client — dùng cho theme, UI settings. Có thể truy cập qua ",[487,11853,11854],{},"useAppConfig()",[474,11856,11857,11859],{},[487,11858,1010],{},": build-time + runtime config — không reactive. Dùng cho routing, modules, server settings.",[474,11861,11862,11865,11866,11868],{},[436,11863,11864],{},"Quan trọng:"," Không đặt secret trong ",[487,11867,11716],{}," — nó exposed to client bundle.",[626,11870],{},[427,11872,11874],{"id":11873},"_9-tổng-kết-toàn-bộ-flow-của-app","9. Tổng kết — Toàn bộ flow của app",[449,11876,11878,11883],{"color":451,"icon":11877},"i-lucide-lightbulb",[432,11879,11880],{},[436,11881,11882],{},"Data flow trong Todo App:",[11884,11885,11886,11895,11910,11924,11933,11943,11956,11968,11982],"ol",{},[474,11887,11888,11891,11892,11894],{},[436,11889,11890],{},"User gõ vào TodoForm"," → ",[487,11893,4867],{}," cập nhật local ref.",[474,11896,11897,11891,11900,11903,11904,11907,11908,497],{},[436,11898,11899],{},"User nhấn Enter\u002FAdd",[487,11901,11902],{},"handleSubmit()"," gọi ",[487,11905,11906],{},"addTodo(title)"," trong ",[487,11909,505],{},[474,11911,11912,11917,11918,11920,11921,11923],{},[436,11913,11914],{},[487,11915,11916],{},"addTodo"," push object ",[487,11919,1804],{}," mới vào ",[487,11922,1817],{}," (useState array).",[474,11925,11926,11932],{},[436,11927,11928,11931],{},[487,11929,11930],{},"filteredTodos"," (computed)"," tự động tính lại → TodoList re-render.",[474,11934,11935,11940,11941,497],{},[436,11936,11937],{},[487,11938,11939],{},"watch(todos, ...)"," phát hiện thay đổi → tự động save vào ",[487,11942,537],{},[474,11944,11945,11948,11949,11891,11952,11955],{},[436,11946,11947],{},"User click checkbox toggle"," → TodoItem emit ",[487,11950,11951],{},"toggle(id)",[487,11953,11954],{},"toggleTodo(id)"," trong useTodos.",[474,11957,11958,11961,11962,5191,11965,497],{},[436,11959,11960],{},"User double-click todo"," → TodoItem vào edit mode → ",[487,11963,11964],{},"saveEdit()",[487,11966,11967],{},"edit(id, title)",[474,11969,11970,11973,11974,11976,11977,11891,11979,11981],{},[436,11971,11972],{},"User click filter"," → TodoFilter cập nhật ",[487,11975,2402],{}," qua ",[487,11978,565],{},[487,11980,11930],{}," re-compute.",[474,11983,11984,11891,11987,11990],{},[436,11985,11986],{},"User click Clear completed",[487,11988,11989],{},"clearCompleted()"," xóa todos đã hoàn thành.",[466,11992,11994],{"id":11993},"tính-năng-nuxt-4-được-áp-dụng","Tính năng Nuxt 4 được áp dụng",[9600,11996,11997,12010],{},[9603,11998,11999],{},[9606,12000,12001,12004,12007],{},[9609,12002,12003],{},"Tính năng",[9609,12005,12006],{},"File",[9609,12008,12009],{},"Giải thích",[9616,12011,12012,12037,12053,12074,12088,12102,12116,12130,12142,12156],{},[9606,12013,12014,12019,12022],{},[9621,12015,12016],{},[436,12017,12018],{},"Auto-import",[9621,12020,12021],{},"Tất cả",[9621,12023,12024,490,12026,490,12028,490,12030,490,12032,490,12034,12036],{},[487,12025,517],{},[487,12027,525],{},[487,12029,533],{},[487,12031,513],{},[487,12033,592],{},[487,12035,595],{}," — không cần import",[9606,12038,12039,12043,12048],{},[9621,12040,12041],{},[436,12042,10705],{},[9621,12044,12045],{},[487,12046,12047],{},"pages\u002Findex.vue",[9621,12049,12050,12051],{},"Tự động trở thành route ",[487,12052,969],{},[9606,12054,12055,12060,12071],{},[9621,12056,12057],{},[436,12058,12059],{},"Components auto-import",[9621,12061,12062,490,12064,490,12066,490,12069],{},[487,12063,10184],{},[487,12065,10193],{},[487,12067,12068],{},"TodoItem",[487,12070,1871],{},[9621,12072,12073],{},"Dùng trực tiếp trong template không cần import",[9606,12075,12076,12081,12085],{},[9621,12077,12078],{},[436,12079,12080],{},"Composables auto-import",[9621,12082,12083],{},[487,12084,505],{},[9621,12086,12087],{},"Export named function → auto-available ở mọi component",[9606,12089,12090,12094,12099],{},[9621,12091,12092],{},[436,12093,513],{},[9621,12095,12096],{},[487,12097,12098],{},"useTodos.ts",[9621,12100,12101],{},"Shared state SSR-safe",[9606,12103,12104,12108,12113],{},[9621,12105,12106],{},[436,12107,288],{},[9621,12109,12110],{},[487,12111,12112],{},"types\u002Ftodo.ts",[9621,12114,12115],{},"Type safety full-stack",[9606,12117,12118,12123,12127],{},[9621,12119,12120],{},[436,12121,12122],{},"useHead \u002F useSeoMeta",[9621,12124,12125],{},[487,12126,12047],{},[9621,12128,12129],{},"SEO built-in, SSR + client",[9606,12131,12132,12136,12139],{},[9621,12133,12134],{},[436,12135,4990],{},[9621,12137,12138],{},"Tất cả component",[9621,12140,12141],{},"Style không leak",[9606,12143,12144,12148,12153],{},[9621,12145,12146],{},[436,12147,584],{},[9621,12149,12150],{},[487,12151,12152],{},"TodoList.vue",[9621,12154,12155],{},"Animation thêm\u002Fxóa item",[9606,12157,12158,12162,12167],{},[9621,12159,12160],{},[436,12161,565],{},[9621,12163,12164],{},[487,12165,12166],{},"TodoFilter.vue",[9621,12168,12169],{},"Vue 3.4+ v-model sugar",[626,12171],{},[427,12173,12175],{"id":12174},"_10-bonus-mở-rộng-và-cải-tiến","10. Bonus — Mở rộng và cải tiến",[432,12177,12178],{},"Sau khi hoàn thành Todo App cơ bản, đây là một số ý tưởng để mở rộng:",[466,12180,12182],{"id":12181},"_101-keyboard-shortcut-ctrlenter-để-thêm","10.1 Keyboard shortcut (Ctrl+Enter để thêm)",[432,12184,12185,12186,12189],{},"Trong ",[487,12187,12188],{},"TodoForm.vue",", thêm keyboard listener:",[616,12191,12193],{"className":3830,"code":12192,"language":934,"meta":624,"style":624},"\u003Cscript setup lang=\"ts\">\n\u002F\u002F Sử dụng useEventListener từ VueUse (hoặc onMounted + addEventListener)\nonMounted(() => {\n  const handler = (e: KeyboardEvent) => {\n    if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {\n      handleSubmit()\n    }\n  }\n  window.addEventListener('keydown', handler)\n  onUnmounted(() => window.removeEventListener('keydown', handler))\n})\n\u003C\u002Fscript>\n",[487,12194,12195,12215,12220,12233,12258,12307,12314,12318,12322,12347,12380,12386],{"__ignoreMap":624},[643,12196,12197,12199,12201,12203,12205,12207,12209,12211,12213],{"class":645,"line":646},[643,12198,1801],{"class":726},[643,12200,3840],{"class":1046},[643,12202,3843],{"class":735},[643,12204,3846],{"class":735},[643,12206,1567],{"class":726},[643,12208,739],{"class":726},[643,12210,3853],{"class":653},[643,12212,739],{"class":726},[643,12214,3858],{"class":726},[643,12216,12217],{"class":645,"line":663},[643,12218,12219],{"class":1023},"\u002F\u002F Sử dụng useEventListener từ VueUse (hoặc onMounted + addEventListener)\n",[643,12221,12222,12225,12227,12229,12231],{"class":645,"line":672},[643,12223,12224],{"class":666},"onMounted",[643,12226,1039],{"class":1038},[643,12228,2389],{"class":726},[643,12230,1725],{"class":735},[643,12232,801],{"class":726},[643,12234,12235,12237,12240,12242,12244,12247,12249,12252,12254,12256],{"class":645,"line":681},[643,12236,1790],{"class":735},[643,12238,12239],{"class":1038}," handler",[643,12241,1459],{"class":726},[643,12243,1937],{"class":726},[643,12245,12246],{"class":2070},"e",[643,12248,742],{"class":726},[643,12250,12251],{"class":649}," KeyboardEvent",[643,12253,2261],{"class":726},[643,12255,1725],{"class":735},[643,12257,801],{"class":726},[643,12259,12260,12262,12265,12267,12269,12272,12275,12278,12280,12283,12285,12288,12290,12292,12294,12296,12298,12301,12303,12305],{"class":645,"line":789},[643,12261,2791],{"class":1029},[643,12263,12264],{"class":1046}," ((",[643,12266,12246],{"class":1038},[643,12268,497],{"class":726},[643,12270,12271],{"class":1038},"ctrlKey",[643,12273,12274],{"class":726}," ||",[643,12276,12277],{"class":1038}," e",[643,12279,497],{"class":726},[643,12281,12282],{"class":1038},"metaKey",[643,12284,1952],{"class":1046},[643,12286,12287],{"class":726},"&&",[643,12289,12277],{"class":1038},[643,12291,497],{"class":726},[643,12293,9506],{"class":1038},[643,12295,3002],{"class":726},[643,12297,1052],{"class":726},[643,12299,12300],{"class":653},"Enter",[643,12302,1058],{"class":726},[643,12304,1952],{"class":1046},[643,12306,727],{"class":726},[643,12308,12309,12312],{"class":645,"line":804},[643,12310,12311],{"class":666},"      handleSubmit",[643,12313,1578],{"class":1046},[643,12315,12316],{"class":645,"line":826},[643,12317,1205],{"class":726},[643,12319,12320],{"class":645,"line":847},[643,12321,951],{"class":726},[643,12323,12324,12327,12329,12332,12334,12336,12339,12341,12343,12345],{"class":645,"line":868},[643,12325,12326],{"class":1038},"  window",[643,12328,497],{"class":726},[643,12330,12331],{"class":666},"addEventListener",[643,12333,1039],{"class":1046},[643,12335,1058],{"class":726},[643,12337,12338],{"class":653},"keydown",[643,12340,1058],{"class":726},[643,12342,1180],{"class":726},[643,12344,12239],{"class":1038},[643,12346,1217],{"class":1046},[643,12348,12349,12352,12354,12356,12358,12361,12363,12366,12368,12370,12372,12374,12376,12378],{"class":645,"line":888},[643,12350,12351],{"class":666},"  onUnmounted",[643,12353,1039],{"class":1046},[643,12355,2389],{"class":726},[643,12357,1725],{"class":735},[643,12359,12360],{"class":1038}," window",[643,12362,497],{"class":726},[643,12364,12365],{"class":666},"removeEventListener",[643,12367,1039],{"class":1046},[643,12369,1058],{"class":726},[643,12371,12338],{"class":653},[643,12373,1058],{"class":726},[643,12375,1180],{"class":726},[643,12377,12239],{"class":1038},[643,12379,2121],{"class":1046},[643,12381,12382,12384],{"class":645,"line":894},[643,12383,1214],{"class":726},[643,12385,1217],{"class":1038},[643,12387,12388,12390,12392],{"class":645,"line":908},[643,12389,4062],{"class":726},[643,12391,3840],{"class":1046},[643,12393,3858],{"class":726},[466,12395,12397],{"id":12396},"_102-drag-and-drop-reorder","10.2 Drag and drop reorder",[432,12399,12400,12401,4889,12404,742],{},"Sử dụng thư viện ",[487,12402,12403],{},"@vueuse\u002Fintegrations",[487,12405,12406],{},"sortablejs",[616,12408,12410],{"className":637,"code":12409,"language":639,"meta":624,"style":624},"npm install sortablejs\nnpm install @vueuse\u002Fintegrations\n",[487,12411,12412,12422],{"__ignoreMap":624},[643,12413,12414,12416,12419],{"class":645,"line":646},[643,12415,675],{"class":649},[643,12417,12418],{"class":653}," install",[643,12420,12421],{"class":653}," sortablejs\n",[643,12423,12424,12426,12428],{"class":645,"line":663},[643,12425,675],{"class":649},[643,12427,12418],{"class":653},[643,12429,12430],{"class":653}," @vueuse\u002Fintegrations\n",[616,12432,12434],{"className":3830,"code":12433,"language":934,"meta":624,"style":624},"\u003Cscript setup lang=\"ts\">\nimport { useSortable } from '@vueuse\u002Fintegrations\u002FuseSortable'\n\nconst el = ref\u003CHTMLElement>()\nuseSortable(el, todos, {\n  animation: 150,\n  onEnd: () => { \u002F* save order *\u002F }\n})\n\u003C\u002Fscript>\n",[487,12435,12436,12456,12476,12480,12500,12516,12528,12546,12552],{"__ignoreMap":624},[643,12437,12438,12440,12442,12444,12446,12448,12450,12452,12454],{"class":645,"line":646},[643,12439,1801],{"class":726},[643,12441,3840],{"class":1046},[643,12443,3843],{"class":735},[643,12445,3846],{"class":735},[643,12447,1567],{"class":726},[643,12449,739],{"class":726},[643,12451,3853],{"class":653},[643,12453,739],{"class":726},[643,12455,3858],{"class":726},[643,12457,12458,12460,12462,12465,12467,12469,12471,12474],{"class":645,"line":663},[643,12459,968],{"class":1029},[643,12461,1070],{"class":726},[643,12463,12464],{"class":1038}," useSortable",[643,12466,1665],{"class":726},[643,12468,1668],{"class":1029},[643,12470,1052],{"class":726},[643,12472,12473],{"class":653},"@vueuse\u002Fintegrations\u002FuseSortable",[643,12475,1486],{"class":726},[643,12477,12478],{"class":645,"line":672},[643,12479,1307],{"emptyLinePlaceholder":1306},[643,12481,12482,12484,12487,12489,12491,12493,12496,12498],{"class":645,"line":681},[643,12483,1561],{"class":735},[643,12485,12486],{"class":1038}," el ",[643,12488,1567],{"class":726},[643,12490,3916],{"class":666},[643,12492,1801],{"class":726},[643,12494,12495],{"class":649},"HTMLElement",[643,12497,1810],{"class":726},[643,12499,1578],{"class":1038},[643,12501,12502,12505,12508,12510,12512,12514],{"class":645,"line":789},[643,12503,12504],{"class":666},"useSortable",[643,12506,12507],{"class":1038},"(el",[643,12509,1180],{"class":726},[643,12511,1793],{"class":1038},[643,12513,1180],{"class":726},[643,12515,801],{"class":726},[643,12517,12518,12521,12523,12526],{"class":645,"line":804},[643,12519,12520],{"class":1046},"  animation",[643,12522,742],{"class":726},[643,12524,12525],{"class":4335}," 150",[643,12527,753],{"class":726},[643,12529,12530,12533,12535,12537,12539,12541,12544],{"class":645,"line":826},[643,12531,12532],{"class":666},"  onEnd",[643,12534,742],{"class":726},[643,12536,1722],{"class":726},[643,12538,1725],{"class":735},[643,12540,1070],{"class":726},[643,12542,12543],{"class":1023}," \u002F* save order *\u002F",[643,12545,1195],{"class":726},[643,12547,12548,12550],{"class":645,"line":847},[643,12549,1214],{"class":726},[643,12551,1217],{"class":1038},[643,12553,12554,12556,12558],{"class":645,"line":868},[643,12555,4062],{"class":726},[643,12557,3840],{"class":1046},[643,12559,3858],{"class":726},[466,12561,12563],{"id":12562},"_103-dark-mode-toggle-manual","10.3 Dark mode toggle (manual)",[432,12565,12566,12567,742],{},"Thêm component ",[487,12568,12569],{},"ThemeToggle.vue",[616,12571,12573],{"className":3830,"code":12572,"language":934,"meta":624,"style":624},"\u003Cscript setup lang=\"ts\">\nconst isDark = useDark()\nconst toggleDark = useToggle(isDark)\n\u003C\u002Fscript>\n\u003Ctemplate>\n  \u003Cbutton @click=\"toggleDark()\">\n    {{ isDark ? '☀️' : '🌙' }}\n  \u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n",[487,12574,12575,12595,12609,12624,12632,12640,12660,12665,12673],{"__ignoreMap":624},[643,12576,12577,12579,12581,12583,12585,12587,12589,12591,12593],{"class":645,"line":646},[643,12578,1801],{"class":726},[643,12580,3840],{"class":1046},[643,12582,3843],{"class":735},[643,12584,3846],{"class":735},[643,12586,1567],{"class":726},[643,12588,739],{"class":726},[643,12590,3853],{"class":653},[643,12592,739],{"class":726},[643,12594,3858],{"class":726},[643,12596,12597,12599,12602,12604,12607],{"class":645,"line":663},[643,12598,1561],{"class":735},[643,12600,12601],{"class":1038}," isDark ",[643,12603,1567],{"class":726},[643,12605,12606],{"class":666}," useDark",[643,12608,1578],{"class":1038},[643,12610,12611,12613,12616,12618,12621],{"class":645,"line":672},[643,12612,1561],{"class":735},[643,12614,12615],{"class":1038}," toggleDark ",[643,12617,1567],{"class":726},[643,12619,12620],{"class":666}," useToggle",[643,12622,12623],{"class":1038},"(isDark)\n",[643,12625,12626,12628,12630],{"class":645,"line":681},[643,12627,4062],{"class":726},[643,12629,3840],{"class":1046},[643,12631,3858],{"class":726},[643,12633,12634,12636,12638],{"class":645,"line":789},[643,12635,1801],{"class":726},[643,12637,4077],{"class":1046},[643,12639,3858],{"class":726},[643,12641,12642,12644,12646,12649,12651,12653,12656,12658],{"class":645,"line":804},[643,12643,4084],{"class":726},[643,12645,4267],{"class":1046},[643,12647,12648],{"class":735}," @click",[643,12650,1567],{"class":726},[643,12652,739],{"class":726},[643,12654,12655],{"class":653},"toggleDark()",[643,12657,739],{"class":726},[643,12659,3858],{"class":726},[643,12661,12662],{"class":645,"line":826},[643,12663,12664],{"class":1038},"    {{ isDark ? '☀️' : '🌙' }}\n",[643,12666,12667,12669,12671],{"class":645,"line":847},[643,12668,4274],{"class":726},[643,12670,4267],{"class":1046},[643,12672,3858],{"class":726},[643,12674,12675,12677,12679],{"class":645,"line":868},[643,12676,4062],{"class":726},[643,12678,4077],{"class":1046},[643,12680,3858],{"class":726},[432,12682,12683,12684,742],{},"Cài đặt ",[487,12685,12686],{},"@vueuse\u002Fnuxt",[616,12688,12690],{"className":637,"code":12689,"language":639,"meta":624,"style":624},"npm install @vueuse\u002Fnuxt\n# Thêm vào nuxt.config.ts modules: ['@vueuse\u002Fnuxt']\n",[487,12691,12692,12701],{"__ignoreMap":624},[643,12693,12694,12696,12698],{"class":645,"line":646},[643,12695,675],{"class":649},[643,12697,12418],{"class":653},[643,12699,12700],{"class":653}," @vueuse\u002Fnuxt\n",[643,12702,12703],{"class":645,"line":663},[643,12704,12705],{"class":1023},"# Thêm vào nuxt.config.ts modules: ['@vueuse\u002Fnuxt']\n",[466,12707,12709],{"id":12708},"_104-toast-notifications","10.4 Toast notifications",[432,12711,12712,12713,742],{},"Tạo composable ",[487,12714,12715],{},"useToast.ts",[616,12717,12719],{"className":1014,"code":12718,"language":1016,"meta":624,"style":624},"export const useToast = () => {\n  const toasts = useState\u003CArray\u003C{ id: string; message: string; type: 'success' | 'error' }>>('toasts', () => [])\n\n  const addToast = (message: string, type: 'success' | 'error' = 'success') => {\n    const id = crypto.randomUUID()\n    toasts.value.push({ id, message, type })\n    setTimeout(() => {\n      toasts.value = toasts.value.filter(t => t.id !== id)\n    }, 3000)\n  }\n\n  return { toasts, addToast }\n}\n",[487,12720,12721,12738,12813,12817,12869,12885,12916,12929,12968,12977,12981,12985,12999],{"__ignoreMap":624},[643,12722,12723,12725,12727,12730,12732,12734,12736],{"class":645,"line":646},[643,12724,972],{"class":1029},[643,12726,1714],{"class":735},[643,12728,12729],{"class":1038}," useToast ",[643,12731,1567],{"class":726},[643,12733,1722],{"class":726},[643,12735,1725],{"class":735},[643,12737,801],{"class":726},[643,12739,12740,12742,12745,12747,12749,12751,12754,12756,12758,12760,12762,12764,12767,12769,12771,12773,12775,12777,12779,12782,12784,12786,12788,12791,12793,12796,12798,12800,12803,12805,12807,12809,12811],{"class":645,"line":663},[643,12741,1790],{"class":735},[643,12743,12744],{"class":1038}," toasts",[643,12746,1459],{"class":726},[643,12748,1798],{"class":666},[643,12750,1801],{"class":726},[643,12752,12753],{"class":649},"Array",[643,12755,7899],{"class":726},[643,12757,3005],{"class":1046},[643,12759,742],{"class":726},[643,12761,2758],{"class":649},[643,12763,7268],{"class":726},[643,12765,12766],{"class":1046}," message",[643,12768,742],{"class":726},[643,12770,2758],{"class":649},[643,12772,7268],{"class":726},[643,12774,1453],{"class":1046},[643,12776,742],{"class":726},[643,12778,1052],{"class":726},[643,12780,12781],{"class":653},"success",[643,12783,1058],{"class":726},[643,12785,1469],{"class":726},[643,12787,1052],{"class":726},[643,12789,12790],{"class":653},"error",[643,12792,1058],{"class":726},[643,12794,12795],{"class":726}," }>>",[643,12797,1039],{"class":1046},[643,12799,1058],{"class":726},[643,12801,12802],{"class":653},"toasts",[643,12804,1058],{"class":726},[643,12806,1180],{"class":726},[643,12808,1722],{"class":726},[643,12810,1725],{"class":735},[643,12812,1828],{"class":1046},[643,12814,12815],{"class":645,"line":672},[643,12816,1307],{"emptyLinePlaceholder":1306},[643,12818,12819,12821,12824,12826,12828,12831,12833,12835,12837,12839,12841,12843,12845,12847,12849,12851,12853,12855,12857,12859,12861,12863,12865,12867],{"class":645,"line":681},[643,12820,1790],{"class":735},[643,12822,12823],{"class":1038}," addToast",[643,12825,1459],{"class":726},[643,12827,1937],{"class":726},[643,12829,12830],{"class":2070},"message",[643,12832,742],{"class":726},[643,12834,2758],{"class":649},[643,12836,1180],{"class":726},[643,12838,1453],{"class":2070},[643,12840,742],{"class":726},[643,12842,1052],{"class":726},[643,12844,12781],{"class":653},[643,12846,1058],{"class":726},[643,12848,1469],{"class":726},[643,12850,1052],{"class":726},[643,12852,12790],{"class":653},[643,12854,1058],{"class":726},[643,12856,1459],{"class":726},[643,12858,1052],{"class":726},[643,12860,12781],{"class":653},[643,12862,1058],{"class":726},[643,12864,2261],{"class":726},[643,12866,1725],{"class":735},[643,12868,801],{"class":726},[643,12870,12871,12873,12875,12877,12879,12881,12883],{"class":645,"line":789},[643,12872,2770],{"class":735},[643,12874,3005],{"class":1038},[643,12876,1459],{"class":726},[643,12878,1570],{"class":1038},[643,12880,497],{"class":726},[643,12882,1575],{"class":666},[643,12884,1578],{"class":1046},[643,12886,12887,12890,12892,12894,12896,12898,12900,12902,12904,12906,12908,12910,12912,12914],{"class":645,"line":804},[643,12888,12889],{"class":1038},"    toasts",[643,12891,497],{"class":726},[643,12893,2056],{"class":1038},[643,12895,497],{"class":726},[643,12897,2904],{"class":666},[643,12899,1039],{"class":1046},[643,12901,7227],{"class":726},[643,12903,3005],{"class":1038},[643,12905,1180],{"class":726},[643,12907,12766],{"class":1038},[643,12909,1180],{"class":726},[643,12911,1453],{"class":1038},[643,12913,1665],{"class":726},[643,12915,1217],{"class":1046},[643,12917,12918,12921,12923,12925,12927],{"class":645,"line":826},[643,12919,12920],{"class":666},"    setTimeout",[643,12922,1039],{"class":1046},[643,12924,2389],{"class":726},[643,12926,1725],{"class":735},[643,12928,801],{"class":726},[643,12930,12931,12934,12936,12938,12940,12942,12944,12946,12948,12950,12952,12954,12956,12958,12960,12962,12964,12966],{"class":645,"line":847},[643,12932,12933],{"class":1038},"      toasts",[643,12935,497],{"class":726},[643,12937,2056],{"class":1038},[643,12939,1459],{"class":726},[643,12941,12744],{"class":1038},[643,12943,497],{"class":726},[643,12945,2056],{"class":1038},[643,12947,497],{"class":726},[643,12949,2402],{"class":666},[643,12951,1039],{"class":1046},[643,12953,2407],{"class":2070},[643,12955,1725],{"class":735},[643,12957,2457],{"class":1038},[643,12959,497],{"class":726},[643,12961,2957],{"class":1038},[643,12963,3144],{"class":726},[643,12965,3005],{"class":1038},[643,12967,1217],{"class":1046},[643,12969,12970,12972,12975],{"class":645,"line":868},[643,12971,2305],{"class":726},[643,12973,12974],{"class":4335}," 3000",[643,12976,1217],{"class":1046},[643,12978,12979],{"class":645,"line":888},[643,12980,951],{"class":726},[643,12982,12983],{"class":645,"line":894},[643,12984,1307],{"emptyLinePlaceholder":1306},[643,12986,12987,12989,12991,12993,12995,12997],{"class":645,"line":908},[643,12988,3469],{"class":1029},[643,12990,1070],{"class":726},[643,12992,12744],{"class":1038},[643,12994,1180],{"class":726},[643,12996,12823],{"class":1038},[643,12998,1195],{"class":726},[643,13000,13001],{"class":645,"line":929},[643,13002,957],{"class":726},[466,13004,13006],{"id":13005},"_105-due-dates-và-priority","10.5 Due dates và priority",[432,13008,13009,13010,742],{},"Mở rộng ",[487,13011,12112],{},[616,13013,13015],{"className":1014,"code":13014,"language":1016,"meta":624,"style":624},"export type Priority = 'low' | 'medium' | 'high'\n\nexport interface Todo {\n  id: string\n  title: string\n  completed: boolean\n  createdAt: Date\n  dueDate?: Date        \u002F\u002F Optional — ngày hết hạn\n  priority: Priority     \u002F\u002F Độ ưu tiên\n}\n",[487,13016,13017,13053,13057,13067,13075,13083,13091,13099,13112,13124],{"__ignoreMap":624},[643,13018,13019,13021,13023,13026,13028,13030,13033,13035,13037,13039,13042,13044,13046,13048,13051],{"class":645,"line":646},[643,13020,972],{"class":1029},[643,13022,1453],{"class":735},[643,13024,13025],{"class":649}," Priority",[643,13027,1459],{"class":726},[643,13029,1052],{"class":726},[643,13031,13032],{"class":653},"low",[643,13034,1058],{"class":726},[643,13036,1469],{"class":726},[643,13038,1052],{"class":726},[643,13040,13041],{"class":653},"medium",[643,13043,1058],{"class":726},[643,13045,1469],{"class":726},[643,13047,1052],{"class":726},[643,13049,13050],{"class":653},"high",[643,13052,1486],{"class":726},[643,13054,13055],{"class":645,"line":663},[643,13056,1307],{"emptyLinePlaceholder":1306},[643,13058,13059,13061,13063,13065],{"class":645,"line":672},[643,13060,972],{"class":1029},[643,13062,1334],{"class":735},[643,13064,1337],{"class":649},[643,13066,801],{"class":726},[643,13068,13069,13071,13073],{"class":645,"line":681},[643,13070,1349],{"class":1046},[643,13072,742],{"class":726},[643,13074,1354],{"class":649},[643,13076,13077,13079,13081],{"class":645,"line":789},[643,13078,1368],{"class":1046},[643,13080,742],{"class":726},[643,13082,1354],{"class":649},[643,13084,13085,13087,13089],{"class":645,"line":804},[643,13086,1386],{"class":1046},[643,13088,742],{"class":726},[643,13090,1391],{"class":649},[643,13092,13093,13095,13097],{"class":645,"line":826},[643,13094,1408],{"class":1046},[643,13096,742],{"class":726},[643,13098,1413],{"class":649},[643,13100,13101,13104,13107,13109],{"class":645,"line":847},[643,13102,13103],{"class":1046},"  dueDate",[643,13105,13106],{"class":726},"?:",[643,13108,2101],{"class":649},[643,13110,13111],{"class":1023},"        \u002F\u002F Optional — ngày hết hạn\n",[643,13113,13114,13117,13119,13121],{"class":645,"line":868},[643,13115,13116],{"class":1046},"  priority",[643,13118,742],{"class":726},[643,13120,13025],{"class":649},[643,13122,13123],{"class":1023},"     \u002F\u002F Độ ưu tiên\n",[643,13125,13126],{"class":645,"line":888},[643,13127,957],{"class":726},[626,13129],{},[427,13131,13133],{"id":13132},"_11-kiểm-tra-kết-quả","11. Kiểm tra kết quả",[432,13135,13136,13137,13139,13140,13142],{},"Sau khi code xong, chạy ",[487,13138,1274],{}," và mở ",[487,13141,1278],{},". Bạn nên thấy:",[11884,13144,13145,13151,13157,13163,13169,13175,13181,13187],{},[474,13146,13147,13150],{},[436,13148,13149],{},"Form Add"," ở đầu trang — gõ text vào và nhấn Enter hoặc click Add.",[474,13152,13153,13156],{},[436,13154,13155],{},"Todo item"," xuất hiện bên dưới — click checkbox để toggle completed.",[474,13158,13159,13162],{},[436,13160,13161],{},"Filter buttons"," (All \u002F Active \u002F Completed) — lọc danh sách todo.",[474,13164,13165,13168],{},[436,13166,13167],{},"Double-click"," vào todo title để edit — Enter\u002FEscape để save\u002Fhủy.",[474,13170,13171,13174],{},[436,13172,13173],{},"Hover"," vào dòng todo để thấy nút xóa (dấu ×).",[474,13176,13177,13180],{},[436,13178,13179],{},"Footer"," hiển thị số lượng active items + Clear completed.",[474,13182,13183,13186],{},[436,13184,13185],{},"Reload page"," — todo vẫn còn vì đã save vào localStorage.",[474,13188,13189,13192],{},[436,13190,13191],{},"Animation"," — thêm\u002Fxóa todo có transition mượt mà.",[449,13194,13196,13202,13227],{"color":12781,"icon":13195},"i-lucide-check-circle",[432,13197,13198,13199,13201],{},"Chúc mừng! Bạn đã hoàn thành ",[436,13200,438],{}," với Nuxt 4.",[432,13203,13204,13205,13208,13209,13211,13212,13214,13215,13217,13218,490,13220,490,13222,969,13224,13226],{},"Đây là project thực tế đầu tiên — áp dụng ",[436,13206,13207],{},"tất cả kiến thức Nuxt 4",": ",[487,13210,513],{}," shared state, ",[487,13213,525],{}," reactive derivation, ",[487,13216,533],{}," localStorage persistence, file-based routing, component auto-import, TypeScript types, ",[487,13219,565],{},[487,13221,584],{},[487,13223,592],{},[487,13225,595],{}," SEO.",[432,13228,13229],{},"Từ đây, bạn có thể tự tin xây dựng các project Nuxt 4 phức tạp hơn.",[449,13231,13233,13234,13237,13238,13240],{"color":451,"icon":13232},"i-lucide-arrow-right","Quay lại ",[13235,13236,358],"a",{"href":360}," để xem lại các section lý thuyết, hoặc ",[13235,13239,154],{"href":156}," để khám phá chủ đề khác.",[4298,13242,13243],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sqsOY, html code.shiki .sqsOY{--shiki-light:#8796B0;--shiki-default:#B2CCD6;--shiki-dark:#B2CCD6}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":624,"searchDepth":663,"depth":663,"links":13245},[13246,13250,13253,13256,13260,13270,13274,13276,13279,13281,13284,13291],{"id":429,"depth":663,"text":430,"children":13247},[13248,13249],{"id":468,"depth":672,"text":469},{"id":613,"depth":672,"text":614},{"id":630,"depth":663,"text":631,"children":13251},[13252],{"id":705,"depth":672,"text":706},{"id":1284,"depth":663,"text":1285,"children":13254},[13255],{"id":1489,"depth":672,"text":1490},{"id":1625,"depth":663,"text":13257,"children":13258},"3. Composable useTodos — Trái tim của app",[13259],{"id":3605,"depth":672,"text":3606},{"id":3817,"depth":663,"text":3818,"children":13261},[13262,13263,13264,13265,13266,13267,13268,13269],{"id":3821,"depth":672,"text":3822},{"id":4860,"depth":672,"text":1490},{"id":5028,"depth":672,"text":5029},{"id":6825,"depth":672,"text":1490},{"id":7081,"depth":672,"text":7082},{"id":7835,"depth":672,"text":1490},{"id":8057,"depth":672,"text":8058},{"id":9469,"depth":672,"text":1490},{"id":9726,"depth":663,"text":13271,"children":13272},"5. Pages — index.vue",[13273],{"id":10607,"depth":672,"text":1490},{"id":10744,"depth":663,"text":13275},"6. Root Layout — app.vue",{"id":11068,"depth":663,"text":11069,"children":13277},[13278],{"id":11655,"depth":672,"text":1490},{"id":11712,"depth":663,"text":13280},"8. App Configuration — app.config.ts",{"id":11873,"depth":663,"text":11874,"children":13282},[13283],{"id":11993,"depth":672,"text":11994},{"id":12174,"depth":663,"text":12175,"children":13285},[13286,13287,13288,13289,13290],{"id":12181,"depth":672,"text":12182},{"id":12396,"depth":672,"text":12397},{"id":12562,"depth":672,"text":12563},{"id":12708,"depth":672,"text":12709},{"id":13005,"depth":672,"text":13006},{"id":13132,"depth":663,"text":13133},"Hướng dẫn chi tiết từng bước xây dựng Todo App hoàn chỉnh với Nuxt 4 — composables, useState, components, TypeScript, localStorage.","md",null,{},{"title":380,"icon":383},{"title":422,"description":13292},"Q1OmSkZSqbUbsPbuMreRXJf5qmr6NZaeLKk5jPPneL0",[13300,13302],{"title":307,"path":377,"stem":378,"description":13301,"icon":196,"children":-1},"45+ câu hỏi Nuxt 4 + Vue 3 cho dev frontend \u002F fullstack — version mới nhất.",{"title":385,"path":386,"stem":387,"description":13303,"icon":390,"children":-1},"Học Tailwind CSS v4.3 (2026) — Oxide Engine, CSS-first config, OKLCH colors, container queries, 3D transforms. Từ cơ bản đến practical.",1783490360745]