{"data":[{"id":110682,"title":"Immigration Forms Automation and Immigration Process Automation: 8 Key Trends to Watch in 2027","slug":"ym400bQd491","url":"https:\/\/viblo.asia\/p\/immigration-forms-automation-and-immigration-process-automation-8-key-trends-to-watch-in-2027-ym400bQd491","user_id":207215,"moderation":"pending","transliterated":"immigration-forms-automation-and-immigration-process-automation-8-key-trends-to-watch-in-2027","contents_short":"Immigration processes involve extensive documentation, strict filing requirements, multiple approval stages, and frequent regulatory changes. For immigration law firms and corporate immigration teams, managing these activities manually can increase administrative workloads, create documentation inconsistencies, and delay application processing. Immigration Forms Automation is becoming an import...","contents":"Immigration processes involve extensive documentation, strict filing requirements, multiple approval stages, and frequent regulatory changes. For immigration law firms and corporate immigration teams, managing these activities manually can increase administrative workloads, create documentation inconsistencies, and delay application processing. ***[Immigration Forms Automation](https:\/\/hexacorp.com\/ai-solutions\/immigration-compliance-automation\/)*** is becoming an important approach to simplifying these operations by reducing repetitive data entry, improving document accuracy, and streamlining application preparation. As technology advances, immigration process automation is expected to evolve further in 2027, with artificial intelligence, intelligent document processing, and connected workflows influencing how immigration-related tasks are managed.\n![](https:\/\/images.viblo.asia\/c6f1c05a-9108-4da2-afe1-47e686aa19b4.png)\n\n## 8 Key Immigration Forms Automation Trends to Watch in 2027\n### 1. AI-Powered Immigration Forms Automation\nArtificial intelligence is expected to play a larger role in immigration documentation. AI-powered systems can extract information from uploaded documents, identify missing details, and populate relevant fields in immigration forms. These capabilities help reduce repetitive manual work while improving consistency across applications.\nIn 2027, more platforms are expected to incorporate intelligent validation features that identify incomplete information before forms move to the review stage. Human verification will remain essential for ensuring accuracy and regulatory compliance.\n### 2. Intelligent Document Processing and Data Extraction\nImmigration applications require information from passports, employment records, financial statements, educational certificates, and other supporting documents. Manually collecting and transferring this information can consume significant administrative time.\nIntelligent document processing combines optical character recognition (OCR), machine learning, and data extraction to convert information from documents into structured digital records. This development will strengthen Immigration Forms Automation by reducing repetitive data entry and improving document organization.\n### 3. End-to-End Immigration Process Automation\nAutomating individual forms is only one part of improving immigration operations. Organizations are increasingly looking toward connected workflows that manage the entire application lifecycle.\nImmigration process automation can connect client intake, document collection, form preparation, internal reviews, approval tracking, and application updates. In 2027, integrated workflows will become increasingly important for reducing disconnected activities and providing better visibility into application progress.\n### 4. Automated Compliance and Regulatory Monitoring\nImmigration regulations, filing requirements, and documentation standards can change frequently. Keeping track of these updates across different visa categories and jurisdictions presents an ongoing challenge.\nFuture automation platforms are expected to incorporate regulatory monitoring and configurable validation rules. These capabilities can help identify outdated templates, highlight missing documentation, and flag potential inconsistencies. However, legal professionals must verify regulatory interpretations and filing requirements before submission.\n### 5. AI-Driven Client Intake and Information Collection\nCollecting accurate client information is an important part of immigration case preparation. Traditional intake methods often involve lengthy questionnaires, email exchanges, and repeated requests for missing details.\nAI-assisted intake tools can guide applicants through structured questions, identify incomplete responses, and organize submitted information. Integrating these capabilities with Immigration Forms Automation can create a more consistent transition from initial consultation to application preparation.\n### 6. Cloud-Based Collaboration and Centralized Case Management\nImmigration cases frequently involve applicants, attorneys, paralegals, HR teams, and other stakeholders. Managing documents across disconnected systems can create communication gaps and version-control issues.\nCloud-based case management platforms provide centralized access to application records, supporting documents, and workflow updates. In 2027, cloud integration will continue supporting remote collaboration, controlled document access, and improved visibility throughout immigration workflows.\n### 7. Predictive Analytics for Application Tracking\nPredictive analytics is expected to become a valuable addition to immigration workflow management. By analyzing historical case information, workload patterns, and processing milestones, organizations can identify potential bottlenecks and improve resource planning.\nWhen combined with Immigration Forms Automation, analytics can help teams monitor application progress, identify delayed tasks, and prioritize cases requiring attention. These insights support operational planning without guaranteeing immigration approval outcomes or government processing timelines.\n### 8. Stronger Data Security and Access Governance\nImmigration records contain sensitive personal, financial, employment, and identity information. As more organizations adopt digital automation, protecting this information becomes increasingly important.\nFuture platforms will place greater emphasis on role-based access controls, encryption, audit trails, secure document storage, and data retention policies. Integrating security measures into immigration process automation can help organizations maintain accountability while protecting confidential applicant information.\n### The Future of Immigration Forms Automation in 2027\nThe future of immigration operations will depend on more than digitizing paperwork. Organizations will increasingly focus on connecting documentation, communication, compliance checks, and case management through intelligent workflows.\nImmigration Forms Automation will continue to support this transition by reducing repetitive administrative activities, improving information consistency, and creating more structured application processes. However, successful implementation will require a balance between automation, data protection, regulatory oversight, and human expertise.\nAs these eight trends develop in 2027, immigration law firms and corporate immigration departments will have greater opportunities to improve operational efficiency while maintaining accuracy and control throughout the application lifecycle","published_at":"2026-11-14T19:16:28.000000Z","scheduled_publish_at":null,"is_published":true,"is_shared":false,"updated_at":"2026-11-14T19:17:01.000000Z","edited_at":"2026-11-14T19:15:21.000000Z","translation_source":null,"trend_at":null,"promoted_at":null,"reading_time":3,"points":0,"views_count":1,"clips_count":0,"comments_count":0,"rated_value":null,"promoted":false,"trending":false,"is_draft":false,"is_public":true,"locale_code":"vi","is_video":false,"thumbnail_url":"https:\/\/images.viblo.asia\/ebb9ae37-fdfa-405f-9b0e-f55d25997f7f.png","user":{"data":{"id":207215,"url":"https:\/\/viblo.asia\/u\/Diwakarexe","avatar":"3a79ace6-6712-4596-8642-c60fc6e0daac.png","name":"Diwakar Exe","username":"Diwakarexe","followers_count":0,"reputation":0,"posts_count":20,"banned_at":null,"level_partner":null,"following":false}},"tags":{"data":[{"slug":"immigration","name":"Immigration"},{"slug":"automation","name":"Automation"},{"slug":"immigration-process","name":"Immigration Process"}]},"commentators":{"data":[]}},{"id":110679,"title":"The Four-Image Listing Set: What Each Shot Does (and How to Brief It)","slug":"3RL1BbpvVao","url":"https:\/\/viblo.asia\/p\/the-four-image-listing-set-what-each-shot-does-and-how-to-brief-it-3RL1BbpvVao","user_id":231989,"moderation":"pending","transliterated":"the-four-image-listing-set-what-each-shot-does-and-how-to-brief-it","contents_short":"Assign each image a job\nA four-image product listing can assign each image a distinct role: a white-background main image to identify the item, a lifestyle scene for context and scale, a close-up to show a real feature, and a second angle to reveal details hidden by the front view.\nBrief the four slots\nStart with sharp, evenly lit reference photos. Brief each slot separately, retaining material...","contents":"## Assign each image a job\nA four-image product listing can assign each image a distinct role: a white-background main image to identify the item, a lifestyle scene for context and scale, a close-up to show a real feature, and a second angle to reveal details hidden by the front view.\n## Brief the four slots\nStart with sharp, evenly lit reference photos. Brief each slot separately, retaining material, colour, shape, dimensions and label placement. Keep lighting and scale consistent. Main image: centred product, neutral light, soft contact shadow and no competing props. Lifestyle: quiet plausible setting, consistent colour and a clear scale cue. Close-up: real texture or feature, enough context to locate it. Second angle: a clearly different silhouette, relevant ports, clasps or labels.\n## Review AI output against references\nFor an AI-assisted workflow, specify invariant details in each prompt, compare every output against reference photos, and reject altered labels, geometry, shadows or invented features. A generated image is not exact reproduction and should not misrepresent the physical item.\n## One tool for this workflow\n[PixBloom](https:\/\/pixbloom.org\/en?utm_source=viblo) accepts up to four reference photos with English or Chinese prompts and can turn one product photo into main, lifestyle, close-up and second-angle images. Generation is about 30 seconds per image at roughly 2K. Credits are pay-per-image with no subscription or auto-renew; failed generations are refunded. Paid accounts include no watermark and commercial use. There is also a trending-prompts library with prompts and real results.\n## Publish and test carefully\nReview generated details before publishing, follow marketplace image rules, and test one changed image at a time rather than attributing outcomes to multiple simultaneous edits.","published_at":"2026-11-14T16:24:32.000000Z","scheduled_publish_at":null,"is_published":true,"is_shared":false,"updated_at":"2026-11-14T16:25:02.000000Z","edited_at":"2026-11-14T16:23:12.000000Z","translation_source":null,"trend_at":null,"promoted_at":null,"reading_time":1,"points":0,"views_count":1,"clips_count":0,"comments_count":0,"rated_value":null,"promoted":false,"trending":false,"is_draft":false,"is_public":true,"locale_code":"vi","is_video":false,"thumbnail_url":"https:\/\/images.viblo.asia\/89a9c95a-b09f-4d7e-ab80-4e96affdff87.png","user":{"data":{"id":231989,"url":"https:\/\/viblo.asia\/u\/jay_pixbloom","avatar":null,"name":"Jay PixBloom","username":"jay_pixbloom","followers_count":0,"reputation":0,"posts_count":1,"banned_at":null,"level_partner":null,"following":false}},"tags":{"data":[{"slug":"artificial-intelligence","name":"Artificial Intelligence"},{"slug":"image-generation","name":"image-generation"},{"slug":"ecommerce","name":"ecommerce"}]},"commentators":{"data":[]}},{"id":110677,"title":"Django: d\u00f9ng auto_now v\u00e0 auto_now_add \u0111\u00fang c\u00e1ch \u2014 khi n\u00e0o n\u00ean thay b\u1eb1ng default=timezone.now","slug":"k74a96Ql4eO","url":"https:\/\/viblo.asia\/p\/django-dung-auto-now-va-auto-now-add-dung-cach-khi-nao-nen-thay-bang-defaulttimezonenow-k74a96Ql4eO","user_id":59670,"moderation":null,"transliterated":"django-dung-auto-now-va-auto-now-add-dung-cach-khi-nao-nen-thay-bang-defaulttimezonenow","contents_short":"H\u1ea7u h\u1ebft model Django \u0111\u1ec1u c\u00f3 hai tr\u01b0\u1eddng th\u1eddi gian: createdat \u0111\u1ec3 bi\u1ebft b\u1ea3n ghi \u0111\u01b0\u1ee3c t\u1ea1o khi n\u00e0o v\u00e0 updatedat cho l\u1ea7n s\u1eeda cu\u1ed1i. C\u00e1ch nhanh nh\u1ea5t l\u00e0 autonowadd=True cho createdat v\u00e0 autonow=True cho updatedat \u2014 Django t\u1ef1 \u0111i\u1ec1n c\u1ea3 hai, b\u1ea1n kh\u00f4ng c\u1ea7n ch\u1ea1m v\u00e0o ch\u00fang. Nh\u01b0ng hai tham s\u1ed1 n\u00e0y c\u00f3 ba \"c\u00e1i b\u1eaby\" hay g\u1eb7p: kh\u00f4ng g\u00e1n \u0111\u01b0\u1ee3c ng\u00e0y c\u0169 khi import d\u1eef li\u1ec7u, update() h\u00e0ng lo\u1ea1t kh\u00f4ng \u0111\u1ed5i updatedat, v\u00e0 tr\u01b0\u1eddng...","contents":"H\u1ea7u h\u1ebft model Django \u0111\u1ec1u c\u00f3 hai tr\u01b0\u1eddng th\u1eddi gian: `created_at` \u0111\u1ec3 bi\u1ebft b\u1ea3n ghi \u0111\u01b0\u1ee3c t\u1ea1o khi n\u00e0o v\u00e0 `updated_at` cho l\u1ea7n s\u1eeda cu\u1ed1i. C\u00e1ch nhanh nh\u1ea5t l\u00e0 `auto_now_add=True` cho `created_at` v\u00e0 `auto_now=True` cho `updated_at` \u2014 Django t\u1ef1 \u0111i\u1ec1n c\u1ea3 hai, b\u1ea1n kh\u00f4ng c\u1ea7n ch\u1ea1m v\u00e0o ch\u00fang. Nh\u01b0ng hai tham s\u1ed1 n\u00e0y c\u00f3 ba \"c\u00e1i b\u1eaby\" hay g\u1eb7p: kh\u00f4ng g\u00e1n \u0111\u01b0\u1ee3c ng\u00e0y c\u0169 khi import d\u1eef li\u1ec7u, `update()` h\u00e0ng lo\u1ea1t kh\u00f4ng \u0111\u1ed5i `updated_at`, v\u00e0 tr\u01b0\u1eddng b\u1ecb \u1ea9n kh\u1ecfi form.\n\n## V\u1ea5n \u0111\u1ec1\n\nGi\u1ea3 s\u1eed b\u1ea1n import 500 b\u00e0i vi\u1ebft c\u0169 t\u1eeb blog kh\u00e1c v\u00e0 mu\u1ed1n gi\u1eef nguy\u00ean ng\u00e0y \u0111\u0103ng g\u1ed1c:\n\n```python\nfrom datetime import datetime\nfrom django.utils import timezone\n\nold_date = timezone.make_aware(datetime(2024, 5, 10, 9, 0))\n\n# Mu\u1ed1n gi\u1eef ng\u00e0y \u0111\u0103ng g\u1ed1c khi import...\npost = Post.objects.create(title=\"B\u00e0i c\u0169\", created_at=old_date)\nprint(post.created_at)  # H\u00f4m nay, kh\u00f4ng ph\u1ea3i 10\/05\/2024!\n```\n\nK\u1ebft qu\u1ea3: `created_at` b\u1ecb ghi \u0111\u00e8 b\u1eb1ng th\u1eddi \u0111i\u1ec3m hi\u1ec7n t\u1ea1i, ng\u00e0y g\u1ed1c m\u1ea5t. L\u00fd do: `auto_now_add` ch\u1ec9 ch\u1ea1y l\u00fac INSERT \u2014 m\u1ecdi gi\u00e1 tr\u1ecb b\u1ea1n truy\u1ec1n v\u00e0o `create()` \u0111\u1ec1u b\u1ecb thay b\u1eb1ng `timezone.now()`.\n\nB\u1eaby th\u1ee9 hai n\u1eb1m \u1edf c\u1eadp nh\u1eadt h\u00e0ng lo\u1ea1t:\n\n```python\nfrom django.utils import timezone\n\n# B\u1eaby th\u1ee9 hai: c\u1eadp nh\u1eadt h\u00e0ng lo\u1ea1t\nPost.objects.filter(status=\"draft\").update(status=\"published\")\n# updated_at c\u1ee7a 1.000 b\u00e0i vi\u1ebft v\u1eabn gi\u1eef nguy\u00ean!\n```\n\n`QuerySet.update()` ch\u1ea1y th\u1eb3ng SQL, kh\u00f4ng g\u1ecdi `save()` n\u00ean `auto_now` kh\u00f4ng k\u00edch ho\u1ea1t \u2014 ng\u01b0\u1eddi d\u00f9ng nh\u00ecn v\u00e0o \"c\u1eadp nh\u1eadt l\u1ea7n cu\u1ed1i\" s\u1ebd th\u1ea5y ng\u00e0y c\u0169 d\u00f9 n\u1ed9i dung v\u1eeba \u0111\u1ed5i.\n\n## Gi\u1ea3i ph\u00e1p\n\nMu\u1ed1n gi\u1eef ng\u00e0y g\u1ed1c khi import, \u0111\u1eebng d\u00f9ng `auto_now_add` cho `created_at`. Thay b\u1eb1ng `default=timezone.now` \u2014 l\u01b0u \u00fd truy\u1ec1n h\u00e0m, kh\u00f4ng g\u1ecdi h\u00e0m:\n\n```python\nfrom django.db import models\nfrom django.utils import timezone\n\nclass Post(models.Model):\n    title = models.CharField(max_length=200)\n    status = models.CharField(max_length=20, default=\"draft\")\n    created_at = models.DateTimeField(default=timezone.now)\n    updated_at = models.DateTimeField(default=timezone.now)\n```\n\nV\u1edbi c\u00e1ch n\u00e0y, t\u1ea1o m\u1edbi kh\u00f4ng truy\u1ec1n `created_at` th\u00ec Django v\u1eabn t\u1ef1 \u0111i\u1ec1n th\u1eddi gian hi\u1ec7n t\u1ea1i, c\u00f2n khi import b\u1ea1n truy\u1ec1n ng\u00e0y c\u0169 v\u00e0o v\u00e0 n\u00f3 \u0111\u01b0\u1ee3c gi\u1eef nguy\u00ean. Hai tr\u01b0\u1eddng n\u00e0y v\u1eabn ch\u1ec9nh \u0111\u01b0\u1ee3c trong admin v\u00e0 ModelForm, kh\u00e1c v\u1edbi `auto_now`\/`auto_now_add` v\u1ed1n b\u1ecb \u1ea9n.\n\nV\u1edbi c\u1eadp nh\u1eadt h\u00e0ng lo\u1ea1t, truy\u1ec1n `updated_at` t\u01b0\u1eddng minh:\n\n```python\nfrom django.utils import timezone\n\nPost.objects.filter(status=\"draft\").update(\n    status=\"published\",\n    updated_at=timezone.now(),\n)\n```\n\nC\u00f2n n\u1ebfu v\u1eabn mu\u1ed1n gi\u1eef `auto_now_add` v\u00ec kh\u00f4ng bao gi\u1edd c\u1ea7n s\u1eeda `created_at`, v\u1eabn d\u00f9ng \u0111\u01b0\u1ee3c \u2014 ch\u1ec9 nh\u1edb g\u00e1n ng\u00e0y c\u0169 sau khi t\u1ea1o, v\u00ec `auto_now_add` ch\u1ec9 ch\u1ea1y l\u00fac INSERT n\u00ean l\u1ea7n `save()` th\u1ee9 hai kh\u00f4ng ghi \u0111\u00e8:\n\n```python\npost = Post.objects.create(title=\"B\u00e0i c\u0169\")\npost.created_at = old_date  # G\u00e1n sau khi t\u1ea1o th\u00ec gi\u1eef \u0111\u01b0\u1ee3c\npost.save()\n```\n\n## \u00c1p d\u1ee5ng v\u00e0o d\u1ef1 \u00e1n\n\nQuy t\u1eafc ch\u1ecdn nhanh: d\u00f9ng `auto_now_add`\/`auto_now` cho c\u00e1c b\u1ea3ng ch\u1ec9 c\u1ea7n \"\u0111\u00f3ng d\u1ea5u\" th\u1eddi gian nh\u01b0 log, l\u1ecbch s\u1eed thao t\u00e1c. D\u00f9ng `default=timezone.now` khi ng\u00e0y gi\u1edd l\u00e0 d\u1eef li\u1ec7u nghi\u1ec7p v\u1ee5 \u2014 ng\u00e0y \u0111\u0103ng b\u00e0i, ng\u00e0y h\u1ebft h\u1ea1n, ng\u00e0y b\u1eaft \u0111\u1ea7u s\u1ef1 ki\u1ec7n \u2014 v\u00ec s\u1edbm mu\u1ed9n b\u1ea1n c\u0169ng c\u1ea7n s\u1eeda tay, import d\u1eef li\u1ec7u c\u0169 ho\u1eb7c cho ng\u01b0\u1eddi d\u00f9ng ch\u1ec9nh trong admin. V\u00e0 sau m\u1ed7i l\u1ea7n d\u00f9ng `QuerySet.update()` hay `bulk_update()`, ki\u1ec3m tra l\u1ea1i xem `updated_at` c\u00f3 c\u1ea7n c\u1eadp nh\u1eadt t\u01b0\u1eddng minh kh\u00f4ng.\n\n## C\u00e2u h\u1ecfi th\u01b0\u1eddng g\u1eb7p\n\n**`auto_now` v\u00e0 `auto_now_add` kh\u00e1c nhau th\u1ebf n\u00e0o?**\n`auto_now_add` ch\u1ec9 ch\u1ea1y l\u00fac t\u1ea1o m\u1edbi (INSERT), `auto_now` ch\u1ea1y m\u1ed7i l\u1ea7n `save()`. C\u1ea3 hai \u0111\u1ec1u t\u1ef1 \u0111i\u1ec1n `timezone.now()` v\u00e0 kh\u00f4ng s\u1eeda \u0111\u01b0\u1ee3c qua form.\n\n**V\u00ec sao `update()` kh\u00f4ng \u0111\u1ed5i `updated_at`?**\n`update()` ch\u1ea1y SQL tr\u1ef1c ti\u1ebfp, b\u1ecf qua `save()` n\u00ean `pre_save` c\u1ee7a `auto_now` kh\u00f4ng ch\u1ea1y. Truy\u1ec1n `updated_at=timezone.now()` v\u00e0o `update()` \u0111\u1ec3 \u0111\u1ed5i t\u01b0\u1eddng minh.\n\n**`default=timezone.now` v\u00e0 `default=timezone.now()` kh\u00e1c nhau ra sao?**\nTruy\u1ec1n h\u00e0m (kh\u00f4ng ngo\u1eb7c) th\u00ec Django g\u1ecdi l\u1ea1i m\u1ed7i l\u1ea7n t\u1ea1o b\u1ea3n ghi, m\u1ed7i b\u1ea3n ghi c\u00f3 gi\u1edd ri\u00eang. G\u1ecdi h\u00e0m s\u1eb5n (c\u00f3 ngo\u1eb7c) th\u00ec m\u1ecdi b\u1ea3n ghi d\u00f9ng chung m\u1ed9t th\u1eddi \u0111i\u1ec3m t\u00ednh l\u00fac import module \u2014 l\u1ed7i kinh \u0111i\u1ec3n.\n\n\ud83d\udcac B\u1ea1n \u0111\u00e3 th\u1eed c\u00e1ch n\u00e0y trong d\u1ef1 \u00e1n c\u1ee7a m\u00ecnh ch\u01b0a? N\u1ebfu g\u1eb7p l\u1ed7i ho\u1eb7c c\u00f3 c\u00e1ch l\u00e0m g\u1ecdn h\u01a1n, \u0111\u1ec3 l\u1ea1i b\u00ecnh lu\u1eadn b\u00ean d\u01b0\u1edbi nh\u00e9.\n\n## Ngu\u1ed3n tham kh\u1ea3o\n\n- Django docs \u2014 DateField.auto_now \/ auto_now_add: https:\/\/docs.djangoproject.com\/en\/6.1\/ref\/models\/fields\/#django.db.models.DateField.auto_now\n- Django docs \u2014 QuerySet.update(): https:\/\/docs.djangoproject.com\/en\/6.1\/ref\/models\/querysets\/#update\n- Django docs \u2014 django.utils.timezone.now: https:\/\/docs.djangoproject.com\/en\/6.1\/ref\/utils\/#django.utils.timezone.now\n\n---\n\n\u0110\u1ecdc b\u1ea3n \u0111\u1ea7y \u0111\u1ee7 b\u1eb1ng ti\u1ebfng Anh t\u1ea1i: https:\/\/linhfish.dev\/en\/2026\/11\/14\/django-use-auto_now-and-auto_now_add-correctly-when-to-switch-to-defaulttimezone-now\/","published_at":"2026-11-14T15:32:37.000000Z","scheduled_publish_at":null,"is_published":true,"is_shared":false,"updated_at":"2026-11-15T01:00:15.000000Z","edited_at":"2026-11-14T15:31:24.000000Z","translation_source":null,"trend_at":null,"promoted_at":null,"reading_time":4,"points":0,"views_count":13,"clips_count":0,"comments_count":0,"rated_value":null,"promoted":false,"trending":false,"is_draft":false,"is_public":true,"locale_code":"vi","is_video":false,"thumbnail_url":"https:\/\/images.viblo.asia\/f276d918-bbe3-4a85-bf5e-241c3e652082.png","user":{"data":{"id":59670,"url":"https:\/\/viblo.asia\/u\/linhfishCR7","avatar":"5105dbd5-d474-47dd-a13e-f60f64a0370e.png","name":"Linh Fish Dev","username":"linhfishCR7","followers_count":0,"reputation":0,"posts_count":9,"banned_at":null,"level_partner":null,"following":false}},"tags":{"data":[{"slug":"django","name":"Django"},{"slug":"python","name":"Python"},{"slug":"web-development","name":"Web Development"}]},"commentators":{"data":[]}},{"id":110673,"title":"CHINH PH\u1ee4C ANZ SAU 5 N\u0102M L\u00c0M BACKEND ENGINEER C\u1ee6A N\u1eee H\u1eccC VI\u00caN","slug":"R5JRQrgr4Gv","url":"https:\/\/viblo.asia\/p\/chinh-phuc-anz-sau-5-nam-lam-backend-engineer-cua-nu-hoc-vien-R5JRQrgr4Gv","user_id":148488,"moderation":null,"transliterated":"chinh-phuc-anz-sau-5-nam-lam-backend-engineer-cua-nu-hoc-vien","contents_short":"Trong h\u00e0nh tr\u00ecnh s\u1ef1 nghi\u1ec7p c\u00f4ng ngh\u1ec7, vi\u1ec7c t\u00ecm ki\u1ebfm c\u01a1 h\u1ed9i t\u1ea1i c\u00e1c t\u1eadp \u0111o\u00e0n qu\u1ed1c t\u1ebf lu\u00f4n l\u00e0 m\u1ee5c ti\u00eau c\u1ee7a nhi\u1ec1u k\u1ef9 s\u01b0 ph\u1ea7n m\u1ec1m. V\u1edbi m\u1ed9t k\u1ef9 s\u01b0 backend c\u00f3 g\u1ea7n 5 n\u0103m kinh nghi\u1ec7m l\u00e0m vi\u1ec7c t\u1ea1i Viettel con \u0111\u01b0\u1eddng \u0111\u1ebfn v\u1edbi ANZ kh\u00f4ng ch\u1ec9 l\u00e0 c\u00e2u chuy\u1ec7n c\u1ee7a n\u1ed7 l\u1ef1c c\u00e1 nh\u00e2n m\u00e0 c\u00f2n l\u00e0 minh ch\u1ee9ng cho s\u1ee9c m\u1ea1nh c\u1ee7a vi\u1ec7c h\u1ecdc t\u1eadp b\u00e0i b\u1ea3n v\u00e0 \u0111\u1ecbnh h\u01b0\u1edbng \u0111\u00fang \u0111\u1eafn.\n\nC\u00f9ng Engineer Pro tr\u00f2 chuy\u1ec7n v\u1edbi n\u1eef h\u1ecdc vi\u00ean n\u00e0y \u0111\u1ec3 h...","contents":"Trong h\u00e0nh tr\u00ecnh s\u1ef1 nghi\u1ec7p c\u00f4ng ngh\u1ec7, vi\u1ec7c t\u00ecm ki\u1ebfm c\u01a1 h\u1ed9i t\u1ea1i c\u00e1c t\u1eadp \u0111o\u00e0n qu\u1ed1c t\u1ebf lu\u00f4n l\u00e0 m\u1ee5c ti\u00eau c\u1ee7a nhi\u1ec1u k\u1ef9 s\u01b0 ph\u1ea7n m\u1ec1m. V\u1edbi m\u1ed9t k\u1ef9 s\u01b0 backend c\u00f3 g\u1ea7n 5 n\u0103m kinh nghi\u1ec7m l\u00e0m vi\u1ec7c t\u1ea1i Viettel con \u0111\u01b0\u1eddng \u0111\u1ebfn v\u1edbi ANZ kh\u00f4ng ch\u1ec9 l\u00e0 c\u00e2u chuy\u1ec7n c\u1ee7a n\u1ed7 l\u1ef1c c\u00e1 nh\u00e2n m\u00e0 c\u00f2n l\u00e0 minh ch\u1ee9ng cho s\u1ee9c m\u1ea1nh c\u1ee7a vi\u1ec7c h\u1ecdc t\u1eadp b\u00e0i b\u1ea3n v\u00e0 \u0111\u1ecbnh h\u01b0\u1edbng \u0111\u00fang \u0111\u1eafn. \n\nC\u00f9ng Engineer Pro tr\u00f2 chuy\u1ec7n v\u1edbi n\u1eef h\u1ecdc vi\u00ean n\u00e0y \u0111\u1ec3 hi\u1ec3u h\u01a1n v\u1ec1 qu\u00e1 tr\u00ecnh h\u1ecdc t\u1eadp, \u00f4n luy\u1ec7n v\u00e0 chinh ph\u1ee5c th\u00e0nh c\u00f4ng bu\u1ed5i ph\u1ecfng v\u1ea5n t\u1ea1i ANZ.\n\n![image.png](https:\/\/images.viblo.asia\/4ded5593-a266-4179-830c-9e92105dfe66.png)\n\n**B\u1ea1n c\u00f3 th\u1ec3 gi\u1edbi thi\u1ec7u m\u1ed9t ch\u00fat v\u1ec1 b\u1ea3n th\u00e2n v\u00e0 h\u00e0nh tr\u00ecnh s\u1ef1 nghi\u1ec7p c\u1ee7a m\u00ecnh kh\u00f4ng?**\n\nM\u00ecnh t\u1ed1t nghi\u1ec7p \u0110\u1ea1i h\u1ecdc B\u00e1ch Khoa H\u00e0 N\u1ed9i v\u00e0 hi\u1ec7n \u0111ang l\u00e0 backend engineer. Sau khi ra tr\u01b0\u1eddng, m\u00ecnh l\u00e0m vi\u1ec7c t\u1ea1i Viettel v\u00e0 \u0111\u1ebfn nay \u0111\u00e3 g\u1eafn b\u00f3 g\u1ea7n 5 n\u0103m. C\u00f4ng vi\u1ec7c hi\u1ec7n t\u1ea1i c\u1ee7a m\u00ecnh v\u1eabn l\u00e0 ph\u00e1t tri\u1ec3n h\u1ec7 th\u1ed1ng backend t\u1ea1i \u0111\u00e2y.\n\n**B\u1ea1n \u0111\u00e3 ch\u00ednh th\u1ee9c nh\u1eadn \u0111\u01b0\u1ee3c offer t\u1eeb ANZ ch\u01b0a?**\n\nM\u00ecnh v\u1eeba nh\u1eadn \u0111\u01b0\u1ee3c offer c\u00e1ch \u0111\u00e2y hai ng\u00e0y. \u0110\u00e2y l\u00e0 c\u01a1 h\u1ed9i r\u1ea5t \u0111\u00e1ng qu\u00fd v\u1edbi m\u00ecnh v\u00e0 m\u00ecnh c\u00f3 k\u1ebf ho\u1ea1ch b\u1eaft \u0111\u1ea7u l\u00e0m vi\u1ec7c ch\u00ednh th\u1ee9c t\u1ea1i \u0111\u00e2y trong th\u1eddi gian t\u1edbi.\n\n**Trong qu\u00e1 tr\u00ecnh h\u1ecdc t\u1ea1i Engineer Pro, b\u1ea1n \u0111\u00e3 tham gia nh\u1eefng kh\u00f3a h\u1ecdc n\u00e0o?**\n\nM\u00ecnh g\u1ea7n nh\u01b0 h\u1ecdc h\u1ebft c\u00e1c kh\u00f3a t\u1ea1i Engineer Pro: t\u1eeb DSA 1, 2, 3 \u0111\u1ebfn System Design 1, 2, kh\u00f3a EV v\u00e0 c\u1ea3 Java. C\u00f3 th\u1ec3 n\u00f3i l\u00e0 m\u00ecnh \u0111\u00e3 \u0111i kh\u00e1 s\u00e2u v\u00e0o to\u00e0n b\u1ed9 ch\u01b0\u01a1ng tr\u00ecnh.\n\n**V\u00ec sao b\u1ea1n l\u1ea1i l\u1ef1a ch\u1ecdn h\u1ecdc t\u1ea1i Engineer Pro thay v\u00ec c\u00e1c trung t\u00e2m kh\u00e1c?**\n\nKho\u1ea3ng n\u0103m 2023, m\u00ecnh b\u1eaft \u0111\u1ea7u t\u00ecm ki\u1ebfm c\u01a1 h\u1ed9i m\u1edbi v\u00e0 ch\u1ee7 \u0111\u1ed9ng t\u00ecm hi\u1ec3u c\u00e1c kh\u00f3a h\u1ecdc tr\u00ean m\u1ea1ng. Khi xem video tr\u00ean YouTube v\u00e0 c\u00e1c n\u1ed9i dung t\u1eeb Engineer Pro, m\u00ecnh c\u1ea3m nh\u1eadn \u0111\u01b0\u1ee3c s\u1ef1 t\u00e2m huy\u1ebft v\u00e0 \u0111\u1ecbnh h\u01b0\u1edbng r\u00f5 r\u00e0ng m\u00e0 c\u00e1c anh mang l\u1ea1i. \u0110i\u1ec1u \u0111\u00f3 khi\u1ebfn m\u00ecnh tin t\u01b0\u1edfng r\u1eb1ng Engineer Pro s\u1ebd l\u00e0 n\u01a1i gi\u00fap m\u00ecnh ti\u1ebfn xa h\u01a1n trong s\u1ef1 nghi\u1ec7p.\n\n**Sau khi ho\u00e0n th\u00e0nh c\u00e1c kh\u00f3a h\u1ecdc, b\u1ea1n \u0111\u00e1nh gi\u00e1 th\u1ebf n\u00e0o v\u1ec1 ch\u1ea5t l\u01b0\u1ee3ng gi\u1ea3ng d\u1ea1y v\u00e0 ki\u1ebfn th\u1ee9c t\u1ea1i \u0111\u00e2y?**\n\nKi\u1ebfn th\u1ee9c r\u1ea5t c\u00f3 h\u1ec7 th\u1ed1ng v\u00e0 \u0111\u1ecbnh h\u01b0\u1edbng r\u00f5 r\u00e0ng. Tr\u01b0\u1edbc \u0111\u00e2y m\u00ecnh kh\u00e1 m\u00f4ng lung, kh\u00f4ng bi\u1ebft n\u00ean b\u1eaft \u0111\u1ea7u t\u1eeb \u0111\u00e2u, nh\u01b0ng sau khi h\u1ecdc, m\u00ecnh c\u00f3 l\u1ed9 tr\u00ecnh c\u1ee5 th\u1ec3 \u0111\u1ec3 t\u1eadp trung r\u00e8n luy\u1ec7n. C\u00e1c mentor gi\u1ea3ng d\u1ea1y r\u1ea5t nhi\u1ec7t t\u00ecnh, gi\u1ea3i th\u00edch d\u1ec5 hi\u1ec3u v\u00e0 gi\u00fap m\u00ecnh \u201cv\u1ee1\u201d ra nhi\u1ec1u ki\u1ebfn th\u1ee9c quan tr\u1ecdng. Trong s\u1ed1 \u0111\u00f3, anh L\u00e2m l\u00e0 ng\u01b0\u1eddi m\u00ecnh \u1ea5n t\u01b0\u1ee3ng nh\u1ea5t v\u00ec \u0111\u00e3 h\u1ed7 tr\u1ee3 m\u00ecnh r\u1ea5t nhi\u1ec1u trong su\u1ed1t qu\u00e1 tr\u00ecnh h\u1ecdc.\n\n**B\u1ea1n c\u00f3 th\u1ec3 chia s\u1ebb l\u1ed9 tr\u00ecnh \u00f4n luy\u1ec7n tr\u01b0\u1edbc khi ph\u1ecfng v\u1ea5n t\u1ea1i ANZ kh\u00f4ng?**\n\nM\u00ecnh ch\u1ee7 y\u1ebfu tu\u00e2n theo c\u00e1c l\u1ed9 tr\u00ecnh h\u1ecdc m\u00e0 Engineer Pro cung c\u1ea5p. Ngo\u00e0i vi\u1ec7c luy\u1ec7n thu\u1eadt to\u00e1n theo t\u1eebng ch\u1ee7 \u0111\u1ec1, m\u00ecnh c\u00f2n t\u1eadp trung v\u00e0o system design theo \u0111\u00fang c\u00e1c b\u01b0\u1edbc h\u01b0\u1edbng d\u1eabn. Ngo\u00e0i ra, m\u00ecnh c\u0169ng \u0111\u01b0\u1ee3c anh L\u00e2m gi\u1edbi thi\u1ec7u \u0111\u1ec3 apply v\u00e0o ANZ.\n\n**Quy tr\u00ecnh ph\u1ecfng v\u1ea5n t\u1ea1i ANZ di\u1ec5n ra nh\u01b0 th\u1ebf n\u00e0o?**\n\nC\u00f3 hai v\u00f2ng ph\u1ecfng v\u1ea5n.\n\n - V\u00f2ng 1 g\u1ed3m hai ph\u1ea7n: thu\u1eadt to\u00e1n k\u1ebft h\u1ee3p v\u1edbi c\u00e1c c\u00e2u h\u1ecfi v\u1ec1 h\u00e0nh vi, kinh nghi\u1ec7m l\u00e0m vi\u1ec7c v\u00e0 ki\u1ebfn th\u1ee9c n\u1ec1n t\u1ea3ng. M\u1ed7i ph\u1ea7n k\u00e9o d\u00e0i kho\u1ea3ng h\u01a1n 1 ti\u1ebfng.\n\n - V\u00f2ng 2 t\u1eadp trung v\u00e0o system design, d\u1ef1 ki\u1ebfn 90 ph\u00fat nh\u01b0ng h\u00f4m ph\u1ecfng v\u1ea5n k\u00e9o d\u00e0i \u0111\u1ebfn 2 ti\u1ebfng. T\u1ea5t c\u1ea3 \u0111\u1ec1u di\u1ec5n ra b\u1eb1ng ti\u1ebfng Anh.\n\nM\u1ed9t ng\u00e0y sau ph\u1ecfng v\u1ea5n m\u00ecnh \u0111\u00e3 nh\u1eadn \u0111\u01b0\u1ee3c th\u00f4ng b\u00e1o, v\u00e0 ch\u1ec9 hai ng\u00e0y sau l\u00e0 offer ch\u00ednh th\u1ee9c \u0111\u01b0\u1ee3c g\u1eedi \u0111\u1ebfn.\n\n**B\u1ea1n c\u00f3 g\u1eb7p kh\u00f3 kh\u0103n g\u00ec trong qu\u00e1 tr\u00ecnh ph\u1ecfng v\u1ea5n kh\u00f4ng?**\n\n\u1ede v\u00f2ng 1, ban \u0111\u1ea7u m\u00ecnh h\u01a1i l\u00fang t\u00fang v\u1edbi b\u00e0i thu\u1eadt to\u00e1n v\u00e0 trong vi\u1ec7c giao ti\u1ebfp v\u1edbi interviewer. Tuy nhi\u00ean, sau khi b\u00ecnh t\u0129nh l\u1ea1i, m\u00ecnh \u0111\u00e3 t\u00ecm ra h\u01b0\u1edbng gi\u1ea3i quy\u1ebft. Nh\u1edd ki\u1ebfn th\u1ee9c t\u1eeb Engineer Pro, m\u00ecnh c\u1ea3m th\u1ea5y ho\u00e0n to\u00e0n \u0111\u1ee7 n\u1ec1n t\u1ea3ng \u0111\u1ec3 v\u01b0\u1ee3t qua c\u00e1c v\u00f2ng th\u1eadm ch\u00ed c\u00f2n d\u01b0 ki\u1ebfn th\u1ee9c \u0111\u1ec3 \u1ee9ng d\u1ee5ng.\n\n**B\u1ea1n c\u00f3 l\u1eddi khuy\u00ean n\u00e0o d\u00e0nh cho nh\u1eefng b\u1ea1n \u0111ang chu\u1ea9n b\u1ecb apply v\u00e0o c\u00e1c c\u00f4ng ty nh\u01b0 ANZ kh\u00f4ng?**\n\nV\u1ec1 thu\u1eadt to\u00e1n, n\u00ean chia nh\u1ecf ki\u1ebfn th\u1ee9c th\u00e0nh t\u1eebng ch\u1ee7 \u0111\u1ec1 v\u00e0 luy\u1ec7n t\u1eadp theo t\u1eebng ph\u1ea7n \u0111\u1ec3 n\u1eafm ch\u1eafc n\u1ec1n t\u1ea3ng. C\u00f2n v\u1edbi system design, h\u00e3y h\u1ecdc v\u00e0 \u00e1p d\u1ee5ng theo t\u1eebng b\u01b0\u1edbc m\u00e0 mentor h\u01b0\u1edbng d\u1eabn. Quan tr\u1ecdng nh\u1ea5t l\u00e0 ki\u00ean tr\u00ec v\u00e0 c\u00f3 l\u1ed9 tr\u00ecnh r\u00f5 r\u00e0ng.\n\n**C\u1ed9ng \u0111\u1ed3ng Engineer Pro \u0111\u00e3 gi\u00fap b\u1ea1n nh\u01b0 th\u1ebf n\u00e0o trong qu\u00e1 tr\u00ecnh h\u1ecdc v\u00e0 ph\u1ecfng v\u1ea5n?**\n\nM\u00ecnh \u0111\u00e3 tham gia nhi\u1ec1u ho\u1ea1t \u0111\u1ed9ng nh\u01b0 webinar, bu\u1ed5i review CV v\u00e0 th\u1eadm ch\u00ed \u0111\u01b0\u1ee3c c\u00e1c anh review CV tr\u1ef1c ti\u1ebfp tr\u00ean livestream. Nh\u1edd \u0111\u00f3, m\u00ecnh c\u00f3 c\u01a1 h\u1ed9i trau d\u1ed3i ki\u1ebfn th\u1ee9c, c\u1ea3i thi\u1ec7n h\u1ed3 s\u01a1 v\u00e0 n\u00e2ng cao k\u1ef9 n\u0103ng ph\u1ecfng v\u1ea5n.\n\n**D\u1ef1 \u0111\u1ecbnh s\u1eafp t\u1edbi c\u1ee7a b\u1ea1n l\u00e0 g\u00ec?**\n\nM\u00ecnh s\u1ebd ti\u1ebfp t\u1ee5c theo \u0111u\u1ed5i con \u0111\u01b0\u1eddng backend engineer v\u00e0 ph\u00e1t tri\u1ec3n th\u00eam c\u00e1c k\u1ef9 n\u0103ng m\u1edbi. Hi\u1ec7n t\u1ea1i m\u00ecnh \u0111\u00e3 \u0111\u0103ng k\u00fd kh\u00f3a System Design 2 \u0111\u1ec3 m\u1edf r\u1ed9ng ki\u1ebfn th\u1ee9c m\u00e0 tr\u01b0\u1edbc \u0111\u00e2y ch\u01b0a c\u00f3 c\u01a1 h\u1ed9i ti\u1ebfp c\u1eadn.\n\n*T\u1eeb m\u1ed9t k\u1ef9 s\u01b0 backend t\u1ea1i Viettel \u0111\u1ebfn v\u1ecb tr\u00ed m\u1edbi t\u1ea1i ANZ, h\u00e0nh tr\u00ecnh c\u1ee7a b\u1ea1n h\u1ecdc vi\u00ean l\u00e0 minh ch\u1ee9ng r\u00f5 n\u00e9t cho gi\u00e1 tr\u1ecb c\u1ee7a vi\u1ec7c h\u1ecdc t\u1eadp b\u00e0i b\u1ea3n, chu\u1ea9n b\u1ecb k\u1ef9 l\u01b0\u1ee1ng v\u00e0 \u0111\u1ecbnh h\u01b0\u1edbng r\u00f5 r\u00e0ng. V\u1edbi n\u1ec1n t\u1ea3ng v\u1eefng ch\u1eafc c\u00f9ng tinh th\u1ea7n kh\u00f4ng ng\u1eebng h\u1ecdc h\u1ecfi, \u0111\u00e2y kh\u00f4ng ch\u1ec9 m\u1edf ra c\u01a1 h\u1ed9i m\u1edbi cho b\u1ea3n th\u00e2n m\u00e0 c\u00f2n truy\u1ec1n c\u1ea3m h\u1ee9ng cho nhi\u1ec1u k\u1ef9 s\u01b0 ph\u1ea7n m\u1ec1m kh\u00e1c tr\u00ean con \u0111\u01b0\u1eddng chinh ph\u1ee5c nh\u1eefng th\u1eed th\u00e1ch l\u1edbn h\u01a1n trong s\u1ef1 nghi\u1ec7p.*\n\n_________________________________\n\nEngineer Pro l\u00e0 m\u1ed9t trung t\u00e2m \u0111\u00e0o t\u1ea1o c\u00e1c kh\u00f3a h\u1ecdc chuy\u00ean s\u00e2u d\u00e0nh cho c\u00e1c software engineer. V\u1edbi 100% gi\u1ea3ng vi\u00ean \u0111\u1ebfn t\u1eeb c\u00e1c Big Tech nh\u01b0 Google, Amazon, Shopee, TikTok, \u2026 Engineer Pro \u0111\u1ea3m b\u1ea3o ch\u1ea5t l\u01b0\u1ee3ng gi\u1ea3ng d\u1ea1y v\u00e0 l\u1ed9 tr\u00ecnh h\u1ecdc t\u1eadp r\u00f5 r\u00e0ng, t\u1eeb c\u01a1 b\u1ea3n \u0111\u1ebfn n\u00e2ng cao, gi\u00fap h\u1ecdc vi\u00ean t\u1ef1 tin \u1ee9ng tuy\u1ec3n v\u00e0o c\u00e1c v\u1ecb tr\u00ed software engineer trong ng\u00e0nh c\u00f4ng ngh\u1ec7 n\u00e0y.\n\nTh\u00f4ng tin li\u00ean h\u1ec7:\n\n - Website: https:\/\/engineerprogurus.com\/ \n\n - Fanpage: https:\/\/www.facebook.com\/EngineerPro.Official \n\n - Youtube: https:\/\/www.youtube.com\/@EngineerPro-Official","published_at":"2026-11-14T13:21:39.000000Z","scheduled_publish_at":null,"is_published":true,"is_shared":false,"updated_at":"2026-11-14T22:30:02.000000Z","edited_at":"2026-11-14T13:21:34.000000Z","translation_source":null,"trend_at":null,"promoted_at":null,"reading_time":8,"points":0,"views_count":9,"clips_count":0,"comments_count":0,"rated_value":null,"promoted":false,"trending":false,"is_draft":false,"is_public":true,"locale_code":"vi","is_video":false,"thumbnail_url":"https:\/\/images.viblo.asia\/0aed2df1-a88e-4c8c-8193-1d595707c5b3.png","user":{"data":{"id":148488,"url":"https:\/\/viblo.asia\/u\/EngineerPro","avatar":"7e978c37-0f70-45a7-ae51-ab4efb3eddb5.jpg","name":"EngineerPro","username":"EngineerPro","followers_count":172,"reputation":1867,"posts_count":102,"banned_at":null,"level_partner":null,"following":false}},"tags":{"data":[{"slug":"atbigtech","name":"@BigTech"},{"slug":"atengineerpro","name":"@EngineerPro"},{"slug":"atinterview","name":"@Interview"}]},"commentators":{"data":[]}},{"id":107349,"title":"Series Java Swing B\u00e0i 28: WindowListener - X\u1eed l\u00fd logic khi thu nh\u1ecf, ph\u00f3ng to v\u00e0 C\u1ea3nh b\u00e1o tr\u01b0\u1edbc khi tho\u00e1t","slug":"gdJzvPwjJz5","url":"https:\/\/viblo.asia\/p\/series-java-swing-bai-28-windowlistener-xu-ly-logic-khi-thu-nho-phong-to-va-canh-bao-truoc-khi-thoat-gdJzvPwjJz5","user_id":182653,"moderation":null,"transliterated":"series-java-swing-bai-28-windowlistener-xu-ly-logic-khi-thu-nho-phong-to-va-canh-bao-truoc-khi-thoat","contents_short":"M\u1ecdi c\u1eeda s\u1ed5 \u1ee9ng d\u1ee5ng tr\u00ean h\u1ec7 \u0111i\u1ec1u h\u00e0nh \u0111\u1ec1u c\u00f3 m\u1ed9t v\u00f2ng \u0111\u1eddi: M\u1edf l\u00ean, \u0111\u01b0\u1ee3c ng\u01b0\u1eddi d\u00f9ng s\u1eed d\u1ee5ng (Activate), thu nh\u1ecf xu\u1ed1ng thanh Taskbar (Minimize), ph\u00f3ng to tr\u1edf l\u1ea1i, v\u00e0 cu\u1ed1i c\u00f9ng l\u00e0 b\u1ecb \u0111\u00f3ng (b\u1ea5m n\u00fat X).\n\n\u0110\u00f4i khi, b\u1ea1n mu\u1ed1n \u1ee9ng d\u1ee5ng c\u1ee7a m\u00ecnh \"ph\u1ea3n \u1ee9ng\" l\u1ea1i c\u00e1c tr\u1ea1ng th\u00e1i n\u00e0y. \u0110i\u1ec3n h\u00ecnh nh\u1ea5t l\u00e0 t\u00ednh n\u0103ng: B\u1eadt h\u1ed9p tho\u1ea1i h\u1ecfi \"B\u1ea1n c\u00f3 ch\u1eafc ch\u1eafn mu\u1ed1n tho\u00e1t kh\u00f4ng? D\u1eef li\u1ec7u ch\u01b0a l\u01b0u s\u1ebd b\u1ecb m\u1ea5t\" khi ng\u01b0\u1eddi d\u00f9ng v...","contents":"M\u1ecdi c\u1eeda s\u1ed5 \u1ee9ng d\u1ee5ng tr\u00ean h\u1ec7 \u0111i\u1ec1u h\u00e0nh \u0111\u1ec1u c\u00f3 m\u1ed9t v\u00f2ng \u0111\u1eddi: M\u1edf l\u00ean, \u0111\u01b0\u1ee3c ng\u01b0\u1eddi d\u00f9ng s\u1eed d\u1ee5ng (Activate), thu nh\u1ecf xu\u1ed1ng thanh Taskbar (Minimize), ph\u00f3ng to tr\u1edf l\u1ea1i, v\u00e0 cu\u1ed1i c\u00f9ng l\u00e0 b\u1ecb \u0111\u00f3ng (b\u1ea5m n\u00fat X).\n\n\u0110\u00f4i khi, b\u1ea1n mu\u1ed1n \u1ee9ng d\u1ee5ng c\u1ee7a m\u00ecnh \"ph\u1ea3n \u1ee9ng\" l\u1ea1i c\u00e1c tr\u1ea1ng th\u00e1i n\u00e0y. \u0110i\u1ec3n h\u00ecnh nh\u1ea5t l\u00e0 t\u00ednh n\u0103ng: **B\u1eadt h\u1ed9p tho\u1ea1i h\u1ecfi \"B\u1ea1n c\u00f3 ch\u1eafc ch\u1eafn mu\u1ed1n tho\u00e1t kh\u00f4ng? D\u1eef li\u1ec7u ch\u01b0a l\u01b0u s\u1ebd b\u1ecb m\u1ea5t\" khi ng\u01b0\u1eddi d\u00f9ng v\u00f4 t\u00ecnh b\u1ea5m nh\u1ea7m v\u00e0o d\u1ea5u X.**\n\n\u0110\u1ec3 l\u00e0m \u0111\u01b0\u1ee3c \u0111i\u1ec1u \u0111\u00f3, Swing cung c\u1ea5p b\u1ed9 l\u1eafng nghe **`WindowListener`**.\n\n## 1. V\u00f2ng \u0111\u1eddi c\u1eeda s\u1ed5 v\u00e0 WindowAdapter\n\nGi\u1ed1ng nh\u01b0 `MouseListener` (B\u00e0i 26), Interface `WindowListener` ch\u1ee9a t\u1edbi 7 ph\u01b0\u01a1ng th\u1ee9c m\u00f4 t\u1ea3 7 tr\u1ea1ng th\u00e1i c\u1ee7a c\u1eeda s\u1ed5:\n\n-   **`windowOpened`**: C\u1eeda s\u1ed5 v\u1eeba \u0111\u01b0\u1ee3c m\u1edf l\u00ean l\u1ea7n \u0111\u1ea7u ti\u00ean.\n    \n-   **`windowClosing`**: Ng\u01b0\u1eddi d\u00f9ng v\u1eeba b\u1ea5m d\u1ea5u X (C\u1eeda s\u1ed5 \u0110ANG TRONG QU\u00c1 TR\u00ccNH \u0111\u00f3ng, ch\u01b0a \u0111\u00f3ng h\u1eb3n).\n    \n-   **`windowClosed`**: C\u1eeda s\u1ed5 \u0110\u00c3 \u0110\u00d3NG v\u00e0 b\u1ecb gi\u1ea3i ph\u00f3ng kh\u1ecfi b\u1ed9 nh\u1edb (d\u00f9ng h\u00e0m `dispose()`).\n    \n-   **`windowIconified`**: C\u1eeda s\u1ed5 b\u1ecb thu nh\u1ecf (Minimize) xu\u1ed1ng thanh Taskbar.\n    \n-   **`windowDeiconified`**: C\u1eeda s\u1ed5 \u0111\u01b0\u1ee3c ph\u00f3ng to\/kh\u00f4i ph\u1ee5c l\u1ea1i t\u1eeb thanh Taskbar.\n    \n-   **`windowActivated`**: C\u1eeda s\u1ed5 \u0111\u01b0\u1ee3c click ch\u1ecdn, tr\u1edf th\u00e0nh c\u1eeda s\u1ed5 \u0111ang ho\u1ea1t \u0111\u1ed9ng (\u0111\u01b0\u1ee3c Focus).\n    \n-   **`windowDeactivated`**: C\u1eeda s\u1ed5 m\u1ea5t Focus (ng\u01b0\u1eddi d\u00f9ng click sang m\u1ed9t ph\u1ea7n m\u1ec1m kh\u00e1c).\n    \n\nV\u00e0 t\u1ea5t nhi\u00ean, \u0111\u1ec3 kh\u00f4ng ph\u1ea3i g\u00e1nh v\u00e1c vi\u1ec7c vi\u1ebft \u0111\u00e8 c\u1ea3 7 ph\u01b0\u01a1ng th\u1ee9c n\u00e0y, ch\u00fang ta s\u1ebd s\u1eed d\u1ee5ng l\u1edbp **`WindowAdapter`** v\u00e0 ch\u1ec9 ghi \u0111\u00e8 (Override) nh\u1eefng tr\u1ea1ng th\u00e1i c\u1ea7n thi\u1ebft.\n\n## 2. B\u00ed k\u00edp b\u1eaft s\u1ef1 ki\u1ec7n tho\u00e1t: DO_NOTHING_ON_CLOSE\n\nN\u1ebfu b\u1ea1n ch\u1ec9 g\u1eafn `WindowListener` v\u00e0o `JFrame` v\u00e0 vi\u1ebft code b\u1eaft s\u1ef1 ki\u1ec7n `windowClosing`, b\u1ea1n s\u1ebd nh\u1eadn ra m\u1ed9t \u0111i\u1ec1u t\u1ed3i t\u1ec7: H\u1ed9p tho\u1ea1i c\u1ea3nh b\u00e1o c\u00f3 hi\u1ec7n l\u00ean, nh\u01b0ng \u1ee9ng d\u1ee5ng c\u0169ng... t\u1eaft lu\u00f4n!\n\n**Nguy\u00ean nh\u00e2n:** T\u1eeb B\u00e0i 1 \u0111\u1ebfn gi\u1edd, ch\u00fang ta lu\u00f4n d\u00f9ng c\u00e2u l\u1ec7nh: `setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);` C\u00e2u l\u1ec7nh n\u00e0y ra l\u1ec7nh cho Java: \"C\u1ee9 th\u1ea5y ng\u01b0\u1eddi d\u00f9ng b\u1ea5m d\u1ea5u X l\u00e0 l\u1eadp t\u1ee9c kill (gi\u1ebft) ch\u01b0\u01a1ng tr\u00ecnh, kh\u00f4ng c\u1ea7n h\u1ecfi han g\u00ec c\u1ea3\".\n\n**C\u00e1ch gi\u1ea3i quy\u1ebft:** \u0110\u1ec3 chi\u1ebfm quy\u1ec1n ki\u1ec3m so\u00e1t n\u00fat X, b\u1ea1n ph\u1ea3i t\u01b0\u1edbc b\u1ecf quy\u1ec1n t\u1ef1 \u0111\u1ed9ng t\u1eaft c\u1ee7a `JFrame` b\u1eb1ng h\u1eb1ng s\u1ed1 **`DO_NOTHING_ON_CLOSE`**. Sau \u0111\u00f3, b\u1ea1n t\u1ef1 m\u00ecnh hi\u1ec3n th\u1ecb h\u1ed9p tho\u1ea1i x\u00e1c nh\u1eadn (B\u00e0i 24), v\u00e0 t\u1ef1 g\u1ecdi l\u1ec7nh `System.exit(0)` n\u1ebfu ng\u01b0\u1eddi d\u00f9ng ch\u1ecdn \"Yes\".\n\nJava\n\n```\n\/\/ 1. Ch\u1eb7n h\u00e0nh vi t\u1ef1 \u0111\u1ed9ng t\u1eaft c\u1eeda s\u1ed5\nframe.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);\n\n\/\/ 2. T\u1ef1 x\u1eed l\u00fd logic khi c\u1eeda s\u1ed5 chu\u1ea9n b\u1ecb \u0111\u00f3ng\nframe.addWindowListener(new WindowAdapter() {\n    @Override\n    public void windowClosing(WindowEvent e) {\n        int confirm = JOptionPane.showConfirmDialog(frame, \"B\u1ea1n c\u00f3 mu\u1ed1n tho\u00e1t?\", \"X\u00e1c nh\u1eadn\", JOptionPane.YES_NO_OPTION);\n        if (confirm == JOptionPane.YES_OPTION) {\n            System.exit(0); \/\/ L\u1ec7nh t\u1eaft ch\u01b0\u01a1ng tr\u00ecnh ho\u00e0n to\u00e0n\n        }\n    }\n});\n\n```\n\n## 3. Qu\u1ea3n l\u00fd tr\u1ea1ng th\u00e1i Thu nh\u1ecf \/ Ph\u00f3ng to\n\nNgo\u00e0i vi\u1ec7c ch\u1eb7n t\u1eaft c\u1eeda s\u1ed5, `WindowListener` r\u1ea5t h\u1eefu \u00edch khi b\u1ea1n mu\u1ed1n t\u1ed1i \u01b0u t\u00e0i nguy\u00ean m\u00e1y t\u00ednh. V\u00ed d\u1ee5: \u1ee8ng d\u1ee5ng c\u1ee7a b\u1ea1n c\u00f3 m\u1ed9t lu\u1ed3ng (Thread) li\u00ean t\u1ee5c t\u1ea3i video ho\u1eb7c render h\u00ecnh \u1ea3nh. Khi ng\u01b0\u1eddi d\u00f9ng thu nh\u1ecf c\u1eeda s\u1ed5 xu\u1ed1ng Taskbar (`windowIconified`), b\u1ea1n c\u00f3 th\u1ec3 t\u1ea1m d\u1eebng lu\u1ed3ng \u0111\u00f3 \u0111\u1ec3 ti\u1ebft ki\u1ec7m CPU, v\u00e0 ti\u1ebfp t\u1ee5c ch\u1ea1y l\u1ea1i khi c\u1eeda s\u1ed5 \u0111\u01b0\u1ee3c kh\u00f4i ph\u1ee5c (`windowDeiconified`).\n\nJava\n\n```\nframe.addWindowListener(new WindowAdapter() {\n    @Override\n    public void windowIconified(WindowEvent e) {\n        System.out.println(\"C\u1eeda s\u1ed5 \u0111\u00e3 b\u1ecb thu nh\u1ecf. T\u1ea1m d\u1eebng c\u00e1c t\u00e1c v\u1ee5 n\u1eb7ng...\");\n    }\n\n    @Override\n    public void windowDeiconified(WindowEvent e) {\n        System.out.println(\"C\u1eeda s\u1ed5 \u0111\u00e3 kh\u00f4i ph\u1ee5c. Ti\u1ebfp t\u1ee5c x\u1eed l\u00fd...\");\n    }\n});\n\n```\n\n## 4. Source Code Th\u1ef1c Chi\u1ebfn: C\u1eeda S\u1ed5 B\u1ea5t T\u1eed\n\n\u0110o\u1ea1n code d\u01b0\u1edbi \u0111\u00e2y t\u1ea1o ra m\u1ed9t Form Notepad. Khi b\u1ea1n g\u00f5 b\u1ea5t k\u1ef3 n\u1ed9i dung n\u00e0o v\u00e0o khung so\u1ea1n th\u1ea3o v\u00e0 b\u1ea5m n\u00fat X, ph\u1ea7n m\u1ec1m s\u1ebd ch\u1eb7n b\u1ea1n l\u1ea1i \u0111\u1ec3 c\u1ea3nh b\u00e1o. \u0110\u1ed3ng th\u1eddi, n\u00f3 s\u1ebd thay \u0111\u1ed5i ti\u00eau \u0111\u1ec1 (Title) khi b\u1ea1n thu nh\u1ecf c\u1eeda s\u1ed5.\n\nJava\n\n```\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.WindowAdapter;\nimport java.awt.event.WindowEvent;\n\npublic class WindowListenerDemo extends JFrame {\n\n    private JTextArea textArea;\n\n    public WindowListenerDemo() {\n        setTitle(\"Notepad Th\u1ea7n Th\u00e1nh - B\u00e0i 28\");\n        setSize(500, 350);\n        setLocationRelativeTo(null);\n        \n        \/\/ B\u01af\u1edaC 1 C\u1ef0C K\u1ef2 QUAN TR\u1eccNG: Ng\u0103n ch\u1eb7n t\u1ef1 \u0111\u1ed9ng t\u1eaft c\u1eeda s\u1ed5\n        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);\n\n        \/\/ T\u1ea1o giao di\u1ec7n \u0111\u01a1n gi\u1ea3n\n        textArea = new JTextArea(\"H\u00e3y g\u00f5 th\u00eam v\u00e0i ch\u1eef v\u00e0o \u0111\u00e2y, sau \u0111\u00f3 b\u1ea5m n\u00fat [X] \u0111\u1ec3 tho\u00e1t...\");\n        textArea.setFont(new Font(\"Arial\", Font.PLAIN, 16));\n        textArea.setLineWrap(true);\n        textArea.setMargin(new Insets(10, 10, 10, 10));\n        add(new JScrollPane(textArea), BorderLayout.CENTER);\n\n        \/\/ B\u01af\u1edaC 2: G\u1eafn WindowListener \u0111\u1ec3 chi\u1ebfm quy\u1ec1n ki\u1ec3m so\u00e1t\n        addWindowListener(new WindowAdapter() {\n\n            \/\/ -----------------------------------------------------\n            \/\/ 1. X\u1eec L\u00dd KHI B\u1ea4M N\u00daT [X] (\u0110ang \u0111\u00f3ng c\u1eeda s\u1ed5)\n            \/\/ -----------------------------------------------------\n            @Override\n            public void windowClosing(WindowEvent e) {\n                \/\/ Ki\u1ec3m tra xem ng\u01b0\u1eddi d\u00f9ng c\u00f3 g\u00f5 th\u00eam g\u00ec kh\u00f4ng\n                if (!textArea.getText().isEmpty()) {\n                    \/\/ Hi\u1ec7n h\u1ed9p tho\u1ea1i h\u1ecfi\n                    int result = JOptionPane.showConfirmDialog(\n                            WindowListenerDemo.this,\n                            \"B\u1ea1n c\u00f3 d\u1eef li\u1ec7u ch\u01b0a l\u01b0u. B\u1ea1n c\u00f3 ch\u1eafc ch\u1eafn mu\u1ed1n tho\u00e1t \u1ee9ng d\u1ee5ng kh\u00f4ng?\",\n                            \"C\u1ea3nh b\u00e1o tho\u00e1t\",\n                            JOptionPane.YES_NO_OPTION,\n                            JOptionPane.WARNING_MESSAGE\n                    );\n\n                    \/\/ N\u1ebfu ch\u1ecdn Yes (\u0110\u1ed3ng \u00fd tho\u00e1t)\n                    if (result == JOptionPane.YES_OPTION) {\n                        System.out.println(\"\u0110ang d\u1ecdn d\u1eb9p t\u00e0i nguy\u00ean...\");\n                        \/\/ Th\u1ef1c hi\u1ec7n ng\u1eaft k\u1ebft n\u1ed1i Database \u1edf \u0111\u00e2y (n\u1ebfu c\u00f3)\n                        dispose(); \/\/ Gi\u1ea3i ph\u00f3ng JFrame kh\u1ecfi b\u1ed9 nh\u1edb\n                        System.exit(0); \/\/ T\u1eaft ti\u1ebfn tr\u00ecnh JVM\n                    } \n                    \/\/ N\u1ebfu ch\u1ecdn No, c\u1eeda s\u1ed5 v\u1eabn gi\u1eef nguy\u00ean (DO_NOTHING)\n                } else {\n                    \/\/ N\u1ebfu kh\u00f4ng c\u00f3 ch\u1eef n\u00e0o, t\u1eaft lu\u00f4n kh\u00f4ng c\u1ea7n h\u1ecfi\n                    System.exit(0);\n                }\n            }\n\n            \/\/ -----------------------------------------------------\n            \/\/ 2. X\u1eec L\u00dd KHI THU NH\u1ece C\u1eecA S\u1ed4 XU\u1ed0NG TASKBAR\n            \/\/ -----------------------------------------------------\n            @Override\n            public void windowIconified(WindowEvent e) {\n                System.out.println(\"S\u1ef0 KI\u1ec6N: C\u1eeda s\u1ed5 \u0111\u00e3 b\u1ecb thu nh\u1ecf (Minimized)\");\n                \/\/ \u0110\u1ed5i ti\u00eau \u0111\u1ec1 \u1ee9ng d\u1ee5ng\n                setTitle(\"Notepad [\u0110ang ch\u1ea1y ng\u1ea7m]\");\n            }\n\n            \/\/ -----------------------------------------------------\n            \/\/ 3. X\u1eec L\u00dd KHI KH\u00d4I PH\u1ee4C C\u1eecA S\u1ed4\n            \/\/ -----------------------------------------------------\n            @Override\n            public void windowDeiconified(WindowEvent e) {\n                System.out.println(\"S\u1ef0 KI\u1ec6N: C\u1eeda s\u1ed5 \u0111\u00e3 kh\u00f4i ph\u1ee5c (Restored)\");\n                setTitle(\"Notepad Th\u1ea7n Th\u00e1nh - B\u00e0i 28\");\n            }\n        });\n    }\n\n    public static void main(String[] args) {\n        SwingUtilities.invokeLater(() -> {\n            new WindowListenerDemo().setVisible(true);\n        });\n    }\n}\n\n```\n\nB\u1ea1n h\u00e3y ch\u1ea1y code, x\u00f3a s\u1ea1ch ch\u1eef \u0111i v\u00e0 b\u1ea5m **X** -> C\u1eeda s\u1ed5 t\u1eaft lu\u00f4n. Ch\u1ea1y l\u1ea1i, g\u00f5 th\u00eam ch\u1eef v\u00e0 b\u1ea5m **X** -> H\u1ec7 th\u1ed1ng l\u1eadp t\u1ee9c ch\u1eb7n l\u1ea1i v\u00e0 h\u1ecfi \u00fd ki\u1ebfn b\u1ea1n. Th\u1eed \u1ea5n n\u00fat D\u1ea5u tr\u1eeb (-) \u1edf g\u00f3c tr\u00ean b\u00ean ph\u1ea3i c\u1eeda s\u1ed5 \u0111\u1ec3 xem th\u00f4ng b\u00e1o in ra trong Console.","published_at":"2026-11-14T12:26:36.000000Z","scheduled_publish_at":null,"is_published":true,"is_shared":false,"updated_at":"2026-11-14T22:34:02.000000Z","edited_at":"2026-09-26T03:17:30.000000Z","translation_source":null,"trend_at":null,"promoted_at":null,"reading_time":4,"points":0,"views_count":14,"clips_count":0,"comments_count":0,"rated_value":null,"promoted":false,"trending":false,"is_draft":false,"is_public":true,"locale_code":"vi","is_video":false,"thumbnail_url":"https:\/\/images.viblo.asia\/43c724a4-1c2d-4faf-af03-41ddefb8fe0d.png","user":{"data":{"id":182653,"url":"https:\/\/viblo.asia\/u\/hhoang","avatar":"8392e404-ea43-497f-8b18-3777e24dc6ab.jpg","name":"Huy Ho\u00e0ng","username":"hhoang","followers_count":216,"reputation":14329,"posts_count":1762,"banned_at":null,"level_partner":null,"following":false}},"tags":{"data":[{"slug":"backend","name":"Backend"}]},"commentators":{"data":[]}},{"id":110645,"title":"[Series 100 B\u00e0i H\u1ecdc HTML\/CSS Th\u1ef1c Chi\u1ebfn - B\u00e0i 26]: \u0110\u1ea1i Ph\u1eabu Responsive Th\u00e2n Trang Shopee \u2014 K\u1ef9 Thu\u1eadt Vu\u1ed1t Ngang M\u01b0\u1ee3t Nh\u01b0 App (CSS Scroll Snap) & Co Gi\u00e3n L\u01b0\u1edbi S\u1ea3n Ph\u1ea9m (6 -> 4 -> 2 C\u1ed9t)","slug":"pPLkN8YDJRZ","url":"https:\/\/viblo.asia\/p\/series-100-bai-hoc-htmlcss-thuc-chien-bai-26-dai-phau-responsive-than-trang-shopee-ky-thuat-vuot-ngang-muot-nhu-app-css-scroll-snap-co-gian-luoi-san-pham-6-4-2-cot-pPLkN8YDJRZ","user_id":184801,"moderation":null,"transliterated":"series-100-bai-hoc-htmlcss-thuc-chien-bai-26-dai-phau-responsive-than-trang-shopee-ky-thuat-vuot-ngang-muot-nhu-app-css-scroll-snap-co-gian-luoi-san-pham-6-4-2-cot","contents_short":"Ch\u00e0o m\u1eebng anh em \u0111\u00e3 b\u01b0\u1edbc sang B\u00e0i 26 trong chu\u1ed7i 100 B\u00e0i H\u1ecdc L\u00e0m Ch\u1ee7 HTML\/CSS Qua D\u1ef1 \u00c1n Clone Shopee!\n\n\u1ede B\u00e0i 25, ch\u00fang ta \u0111\u00e3 thi\u1ebft l\u1eadp h\u1ec7 th\u1ed1ng 3 Breakpoints chu\u1ea9n m\u1ef1c trong responsive.css v\u00e0 bi\u1ebfn h\u00f3a thanh Header Shopee t\u1eeb giao di\u1ec7n Desktop (120px) th\u00e0nh giao di\u1ec7n Mobile App (68px) k\u00e8m thanh l\u1ecdc nhanh 4 tab b\u00e1m d\u00ednh c\u1ef1c k\u1ef3 g\u1ecdn g\u00e0ng.\n\nTuy nhi\u00ean, khi b\u1ea1n cu\u1ed9n xu\u1ed1ng Ph\u1ea7n Th\u00e2n Trang (<main class=\"...","contents":"Ch\u00e0o m\u1eebng anh em \u0111\u00e3 b\u01b0\u1edbc sang **B\u00e0i 26** trong chu\u1ed7i **100 B\u00e0i H\u1ecdc L\u00e0m Ch\u1ee7 HTML\/CSS Qua D\u1ef1 \u00c1n Clone Shopee**!\n\n\u1ede **B\u00e0i 25**, ch\u00fang ta \u0111\u00e3 thi\u1ebft l\u1eadp h\u1ec7 th\u1ed1ng 3 Breakpoints chu\u1ea9n m\u1ef1c trong `responsive.css` v\u00e0 bi\u1ebfn h\u00f3a thanh **Header Shopee** t\u1eeb giao di\u1ec7n Desktop (`120px`) th\u00e0nh giao di\u1ec7n Mobile App (`68px`) k\u00e8m thanh l\u1ecdc nhanh 4 tab b\u00e1m d\u00ednh c\u1ef1c k\u1ef3 g\u1ecdn g\u00e0ng.\n\nTuy nhi\u00ean, khi b\u1ea1n cu\u1ed9n xu\u1ed1ng **Ph\u1ea7n Th\u00e2n Trang (`<main class=\"shopee-body\">`)** tr\u00ean m\u00e0n h\u00ecnh \u0110i\u1ec7n tho\u1ea1i (`375px`) ho\u1eb7c iPad (`768px`), b\u1ea1n s\u1ebd th\u1ea5y h\u00e0ng lo\u1ea1t kh\u1ed1i n\u1ed9i dung \u0111ang b\u1ecb \"b\u00f3p ngh\u1eb9t\" \u0111\u1ebfn bi\u1ebfn d\u1ea1ng:\n\n1.  **C\u1ee5m Hero Banner (`2fr 1fr`)** qu\u00e1 h\u1eb9p khi\u1ebfn 2 t\u1ea5m banner nh\u1ecf b\u00ean ph\u1ea3i bi\u1ebfn th\u00e0nh hai d\u1ea3i b\u0103ng b\u00e9 x\u00edu kh\u00f4ng \u0111\u1ecdc n\u1ed5i ch\u1eef.\n    \n2.  **D\u00e3y 8 Icon D\u1ecbch V\u1ee5** v\u00e0 **L\u01b0\u1edbi 20 Danh M\u1ee5c Ng\u00e0nh H\u00e0ng (`10 c\u1ed9t`)** b\u1ecb \u00e9p ch\u1ed3ng ch\u00e9o l\u00ean nhau; ch\u1eef _\"Th\u1eddi Trang Nam\"_, _\"\u0110i\u1ec7n Tho\u1ea1i & Ph\u1ee5 Ki\u1ec7n\"_ b\u1ecb g\u00e3y th\u00e0nh 5\u20136 d\u00f2ng d\u1ecdc t\u1eebng ch\u1eef c\u00e1i m\u1ed9t!\n    \n3.  **L\u01b0\u1edbi 6 S\u1ea3n Ph\u1ea9m (`Flash Sale` & `G\u1ee3i \u00dd H\u00f4m Nay`)** tr\u00ean m\u00e0n h\u00ecnh `375px` l\u00e0m m\u1ed7i th\u1ebb s\u1ea3n ph\u1ea9m ch\u1ec9 c\u00f2n r\u1ed9ng v\u1ecfn v\u1eb9n `50px` \u2014 \u1ea3nh b\u00e9 b\u1eb1ng m\u00f3ng tay v\u00e0 gi\u00e1 ti\u1ec1n tr\u00e0n ra ngo\u00e0i khung!\n    \n\nTrong **B\u00e0i 26** h\u00f4m nay, ch\u00fang ta s\u1ebd gi\u1ea3i quy\u1ebft tri\u1ec7t \u0111\u1ec3 to\u00e0n b\u1ed9 ph\u1ea7n th\u00e2n trang tr\u00ean Tablet & Mobile b\u1eb1ng 2 v\u0169 kh\u00ed l\u1ee3i h\u1ea1i nh\u1ea5t c\u1ee7a Mobile Web hi\u1ec7n \u0111\u1ea1i: **Bi\u1ebfn c\u00e1c danh s\u00e1ch b\u1ecb tr\u00e0n th\u00e0nh Thanh Vu\u1ed1t Ngang M\u01b0\u1ee3t Nh\u01b0 App Native (`overflow-x: auto` + `scroll-snap-type`)**, v\u00e0 **H\u1ea1 b\u1eadc c\u1ed9t CSS Grid (`6 c\u1ed9t` $\\rightarrow$ `4 c\u1ed9t` $\\rightarrow$ `2 c\u1ed9t`)**!\n\n## 1. B\u00ed thu\u1eadt \"Vu\u1ed1t Ngang B\u1eaft \u0110i\u1ec3m\" (`CSS Scroll Snap`): Bi\u1ebfn L\u01b0\u1edbi T\u0129nh th\u00e0nh Carousel C\u1ea3m \u1ee8ng Kh\u00f4ng C\u1ea7n JS!\n\nTr\u00ean m\u00e0n h\u00ecnh PC r\u1ed9ng `1200px`, ch\u00fang ta c\u00f3 \u0111\u1ee7 ch\u1ed7 \u0111\u1ec3 b\u00e0y c\u1ea3 **8 Icon d\u1ecbch v\u1ee5** ho\u1eb7c **10 c\u1ed9t Danh m\u1ee5c ng\u00e0nh h\u00e0ng** l\u00ean c\u00f9ng m\u1ed9t h\u00e0ng ngang. Nh\u01b0ng tr\u00ean m\u00e0n h\u00ecnh \u0111i\u1ec7n tho\u1ea1i r\u1ed9ng ch\u1ec9 `375px`, b\u1ea1n ch\u1ec9 c\u00f3 th\u1ec3 hi\u1ec3n th\u1ecb t\u1ed1i \u0111a **4 \u0111\u1ebfn 5 m\u1ee5c** c\u00f9ng l\u00fac \u0111\u1ec3 ng\u00f3n tay ng\u01b0\u1eddi d\u00f9ng d\u1ec5 b\u1ea5m.\n\nV\u1eady nh\u1eefng m\u1ee5c c\u00f2n l\u1ea1i (t\u1eeb m\u1ee5c s\u1ed1 5 \u0111\u1ebfn m\u1ee5c s\u1ed1 10) s\u1ebd \u0111i \u0111\u00e2u? Tr\u00ean \u1ee9ng d\u1ee5ng Shopee Mobile, ch\u00fang \u0111\u01b0\u1ee3c gi\u1ea5u sang b\u00ean ph\u1ea3i m\u00e9p m\u00e0n h\u00ecnh \u0111\u1ec3 ng\u01b0\u1eddi d\u00f9ng **d\u00f9ng ng\u00f3n tay c\u00e1i vu\u1ed1t ngang (Horizontal Swipe)**!\n\nThay v\u00ec ph\u1ea3i nh\u00fang c\u00e1c th\u01b0 vi\u1ec7n JavaScript n\u1eb7ng n\u1ec1 nh\u01b0 _Swiper.js_ hay _Slick Slider_, CSS hi\u1ec7n \u0111\u1ea1i cung c\u1ea5p b\u1ed9 \u0111\u00f4i thu\u1ed9c t\u00ednh **`scroll-snap-type`** (tr\u00ean th\u1ebb cha) v\u00e0 **`scroll-snap-align`** (tr\u00ean th\u1ebb con) gi\u00fap tr\u00ecnh duy\u1ec7t t\u1ef1 \u0111\u1ed9ng \"h\u00edt\" c\u00e1c m\u1ee5c v\u00e0o \u0111\u00fang m\u00e9p m\u00e0n h\u00ecnh m\u1ed7i khi ng\u01b0\u1eddi d\u00f9ng ng\u1eebng vu\u1ed1t tay!\n\nH\u00e3y quan s\u00e1t s\u01a1 \u0111\u1ed3 minh h\u1ecda c\u01a1 ch\u1ebf b\u1eaft \u0111i\u1ec3m d\u1eebng (`start`, `center`, `end`) c\u1ee7a thu\u1ed9c t\u00ednh `scroll-snap-align` khi cu\u1ed9n ngang d\u01b0\u1edbi \u0111\u00e2y:\n\n![image.png](https:\/\/images.viblo.asia\/4e77fb04-cbc9-4f7d-ad17-95d40d6203c4.png)\n\n\u0110\u1ec3 bi\u1ebfn m\u1ed9t danh s\u00e1ch `<ul>` b\u00ecnh th\u01b0\u1eddng th\u00e0nh thanh tr\u01b0\u1ee3t c\u1ea3m \u1ee9ng m\u01b0\u1ee3t nh\u01b0 App iOS\/Android, ch\u00fang ta k\u1ebft h\u1ee3p 4 d\u00f2ng CSS v\u00e0ng sau \u0111\u00e2y:\n\n# C\u00e1c thu\u1ed9c t\u00ednh CSS t\u1ed1i \u01b0u h\u00f3a thao t\u00e1c vu\u1ed1t ngang (Horizontal Scroll) tr\u00ean Mobile & Tablet\n\n| Thu\u1ed9c t\u00ednh CSS | \u0110\u1eb7t \u1edf \u0111\u00e2u? | T\u00e1c d\u1ee5ng th\u1ef1c chi\u1ebfn tr\u00ean Mobile \/ Tablet |\n| :--- | :--- | :--- |\n| `overflow-x: auto;` | Th\u1ebb cha (`.hero-service-list`, `.category-grid`) | Cho ph\u00e9p n\u1ed9i dung tr\u00e0n sang b\u00ean ph\u1ea3i v\u00e0 k\u00edch ho\u1ea1t thao t\u00e1c vu\u1ed1t ngang b\u1eb1ng ng\u00f3n tay tr\u00ean m\u00e0n h\u00ecnh c\u1ea3m \u1ee9ng. |\n| `scroll-snap-type: x mandatory;` | Th\u1ebb cha | Ra l\u1ec7nh cho tr\u1ee5c ngang (`x`) b\u1eaft bu\u1ed9c (`mandatory`) ph\u1ea3i t\u1ef1 \u0111\u1ed9ng h\u00edt v\u00e0o m\u1ee5c g\u1ea7n nh\u1ea5t khi ng\u01b0\u1eddi d\u00f9ng th\u1ea3 ng\u00f3n tay ra (kh\u00f4ng bao gi\u1edd \u0111\u1ec3 m\u1ed9t icon b\u1ecb c\u1eaft \u0111\u00f4i l\u01a1 l\u1eedng \u1edf m\u00e9p m\u00e0n h\u00ecnh!). |\n| `scroll-snap-align: start;` | Th\u1ebb con (`.hero-service-item`, `.category-item`) | \u0110i\u1ec3m neo \u0111\u1ec3 m\u00e9p tr\u00e1i c\u1ee7a m\u1ee5c con kh\u1edbp kh\u00edt v\u00e0o m\u00e9p tr\u00e1i c\u1ee7a khung nh\u00ecn. |\n| `::-webkit-scrollbar { display: none; }` | Th\u1ebb cha | \u1ea8n ho\u00e0n to\u00e0n thanh cu\u1ed9n ngang m\u00e0u x\u00e1m th\u00f4 k\u1ec7ch \u1edf \u0111\u00e1y danh s\u00e1ch, gi\u00fap giao di\u1ec7n s\u1ea1ch s\u1ebd 100% nh\u01b0 \u1ee9ng d\u1ee5ng g\u1ed1c! |\n\n## 2. Chi\u1ebfn l\u01b0\u1ee3c \"H\u1ea1 B\u1eadc C\u1ed9t\" (`Column Drop Strategy`) cho L\u01b0\u1edbi S\u1ea3n Ph\u1ea9m & Footer\n\n\u0110\u1ed1i v\u1edbi danh s\u00e1ch s\u1ea3n ph\u1ea9m mua s\u1eafm (**G\u1ee3i \u00dd H\u00f4m Nay** v\u00e0 **Trang Danh M\u1ee5c**), ng\u01b0\u1eddi d\u00f9ng c\u00f3 th\u00f3i quen cu\u1ed9n d\u1ecdc t\u1eeb tr\u00ean xu\u1ed1ng d\u01b0\u1edbi v\u00f4 t\u1eadn. Do \u0111\u00f3, ch\u00fang ta kh\u00f4ng d\u00f9ng thanh vu\u1ed1t ngang m\u00e0 \u00e1p d\u1ee5ng chi\u1ebfn l\u01b0\u1ee3c **Gi\u1ea3m s\u1ed1 c\u1ed9t theo t\u1eebng m\u1ed1c Breakpoint**:\n\nPlaintext\n\n```\n\ud83d\udcbb DESKTOP (>= 1024px) : [SP1] [SP2] [SP3] [SP4] [SP5] [SP6]  --> repeat(6, 1fr) (6 c\u1ed9t\/h\u00e0ng)\n\ud83d\udcdf TABLET (740->1023px): [  SP1  ] [  SP2  ] [  SP3  ] [  SP4  ]  --> repeat(4, 1fr) (4 c\u1ed9t\/h\u00e0ng)\n\ud83d\udcf1 MOBILE (<= 739px)   : [      SP1      ] [      SP2      ]  --> repeat(2, 1fr) (2 c\u1ed9t\/h\u00e0ng chu\u1ea9n Shopee App!)\n\n```\n\nT\u1ea1i sao tr\u00ean Mobile (`<= 739px`), m\u1ecdi s\u00e0n Th\u01b0\u01a1ng m\u1ea1i \u0111i\u1ec7n t\u1eed t\u1eeb Shopee, Lazada, TikTok Shop \u0111\u1ebfn Amazon \u0111\u1ec1u ch\u1ed1t \u1edf con s\u1ed1 **`2 c\u1ed9t s\u1ea3n ph\u1ea9m \/ h\u00e0ng` (`repeat(2, 1fr)`)** m\u00e0 kh\u00f4ng ph\u1ea3i `1 c\u1ed9t` hay `3 c\u1ed9t`?\n\n-   N\u1ebfu \u0111\u1ec3 **1 c\u1ed9t**: \u1ea2nh s\u1ea3n ph\u1ea9m qu\u00e1 to chi\u1ebfm h\u1ebft c\u1ea3 m\u00e0n h\u00ecnh \u0111i\u1ec7n tho\u1ea1i, ng\u01b0\u1eddi d\u00f9ng vu\u1ed1t m\u1ecfi tay m\u1edbi xem \u0111\u01b0\u1ee3c 5 m\u00f3n \u0111\u1ed3 (Gi\u1ea3m kh\u1ea3 n\u0103ng so s\u00e1nh gi\u00e1).\n    \n-   N\u1ebfu \u0111\u1ec3 **3 c\u1ed9t**: Chi\u1ec1u r\u1ed9ng m\u1ed7i th\u1ebb tr\u00ean m\u00e0n h\u00ecnh `375px` ch\u1ec9 c\u00f2n kho\u1ea3ng `115px` \u2014 t\u00ean s\u1ea3n ph\u1ea9m ch\u1ec9 hi\u1ec7n \u0111\u01b0\u1ee3c 2 ch\u1eef \u0111\u00e3 b\u1ecb c\u1eaft `...`, kh\u00f4ng \u0111\u1ee7 ch\u1ed7 ghi ch\u1eef _\"\u0110\u00e3 b\u00e1n 4,2k\"_.\n    \n-   **2 c\u1ed9t (`repeat(2, 1fr)`)** l\u00e0 \"t\u1ef7 l\u1ec7 v\u00e0ng\" tr\u00ean di \u0111\u1ed9ng: M\u1ed7i th\u1ebb r\u1ed9ng kho\u1ea3ng `175px`, v\u1eeba \u0111\u1ee7 \u0111\u1ec3 \u1ea3nh r\u00f5 n\u00e9t, t\u00ean hi\u1ec3n th\u1ecb \u0111\u1eb9p 2 d\u00f2ng v\u00e0 ng\u01b0\u1eddi d\u00f9ng c\u00f3 th\u1ec3 so s\u00e1nh tr\u1ef1c ti\u1ebfp 2 s\u1ea3n ph\u1ea9m c\u1ea1nh nhau!\n    \n\n## 3. Th\u1ef1c h\u00e0nh: Vi\u1ebft Code Responsive Th\u00e2n Trang Trong `.\/assets\/css\/responsive.css`\n\n\u1ede B\u00e0i 25, ch\u00fang ta \u0111\u00e3 t\u1ea1o file `.\/assets\/css\/responsive.css` v\u1edbi 3 khung `@media` chu\u1ea9n. B\u00e2y gi\u1edd, h\u00e3y m\u1edf l\u1ea1i file `responsive.css` v\u00e0 b\u1ed5 sung c\u00e1c quy t\u1eafc bi\u1ebfn h\u00f3a ph\u1ea7n Th\u00e2n trang v\u00e0o \u0111\u00fang 3 khu v\u1ef1c \u0111\u00f3:\n\n### B\u01b0\u1edbc 1: C\u1eadp nh\u1eadt khu v\u1ef1c 1 \u2014 Chung cho c\u1ea3 Mobile & Tablet (`max-width: 1023px`)\n\nTrong kh\u1ed1i `@media (max-width: 1023px)` c\u1ee7a file `.\/assets\/css\/responsive.css`, b\u1ea1n h\u00e3y b\u1ed5 sung ti\u1ebfp \u0111o\u1ea1n code d\u01b0\u1edbi \u0111\u00e2y v\u00e0o ngay b\u00ean d\u01b0\u1edbi ph\u1ea7n `.header-cart-list::before`:\n\nCSS\n\n```\n\/* B\u1ed5 sung ti\u1ebfp v\u00e0o b\u00ean trong @media (max-width: 1023px) c\u1ee7a file .\/assets\/css\/responsive.css *\/\n\n    \/* --- 1. T\u1ed0I \u01afU HERO BANNER & THANH D\u1ecaCH V\u1ee4 TR\u00caN MOBILE\/TABLET --- *\/\n    .hero-section-wrapper {\n        padding-top: 1.2rem;\n    }\n\n    \/* Bi\u1ebfn d\u00e3y 8 Icon D\u1ecbch v\u1ee5 th\u00e0nh Thanh Vu\u1ed1t Ngang (Scroll Snap) m\u01b0\u1ee3t m\u00e0 *\/\n    .hero-service-list {\n        justify-content: flex-start;\n        overflow-x: auto;\n        scroll-snap-type: x mandatory;\n        gap: 0.8rem;\n        padding-bottom: 0.6rem;\n        -webkit-overflow-scrolling: touch; \/* T\u0103ng t\u1ed1c \u0111\u1ed9 qu\u00e1n t\u00ednh khi vu\u1ed1t tr\u00ean iPhone\/iPad *\/\n    }\n\n    \/* \u1ea8n thanh cu\u1ed9n ngang m\u1eb7c \u0111\u1ecbnh c\u1ee7a tr\u00ecnh duy\u1ec7t *\/\n    .hero-service-list::-webkit-scrollbar {\n        display: none;\n    }\n\n    .hero-service-item {\n        flex: 0 0 22%;              \/* M\u1ed7i icon chi\u1ebfm 22% m\u00e0n h\u00ecnh (L\u1ed9 nh\u1eb9 m\u1ed9t ph\u1ea7n icon th\u1ee9 5 \u0111\u1ec3 g\u1ee3i \u00fd vu\u1ed1t!) *\/\n        max-width: none;\n        scroll-snap-align: start;\n    }\n\n    \/* --- 2. T\u1ed0I \u01afU L\u01af\u1edaI 20 DANH M\u1ee4C NG\u00c0NH H\u00c0NG: VU\u1ed0T NGANG 2 T\u1ea6NG! --- *\/\n    \/* \u1ea8n n\u00fat m\u0169i t\u00ean tr\u00f2n c\u1ee7a b\u1ea3n PC v\u00ec tr\u00ean c\u1ea3m \u1ee9ng ng\u01b0\u1eddi d\u00f9ng t\u1ef1 vu\u1ed1t tay *\/\n    .category-arrow-btn {\n        display: none;\n    }\n\n    \/* B\u00ed thu\u1eadt Grid Auto-Flow Column: X\u1ebfp 2 h\u00e0ng v\u00e0 cho ph\u00e9p vu\u1ed1t ngang sang ph\u1ea3i! *\/\n    .category-grid {\n        grid-template-columns: unset;\n        grid-auto-flow: column;     \/* \u00c9p c\u00e1c \u00f4 x\u1ebfp theo c\u1ed9t d\u1ecdc 2 t\u1ea7ng n\u1ed1i ti\u1ebfp nhau sang ph\u1ea3i *\/\n        grid-template-rows: repeat(2, auto);\n        grid-auto-columns: 22%;     \/* M\u1ed7i c\u1ed9t r\u1ed9ng 22% m\u00e0n h\u00ecnh (Hi\u1ec3n th\u1ecb ~4.5 c\u1ed9t c\u00f9ng l\u00fac) *\/\n        overflow-x: auto;\n        scroll-snap-type: x mandatory;\n        -webkit-overflow-scrolling: touch;\n    }\n\n    .category-grid::-webkit-scrollbar {\n        display: none;\n    }\n\n    .category-item {\n        scroll-snap-align: start;\n    }\n\n    \/* --- 3. T\u1ed0I \u01afU KH\u1ed0I FLASH SALE: VU\u1ed0T NGANG C\u00c1C TH\u1eba SALE --- *\/\n    .flash-sale-grid {\n        display: flex;              \/* Chuy\u1ec3n t\u1eeb Grid 6 c\u1ed9t c\u1ed1 \u0111\u1ecbnh sang Flex vu\u1ed1t ngang *\/\n        overflow-x: auto;\n        scroll-snap-type: x mandatory;\n        gap: 1.2rem;\n        padding-bottom: 0.4rem;\n    }\n\n    .flash-sale-grid::-webkit-scrollbar {\n        display: none;\n    }\n\n    .flash-sale-item {\n        flex: 0 0 30%;              \/* M\u1ed7i s\u1ea3n ph\u1ea9m Flash Sale chi\u1ebfm 30% chi\u1ec1u ngang *\/\n        scroll-snap-align: start;\n    }\n\n    \/* --- 4. \u1ea8N C\u1ed8T SIDEBAR B\u1ed8 L\u1eccC TR\u00c1I (2 C\u1ed8T) \u1ede CH\u01af\u01a0NG 6 & M\u1ede R\u1ed8NG C\u1ed8T S\u1ea2N PH\u1ea8M RA 12 C\u1ed8T --- *\/\n    .sidebar-col {\n        display: none;              \/* Tr\u00ean Mobile\/Tablet b\u1ed9 l\u1ecdc \u0111\u01b0\u1ee3c \u0111\u01b0a v\u00e0o n\u00fat Drawer ri\u00eang *\/\n    }\n\n    .main-product-col {\n        grid-column: span 12;       \/* C\u1ed9t s\u1ea3n ph\u1ea9m chi\u1ebfm tr\u1ecdn 100% (12\/12 c\u1ed9t) m\u00e0n h\u00ecnh! *\/\n    }\n\n    \/* \u0110i\u1ec1u ch\u1ec9nh l\u1ea1i m\u1ed1c b\u00e1m d\u00ednh c\u1ee7a ti\u00eau \u0111\u1ec1 G\u1ee2I \u00dd H\u00d4M NAY d\u01b0\u1edbi Header Mobile (68px + 46px = 114px) *\/\n    .daily-discover-header {\n        top: calc(var(--header-height) + 4.6rem);\n        height: 4.8rem;\n    }\n\n    \/* --- 5. THU G\u1eccN TRANG CHI TI\u1ebeT S\u1ea2N PH\u1ea8M & GI\u1ece H\u00c0NG (CH\u01af\u01a0NG 7 & 8) --- *\/\n    .product-briefing {\n        flex-direction: column;     \/* X\u1ebfp ch\u1ed3ng: Khung \u1ea2nh \u1edf tr\u00ean, Th\u00f4ng tin mua h\u00e0ng \u1edf d\u01b0\u1edbi *\/\n        gap: 2rem;\n    }\n\n    .product-gallery {\n        width: 100%;                \/* \u1ea2nh s\u1ea3n ph\u1ea9m bung r\u1ed9ng 100% khung nh\u00ecn *\/\n    }\n\n    .product-summary {\n        padding-right: 0;\n    }\n\n    .shop-info-bar {\n        flex-direction: column;\n        align-items: flex-start;\n        gap: 2rem;\n    }\n\n    .shop-info__left {\n        width: 100%;\n        border-right: none;\n        border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n        padding-right: 0;\n        padding-bottom: 2rem;\n    }\n\n    .shop-info__right {\n        width: 100%;\n        padding-left: 0;\n        column-gap: 2rem;\n    }\n\n    .product-content-layout {\n        flex-direction: column;\n    }\n\n    .product-content__sidebar {\n        width: 100%;\n    }\n\n```\n\n### B\u01b0\u1edbc 2: C\u1eadp nh\u1eadt khu v\u1ef1c 2 \u2014 Ri\u00eang cho Tablet \/ iPad (`740px` $\\rightarrow$ `1023px`)\n\nTrong kh\u1ed1i `@media (min-width: 740px) and (max-width: 1023px)` c\u1ee7a file `responsive.css`, h\u00e3y b\u1ed5 sung quy t\u1eafc chia **4 c\u1ed9t s\u1ea3n ph\u1ea9m** v\u00e0 **3 c\u1ed9t Footer** cho m\u00e0n h\u00ecnh m\u00e1y t\u00ednh b\u1ea3ng:\n\nCSS\n\n```\n\/* B\u1ed5 sung ti\u1ebfp v\u00e0o b\u00ean trong @media (min-width: 740px) and (max-width: 1023px) c\u1ee7a .\/assets\/css\/responsive.css *\/\n\n    \/* Tr\u00ean iPad: Gi\u1eef nguy\u00ean l\u01b0\u1edbi Hero Banner nh\u01b0ng gi\u1ea3m chi\u1ec1u cao m\u1ed7i t\u1ea7ng xu\u1ed1ng 95px cho c\u00e2n \u0111\u1ed1i *\/\n    .hero-banner-grid {\n        grid-template-rows: 9.5rem 9.5rem;\n    }\n\n    \/* L\u01b0\u1edbi s\u1ea3n ph\u1ea9m (G\u1ee3i \u00dd H\u00f4m Nay & Danh M\u1ee5c) tr\u00ean iPad hi\u1ec3n th\u1ecb 4 C\u1ed9t \/ H\u00e0ng *\/\n    .product-grid,\n    .catalog-product-grid {\n        grid-template-columns: repeat(4, 1fr);\n    }\n\n    \/* Mega Footer tr\u00ean iPad r\u00fat t\u1eeb 5 c\u1ed9t xu\u1ed1ng 3 c\u1ed9t *\/\n    .footer-top-grid {\n        grid-template-columns: repeat(3, 1fr);\n    }\n\n```\n\n### B\u01b0\u1edbc 3: C\u1eadp nh\u1eadt khu v\u1ef1c 3 \u2014 Ri\u00eang cho \u0110i\u1ec7n tho\u1ea1i Mobile (`max-width: 739px`)\n\nTrong kh\u1ed1i `@media (max-width: 739px)` \u1edf cu\u1ed1i file `responsive.css`, h\u00e3y b\u1ed5 sung c\u00e1c quy t\u1eafc t\u1ed1i \u01b0u ri\u00eang cho m\u00e0n h\u00ecnh d\u1ecdc c\u1ee7a \u0111i\u1ec7n tho\u1ea1i:\n\nCSS\n\n```\n\/* B\u1ed5 sung ti\u1ebfp v\u00e0o b\u00ean trong @media (max-width: 739px) c\u1ee7a .\/assets\/css\/responsive.css *\/\n\n    \/* 1. Hero Banner tr\u00ean \u0110i\u1ec7n tho\u1ea1i: Ch\u1ec9 gi\u1eef l\u1ea1i Banner Slider ch\u00ednh (1 c\u1ed9t), \u1ea9n 2 banner ph\u1ee5 b\u00ean ph\u1ea3i! *\/\n    .hero-banner-grid {\n        grid-template-columns: 1fr;\n        grid-template-rows: 15rem;  \/* Cao 150px v\u1eeba v\u1eb7n m\u00e0n h\u00ecnh \u0111i\u1ec7n tho\u1ea1i *\/\n    }\n\n    .hero-banner-main {\n        grid-row: 1 \/ 2;\n    }\n\n    .hero-banner-sub {\n        display: none;              \/* \u1ea8n 2 banner nh\u1ecf b\u00ean ph\u1ea3i gi\u1ed1ng Shopee Mobile *\/\n    }\n\n    \/* 2. Icon D\u1ecbch v\u1ee5 & Danh m\u1ee5c tr\u00ean m\u00e0n h\u00ecnh nh\u1ecf: M\u1ed7i c\u1ed9t chi\u1ebfm 23% (Hi\u1ec7n 4 c\u1ed9t + l\u1ea5p l\u00f3 c\u1ed9t 5) *\/\n    .hero-service-item {\n        flex: 0 0 23%;\n    }\n\n    .category-grid {\n        grid-auto-columns: 28%;     \/* M\u1ed7i c\u1ed9t Danh m\u1ee5c r\u1ed9ng 28% m\u00e0n h\u00ecnh \u0111i\u1ec7n tho\u1ea1i *\/\n    }\n\n    \/* 3. Flash Sale tr\u00ean \u0110i\u1ec7n tho\u1ea1i: M\u1ed7i s\u1ea3n ph\u1ea9m chi\u1ebfm 42% chi\u1ec1u ngang *\/\n    .flash-sale-header {\n        padding: 0 1.2rem;\n    }\n\n    .flash-sale-title {\n        font-size: 1.6rem;\n    }\n\n    .flash-sale-body {\n        padding: 1.2rem;\n    }\n\n    .flash-sale-item {\n        flex: 0 0 42%;\n    }\n\n    \/* 4. L\u01af\u1edaI S\u1ea2N PH\u1ea8M TR\u00caN \u0110I\u1ec6N THO\u1ea0I: Chu\u1ea9n 2 C\u1ed9t \/ H\u00e0ng (repeat(2, 1fr)) *\/\n    .product-grid,\n    .catalog-product-grid {\n        grid-template-columns: repeat(2, 1fr);\n        gap: 0.8rem;                \/* Khe h\u1edf 8px g\u1ecdn g\u00e0ng tr\u00ean Mobile *\/\n    }\n\n    \/* N\u00fat \"Xem th\u00eam\" co l\u1ea1i v\u1eeba khung m\u00e0n h\u00ecnh \u0111i\u1ec7n tho\u1ea1i *\/\n    .daily-discover-more-btn {\n        min-width: 100%;\n    }\n\n    \/* Thu g\u1ecdn kho\u1ea3ng c\u00e1ch c\u00e1c s\u1ed1 c\u1ee7a Thanh Ph\u00e2n Trang (Pagination) \u0111\u1ec3 kh\u00f4ng tr\u00e0n m\u00e0n h\u00ecnh *\/\n    .pagination {\n        gap: 0.4rem;\n    }\n\n    .pagination-item__link {\n        min-width: 3.2rem;\n        height: 2.8rem;\n        font-size: 1.6rem;\n    }\n\n    \/* 5. Thu g\u1ecdn Form Popup \u0110\u0103ng K\u00fd \/ \u0110\u0103ng Nh\u1eadp v\u1eeba kh\u00edt m\u00e0n h\u00ecnh \u0111i\u1ec7n tho\u1ea1i *\/\n    .auth-form {\n        width: calc(100vw - 2.4rem); \/* R\u1ed9ng b\u1eb1ng m\u00e0n h\u00ecnh tr\u1eeb \u0111i 12px l\u1ec1 m\u1ed7i b\u00ean *\/\n    }\n\n    .auth-form__container {\n        padding: 0 1.6rem;\n    }\n\n    .auth-form__socials {\n        padding: 1.2rem 1.6rem;\n        flex-direction: column;     \/* X\u1ebfp ch\u1ed3ng d\u1ecdc c\u00e1c n\u00fat Facebook \/ Google tr\u00ean m\u00e0n h\u00ecnh h\u1eb9p *\/\n    }\n\n    \/* 6. Mega Footer tr\u00ean \u0110i\u1ec7n tho\u1ea1i: X\u1ebfp th\u00e0nh 2 c\u1ed9t g\u1ecdn g\u00e0ng *\/\n    .footer-top-grid {\n        grid-template-columns: repeat(2, 1fr);\n        gap: 1.6rem;\n    }\n\n    .footer-policy-list {\n        flex-direction: column;\n        gap: 1.2rem;\n    }\n\n    .footer-policy-item {\n        border-right: none;\n    }\n\n```\n\n![image.png](https:\/\/images.viblo.asia\/7191e45f-e3d8-429f-ad2a-029caa5f68e5.png)\n\n## 4. Gi\u1ea3i m\u00e3 d\u00f2ng code: 3 Tuy\u1ec7t chi\u00eau UX \"\u0110\u00e1ng \u0111\u1ed3ng ti\u1ec1n b\u00e1t g\u1ea1o\" trong B\u00e0i 26\n\n1.  **Nguy\u00ean l\u00fd \"Peek-a-boo\" (L\u1ea5p l\u00f3 `22%` \/ `28%`) k\u00edch th\u00edch ng\u01b0\u1eddi d\u00f9ng vu\u1ed1t ngang:**\n    \n    -   B\u1ea1n c\u00f3 th\u1eafc m\u1eafc t\u1ea1i sao \u1edf `.hero-service-item` ch\u00fang ta kh\u00f4ng chia tr\u00f2n `25%` ($100\\% \/ 4 = 25\\%$ \u0111\u1ec3 hi\u1ec7n ch\u1eb5n 4 icon), m\u00e0 l\u1ea1i \u0111\u1eb7t **`flex: 0 0 22%`** ho\u1eb7c **`28%`** \u1edf `.category-grid` kh\u00f4ng?\n        \n    -   Trong t\u00e2m l\u00fd h\u1ecdc thi\u1ebft k\u1ebf Mobile UX, n\u1ebfu b\u1ea1n chia tr\u00f2n `25%` v\u1eeba kh\u00edt 4 icon tr\u00ean m\u00e0n h\u00ecnh v\u00e0 l\u1ea1i \u1ea9n \u0111i thanh cu\u1ed9n ngang (`scrollbar`), ng\u01b0\u1eddi d\u00f9ng s\u1ebd **t\u01b0\u1edfng r\u1eb1ng danh s\u00e1ch ch\u1ec9 c\u00f3 \u0111\u00fang 4 m\u1ee5c \u0111\u00f3 th\u00f4i** v\u00e0 kh\u00f4ng bao gi\u1edd bi\u1ebft r\u1eb1ng c\u00f3 th\u1ec3 vu\u1ed1t sang ph\u1ea3i!\n        \n    -   \u0110\u1eb7t `22%` ho\u1eb7c `28%` gi\u00fap **m\u1ee5c th\u1ee9 5 b\u1ecb c\u1eaft m\u1ea5t m\u1ed9t n\u1eeda \u1edf s\u00e1t m\u00e9p ph\u1ea3i m\u00e0n h\u00ecnh** (Hi\u1ec7u \u1ee9ng _Peek-a-boo \/ Affordance_). Nh\u00ecn th\u1ea5y n\u1eeda chi\u1ebfc icon b\u1ecb khu\u1ea5t, n\u00e3o b\u1ed9 ng\u01b0\u1eddi d\u00f9ng t\u1ef1 \u0111\u1ed9ng hi\u1ec3u ngay l\u1eadp t\u1ee9c: _\"\u00c0! C\u00f2n n\u1ed9i dung \u1edf b\u00ean ph\u1ea3i, h\u00e3y l\u1ea5y ng\u00f3n tay g\u1ea1t ngang qua!\"_\n        \n2.  **K\u1ef9 thu\u1eadt `grid-auto-flow: column` + `grid-template-rows: repeat(2, auto)` tr\u00ean Danh M\u1ee5c Ng\u00e0nh H\u00e0ng:**\n    \n    -   Tr\u00ean b\u1ea3n PC, 20 ng\u00e0nh h\u00e0ng \u0111\u01b0\u1ee3c chia c\u1ed1 \u0111\u1ecbnh 10 c\u1ed9t (`grid-template-columns: repeat(10, 1fr)`). N\u1ebfu l\u00ean \u0111i\u1ec7n tho\u1ea1i b\u1ea1n \u0111\u1ed5i th\u00e0nh `repeat(4, 1fr)`, 20 ng\u00e0nh h\u00e0ng s\u1ebd r\u1edbt xu\u1ed1ng th\u00e0nh **5 h\u00e0ng d\u1ecdc d\u00e0i l\u00ea th\u00ea** chi\u1ebfm h\u1ebft c\u1ea3 m\u00e0n h\u00ecnh!\n        \n    -   B\u1eb1ng c\u00e1ch k\u1ebft h\u1ee3p `grid-auto-flow: column` v\u00e0 `grid-template-rows: repeat(2, auto)`, CSS Grid s\u1ebd gi\u1eef nguy\u00ean **\u0111\u00fang 2 t\u1ea7ng (2 h\u00e0ng ngang)** nh\u01b0 tr\u00ean App Shopee, v\u00e0 t\u1ef1 \u0111\u1ed9ng x\u1ebfp c\u00e1c ng\u00e0nh h\u00e0ng th\u1ee9 9, 10, 11... n\u1ed1i ti\u1ebfp nhau v\u1ec1 ph\u00eda b\u00ean ph\u1ea3i \u0111\u1ec3 ng\u01b0\u1eddi d\u00f9ng vu\u1ed1t ngang c\u1ea3 2 t\u1ea7ng c\u00f9ng l\u00fac!\n        \n3.  **\u0110\u01a1n v\u1ecb `100vw` k\u1ebft h\u1ee3p `calc()` cho Popup Modal tr\u00ean \u0111i\u1ec7n tho\u1ea1i (`.auth-form`):**\n    \n    -   \u1ede B\u00e0i 16, ch\u00fang ta \u0111\u1eb7t `.auth-form { width: 50rem; }` (`500px`). Tr\u00ean m\u00e0n h\u00ecnh \u0111i\u1ec7n tho\u1ea1i r\u1ed9ng `375px`, chi\u1ebfc h\u1ed9p `500px` s\u1ebd b\u1ecb tr\u00e0n m\u1ea5t c\u1ea3 2 b\u00ean m\u00e9p!\n        \n    -   \u0110\u1ed5i th\u00e0nh **`width: calc(100vw - 2.4rem);`** (L\u1ea5y 100% chi\u1ec1u r\u1ed9ng m\u00e0n h\u00ecnh thi\u1ebft b\u1ecb tr\u1eeb \u0111i `24px`) gi\u00fap c\u1eeda s\u1ed5 \u0110\u0103ng K\u00fd \/ \u0110\u0103ng Nh\u1eadp lu\u00f4n co gi\u00e3n v\u1eeba v\u1eb7n v\u1edbi m\u1ecdi d\u00f2ng \u0111i\u1ec7n tho\u1ea1i t\u1eeb iPhone Mini \u0111\u1ebfn Samsung Galaxy Fold, c\u00e1ch \u0111\u1ec1u 2 m\u00e9p tr\u00e1i ph\u1ea3i \u0111\u00fang `12px`!\n        \n\n## 5. B\u00e0i t\u1eadp tr\u1ea3i nghi\u1ec7m th\u1ef1c t\u1ebf & H\u1eb9n g\u1eb7p \u1edf B\u00e0i 27!\n\nH\u00e3y l\u01b0u l\u1ea1i file `.\/assets\/css\/responsive.css`, quay sang tr\u00ecnh duy\u1ec7t nh\u1ea5n F5 v\u00e0 b\u1eadt ch\u1ebf \u0111\u1ed9 gi\u1ea3 l\u1eadp thi\u1ebft b\u1ecb di \u0111\u1ed9ng (**`F12` $\\rightarrow$ `Ctrl + Shift + M`**), ch\u1ecdn **iPhone 12 Pro (`390px`)** ho\u1eb7c **iPad Air (`820px`)**:\n\n1.  H\u00e3y th\u1eed d\u00f9ng chu\u1ed9t (tr\u00ean ch\u1ebf \u0111\u1ed9 m\u00f4 ph\u1ecfng c\u1ea3m \u1ee9ng, con tr\u1ecf chu\u1ed9t c\u1ee7a b\u1ea1n bi\u1ebfn th\u00e0nh h\u00ecnh tr\u00f2n ng\u00f3n tay) **k\u00e9o vu\u1ed1t ngang qua l\u1ea1i** tr\u00ean d\u00e3y **8 Icon D\u1ecbch V\u1ee5**, **B\u1ea3ng 20 Danh M\u1ee5c Ng\u00e0nh H\u00e0ng 2 t\u1ea7ng** v\u00e0 **Danh s\u00e1ch s\u1ea3n ph\u1ea9m Flash Sale**! B\u1ea1n s\u1ebd c\u1ea3m nh\u1eadn \u0111\u01b0\u1ee3c l\u1ef1c \"h\u00edt\" (`scroll-snap`) b\u1eaft d\u00ednh t\u1eebng c\u1ed9t v\u00e0o m\u00e9p tr\u00e1i m\u01b0\u1ee3t m\u00e0 nh\u01b0 \u0111ang l\u01b0\u1edbt App Shopee th\u1eadt tr\u00ean \u0111i\u1ec7n tho\u1ea1i!\n    \n2.  Cu\u1ed9n xu\u1ed1ng m\u1ee5c **G\u1ee3i \u00dd H\u00f4m Nay**: C\u00e1c th\u1ebb s\u1ea3n ph\u1ea9m gi\u1edd \u0111\u00e2y x\u1ebfp th\u00e0nh **2 c\u1ed9t c\u00e2n \u0111\u1ed1i** tr\u00ean iPhone (v\u00e0 **4 c\u1ed9t** khi b\u1ea1n ch\u1ecdn iPad Air), \u0111\u1ea7y \u0111\u1ee7 nh\u00e3n Y\u00eau th\u00edch 3D, gi\u00e1 ti\u1ec1n v\u00e0 s\u1ed1 l\u01b0\u1ee3ng \u0111\u00e3 b\u00e1n r\u00f5 r\u00e0ng!\n    \n3.  B\u1ea5m th\u1eed v\u00e0o n\u00fat \u0110\u0103ng Nh\u1eadp ho\u1eb7c m\u1edf Modal \u0111\u1ec3 th\u1ea5y h\u1ed9p tho\u1ea1i co g\u1ecdn v\u1eeba kh\u00edt m\u00e0n h\u00ecnh \u0111i\u1ec7n tho\u1ea1i.\n    \n\n**Th\u1eed th\u00e1ch kh\u00e1m ph\u00e1 d\u00e0nh cho b\u1ea1n tr\u01b0\u1edbc khi sang B\u00e0i 27:**\n\n1.  H\u00e3y th\u1eed v\u00e0o `.category-grid` trong `responsive.css`, t\u1ea1m th\u1eddi t\u1eaft d\u00f2ng `scroll-snap-type: x mandatory;` \u0111i r\u1ed3i vu\u1ed1t th\u1eed b\u1ea3ng Danh M\u1ee5c \u0111\u1ec3 so s\u00e1nh s\u1ef1 kh\u00e1c bi\u1ec7t gi\u1eefa cu\u1ed9n tr\u00f4i t\u1ef1 do v\u00e0 cu\u1ed9n c\u00f3 \u0111i\u1ec3m neo b\u1eaft d\u00ednh (`Scroll Snap`)!\n    \n2.  Tr\u00ean m\u00e0n h\u00ecnh Mobile (`<= 739px`), b\u1ea3ng **Trang Gi\u1ecf H\u00e0ng (6 c\u1ed9t \u1edf B\u00e0i 23)** v\u00e0 **Trang Thanh To\u00e1n (B\u00e0i 24)** v\u1eabn \u0111ang gi\u1eef c\u1ea5u tr\u00fac nhi\u1ec1u c\u1ed9t c\u1ee7a PC. \u1ede B\u00e0i 27 ti\u1ebfp theo, ch\u00fang ta s\u1ebd h\u1ecdc c\u00e1ch bi\u1ebfn m\u1ed9t d\u00f2ng b\u1ea3ng 6 c\u1ed9t c\u1ee7a PC th\u00e0nh **Th\u1ebb Gi\u1ecf H\u00e0ng R\u00fat G\u1ecdn (Mobile Cart Card)** v\u00e0 ho\u00e0n thi\u1ec7n **Thanh \u0110i\u1ec1u H\u01b0\u1edbng \u0110\u00e1y \u1ee8ng D\u1ee5ng (`Mobile Bottom Tab Bar`: Home | Mall | Live | Th\u00f4ng b\u00e1o | T\u00f4i)**!","published_at":"2026-11-14T11:59:05.000000Z","scheduled_publish_at":null,"is_published":true,"is_shared":false,"updated_at":"2026-11-14T21:28:02.000000Z","edited_at":"2026-11-14T11:59:04.000000Z","translation_source":null,"trend_at":null,"promoted_at":null,"reading_time":11,"points":0,"views_count":14,"clips_count":0,"comments_count":1,"rated_value":null,"promoted":false,"trending":false,"is_draft":false,"is_public":true,"locale_code":"vi","is_video":false,"thumbnail_url":"https:\/\/images.viblo.asia\/fecde3c3-d731-46c2-979c-e031665a0428.png","user":{"data":{"id":184801,"url":"https:\/\/viblo.asia\/u\/Tuanhuu1201","avatar":"765295b3-c266-4756-8f0b-6b149861811c.png","name":"Thanh H\u1ea3i Hu\u1ef3nh","username":"Tuanhuu1201","followers_count":1,"reputation":7,"posts_count":26,"banned_at":null,"level_partner":null,"following":false}},"tags":{"data":[{"slug":"htmlcss","name":"HTML\/CSS"}]},"commentators":{"data":[{"id":231969,"url":"https:\/\/viblo.asia\/u\/anbui839","avatar":"330d7019-372b-4525-8237-c28fb1e87209.jpg","name":"An Bui","username":"anbui839","followers_count":0,"reputation":0,"posts_count":0,"banned_at":null,"level_partner":null}]}},{"id":110643,"title":"[Series 100 B\u00e0i H\u1ecdc HTML\/CSS Th\u1ef1c Chi\u1ebfn - B\u00e0i 25]: Kh\u1edfi \u0110\u1ed9ng Ch\u01b0\u01a1ng 9 \u2014 L\u00e0m Ch\u1ee7 Responsive Web Design V\u1edbi @media queries, Thi\u1ebft L\u1eadp H\u1ec7 Th\u1ed1ng Breakpoints & Bi\u1ebfn H\u00f3a Header Shopee Tr\u00ean Tablet \/ Mobile!","slug":"ymJXDMlaJkq","url":"https:\/\/viblo.asia\/p\/series-100-bai-hoc-htmlcss-thuc-chien-bai-25-khoi-dong-chuong-9-lam-chu-responsive-web-design-voi-atmedia-queries-thiet-lap-he-thong-breakpoints-bien-hoa-header-shopee-tren-tablet-mobile-ymJXDMlaJkq","user_id":184801,"moderation":null,"transliterated":"series-100-bai-hoc-htmlcss-thuc-chien-bai-25-khoi-dong-chuong-9-lam-chu-responsive-web-design-voi-atmedia-queries-thiet-lap-he-thong-breakpoints-bien-hoa-header-shopee-tren-tablet-mobile","contents_short":"Ch\u00e0o m\u1eebng anh em \u0111\u00e3 ch\u1ea1m t\u1edbi c\u1ed9t m\u1ed1c l\u1ecbch s\u1eed: B\u00e0i 25 \u2014 Ho\u00e0n th\u00e0nh  ch\u1eb7ng \u0111\u01b0\u1eddng trong chu\u1ed7i 100 B\u00e0i H\u1ecdc L\u00e0m Ch\u1ee7 HTML\/CSS Qua D\u1ef1 \u00c1n Clone Shopee!\n\nTr\u1ea3i qua 24 b\u00e0i h\u1ecdc \u0111\u1ea7u ti\u00ean, ch\u00fang ta \u0111\u00e3 d\u1ef1ng n\u00ean m\u1ed9t \u0111\u1ebf ch\u1ebf giao di\u1ec7n Shopee Desktop (1200px) \u0111\u1ed3 s\u1ed9 t\u1eeb Trang ch\u1ee7, Popup \u0110\u0103ng nh\u1eadp, Trang T\u00ecm ki\u1ebfm, Chi ti\u1ebft s\u1ea3n ph\u1ea9m cho \u0111\u1ebfn Gi\u1ecf h\u00e0ng v\u00e0 Thanh to\u00e1n. Tuy nhi\u00ean, c\u00f3 m\u1ed9t s\u1ef1 th\u1eadt trong ng\u00e0nh Th\u01b0\u01a1ng m\u1ea1i \u0111i\u1ec7n...","contents":"Ch\u00e0o m\u1eebng anh em \u0111\u00e3 ch\u1ea1m t\u1edbi c\u1ed9t m\u1ed1c l\u1ecbch s\u1eed: **B\u00e0i 25 \u2014 Ho\u00e0n th\u00e0nh $\\frac{1}{4}$ ch\u1eb7ng \u0111\u01b0\u1eddng** trong chu\u1ed7i **100 B\u00e0i H\u1ecdc L\u00e0m Ch\u1ee7 HTML\/CSS Qua D\u1ef1 \u00c1n Clone Shopee**!\n\nTr\u1ea3i qua 24 b\u00e0i h\u1ecdc \u0111\u1ea7u ti\u00ean, ch\u00fang ta \u0111\u00e3 d\u1ef1ng n\u00ean m\u1ed9t \u0111\u1ebf ch\u1ebf giao di\u1ec7n Shopee Desktop (`1200px`) \u0111\u1ed3 s\u1ed9 t\u1eeb Trang ch\u1ee7, Popup \u0110\u0103ng nh\u1eadp, Trang T\u00ecm ki\u1ebfm, Chi ti\u1ebft s\u1ea3n ph\u1ea9m cho \u0111\u1ebfn Gi\u1ecf h\u00e0ng v\u00e0 Thanh to\u00e1n. Tuy nhi\u00ean, c\u00f3 m\u1ed9t s\u1ef1 th\u1eadt trong ng\u00e0nh Th\u01b0\u01a1ng m\u1ea1i \u0111i\u1ec7n t\u1eed m\u00e0 m\u1ecdi k\u1ef9 s\u01b0 Frontend \u0111\u1ec1u ph\u1ea3i bi\u1ebft: **H\u01a1n 80% l\u01b0u l\u01b0\u1ee3ng truy c\u1eadp mua s\u1eafm tr\u00ean Shopee hi\u1ec7n nay \u0111\u1ebfn t\u1eeb \u0110i\u1ec7n tho\u1ea1i di \u0111\u1ed9ng (Mobile) v\u00e0 M\u00e1y t\u00ednh b\u1ea3ng (Tablet)!**\n\nN\u1ebfu b\u1ea1n m\u1edf c\u00f4ng c\u1ee5 **Chrome DevTools (`F12`)**, b\u1eadt ch\u1ebf \u0111\u1ed9 gi\u1ea3 l\u1eadp \u0111i\u1ec7n tho\u1ea1i (**Toggle Device Toolbar - `Ctrl + Shift + M`**) v\u00e0 ch\u1ecdn m\u00e0n h\u00ecnh **iPhone 14 Pro Max (`430px`)** hay **iPad Mini (`768px`)**, b\u1ea1n s\u1ebd th\u1ea5y giao di\u1ec7n Desktop hi\u1ec7n t\u1ea1i c\u1ee7a ch\u00fang ta g\u1eb7p ngay 3 v\u1ea5n \u0111\u1ec1 nghi\u00eam tr\u1ecdng:\n\n1.  Thanh **Top Navbar** ch\u1ee9a qu\u00e1 nhi\u1ec1u ch\u1eef (_K\u00eanh Ng\u01b0\u1eddi B\u00e1n, Tr\u1edf th\u00e0nh Ng\u01b0\u1eddi b\u00e1n, T\u1ea3i \u1ee9ng d\u1ee5ng, Th\u00f4ng b\u00e1o..._) b\u1ecb \u00e9p ch\u1eb7t v\u00e0o nhau, g\u00e3y th\u00e0nh 3\u20134 d\u00f2ng \u0111\u00e8 l\u00ean thanh T\u00ecm ki\u1ebfm b\u00ean d\u01b0\u1edbi!\n    \n2.  Khung **Logo Shopee (`200px`)** v\u00e0 **Gi\u1ecf h\u00e0ng (`150px`)** chi\u1ebfm m\u1ea5t `350px` chi\u1ec1u ngang, khi\u1ebfn thanh T\u00ecm ki\u1ebfm \u1edf gi\u1eefa tr\u00ean m\u00e0n h\u00ecnh \u0111i\u1ec7n tho\u1ea1i (`375px`) b\u1ecb b\u00f3p ngh\u1eb9t ch\u1ec9 c\u00f2n r\u1ed9ng v\u1ecfn v\u1eb9n `25px` \u2014 kh\u00f4ng th\u1ec3 g\u00f5 ch\u1eef!\n    \n3.  D\u00e3y t\u1eeb kh\u00f3a g\u1ee3i \u00fd b\u00ean d\u01b0\u1edbi thanh Search v\u00e0 thanh S\u1eafp x\u1ebfp s\u1ea3n ph\u1ea9m b\u1ecb tr\u00e0n ra ngo\u00e0i m\u00e9p ph\u1ea3i m\u00e0n h\u00ecnh, xu\u1ea5t hi\u1ec7n thanh cu\u1ed9n ngang (`Horizontal Scrollbar`) r\u1ea5t kh\u00f3 ch\u1ecbu.\n    \n\nTrong **B\u00e0i 25** h\u00f4m nay \u2014 b\u00e0i m\u1edf m\u00e0n cho **Ch\u01b0\u01a1ng 9: Responsive Web Design**, ch\u00fang ta s\u1ebd h\u1ecdc c\u00e1ch thi\u1ebft l\u1eadp h\u1ec7 th\u1ed1ng **Media Queries (`@media`)** chu\u1ea9n m\u1ef1c, \u0111\u1ed3ng th\u1eddi \"ph\u1eabu thu\u1eadt th\u1ea9m m\u1ef9\" cho to\u00e0n b\u1ed9 v\u00f9ng **Header Shopee** \u0111\u1ec3 n\u00f3 t\u1ef1 \u0111\u1ed9ng thu g\u1ecdn th\u00e0nh giao di\u1ec7n Mobile App g\u1ecdn g\u00e0ng, k\u00e8m theo **Thanh l\u1ecdc nhanh tr\u01b0\u1ee3t ngang (`Mobile Sort Bar`)** \u0111\u1eb7c tr\u01b0ng tr\u00ean \u0111i\u1ec7n tho\u1ea1i!\n\n## 1. B\u1ea3n ch\u1ea5t c\u1ee7a Responsive Web Design & H\u1ec7 th\u1ed1ng 3 \u0110i\u1ec3m G\u00e3y (`Breakpoints`) chu\u1ea9n E-Commerce\n\n**Responsive Web Design (Thi\u1ebft k\u1ebf web \u0111\u00e1p \u1ee9ng)** l\u00e0 k\u1ef9 thu\u1eadt d\u00f9ng **CSS Media Queries (`@media`)** \u0111\u1ec3 ph\u00e1t hi\u1ec7n k\u00edch th\u01b0\u1edbc chi\u1ec1u r\u1ed9ng m\u00e0n h\u00ecnh c\u1ee7a thi\u1ebft b\u1ecb \u0111ang xem, t\u1eeb \u0111\u00f3 t\u1ef1 \u0111\u1ed9ng thay \u0111\u1ed5i b\u1ed1 c\u1ee5c (\u1ea9n b\u1edbt th\u00e0nh ph\u1ea7n r\u01b0\u1eddm r\u00e0, gi\u1ea3m s\u1ed1 c\u1ed9t t\u1eeb 6 xu\u1ed1ng 4 ho\u1eb7c 2 c\u1ed9t) sao cho v\u1eeba v\u1eb7n nh\u1ea5t v\u1edbi m\u1eaft ng\u01b0\u1eddi d\u00f9ng.\n\nH\u00e3y nh\u00ecn v\u00e0o s\u01a1 \u0111\u1ed3 minh h\u1ecda c\u00e1ch m\u1ed9t giao di\u1ec7n web t\u1ef1 \u0111\u1ed9ng co gi\u00e3n v\u00e0 s\u1eafp x\u1ebfp l\u1ea1i c\u00e1c kh\u1ed1i n\u1ed9i dung t\u1eeb m\u00e0n h\u00ecnh **Desktop** sang **Laptop**, **Tablet** v\u00e0 **Phone** d\u01b0\u1edbi \u0111\u00e2y:\n\n![image.png](https:\/\/images.viblo.asia\/23605f04-0c8b-4e77-997b-cb55f1cc6a7d.png)\n\nV\u00ec d\u1ef1 \u00e1n c\u1ee7a ch\u00fang ta \u0111ang \u0111i theo h\u01b0\u1edbng **Desktop-First** (x\u00e2y d\u1ef1ng b\u1ea3n PC \u0111\u1ea7y \u0111\u1ee7 tr\u01b0\u1edbc r\u1ed3i thu g\u1ecdn d\u1ea7n xu\u1ed1ng m\u00e0n h\u00ecnh nh\u1ecf), ch\u00fang ta s\u1ebd s\u1eed d\u1ee5ng thu\u1ed9c t\u00ednh **`max-width`** (\u00e1p d\u1ee5ng CSS khi chi\u1ec1u r\u1ed9ng m\u00e0n h\u00ecnh **nh\u1ecf h\u01a1n ho\u1eb7c b\u1eb1ng** m\u1ed9t con s\u1ed1 nh\u1ea5t \u0111\u1ecbnh) v\u1edbi 3 quy t\u1eafc v\u00e0ng:\n\nPlaintext\n\n```\n0px                        739px                    1023px                   1200px+\n|---------------------------|-------------------------|-------------------------|\n   \ud83d\udcf1 MOBILE (\u0110i\u1ec7n tho\u1ea1i)      \ud83d\udcdf TABLET (iPad\/Tab)      \ud83d\udcbb PC \/ DESKTOP L\u1edaN\n    (max-width: 739px)         (740px -> 1023px)          (min-width: 1024px)\n|___________________________|_________________________|\n              \u25b2\n   G\u1ed9p chung: MOBILE & TABLET (max-width: 1023px)\n```\n\n## 1. C\u00fa ph\u00e1p @media chung cho c\u1ea3 Mobile & Tablet (D\u01b0\u1edbi 1024px)\n**C\u00fa ph\u00e1p:** `@media (max-width: 1023px)`\n\n* \u1ea8n ho\u00e0n to\u00e0n thanh **Top Navbar** (34px) v\u00e0 d\u00e3y t\u1eeb kh\u00f3a **Hot Trend** b\u00ean d\u01b0\u1edbi thanh Search.\n* Thu g\u1ecdn chi\u1ec1u cao t\u1ed5ng c\u1ee7a Header t\u1eeb **120px** xu\u1ed1ng ch\u1ec9 c\u00f2n **68px**.\n* \u1ea8n danh s\u00e1ch s\u1ea3n ph\u1ea9m trong **Gi\u1ecf h\u00e0ng mini** khi hover (v\u00ec m\u00e0n h\u00ecnh c\u1ea3m \u1ee9ng kh\u00f4ng c\u00f3 chu\u1ed9t \u0111\u1ec3 hover).\n* B\u1eadt **Thanh S\u1eafp x\u1ebfp Mobile (Mobile Sort Bar)** b\u00e1m ngay d\u01b0\u1edbi \u0111\u00e1y Header.\n\n---\n\n## 2. Ri\u00eang cho Tablet (iPad) (T\u1eeb 740px \u0111\u1ebfn 1023px)\n**C\u00fa ph\u00e1p:** `@media (min-width: 740px) and (max-width: 1023px)`\n\n* Thu h\u1eb9p khung `.grid-container` xu\u1ed1ng **644px** ho\u1eb7c **100%** c\u00f3 padding **16px**.\n* Khung Gi\u1ecf h\u00e0ng thu g\u1ecdn v\u1ec1 **80px** s\u00e1t m\u00e9p ph\u1ea3i.\n\n---\n\n## 3. Ri\u00eang cho Mobile (\u0110i\u1ec7n tho\u1ea1i) (D\u01b0\u1edbi 740px)\n**C\u00fa ph\u00e1p:** `@media (max-width: 739px)`\n\n* \u1ea8n khung Logo l\u1edbn b\u00ean tr\u00e1i (ho\u1eb7c \u0111\u01b0a v\u00e0o gi\u1eefa), nh\u01b0\u1eddng **100% kh\u00f4ng gian** cho Thanh T\u00ecm Ki\u1ebfm v\u00e0 Icon Gi\u1ecf H\u00e0ng.\n* B\u1eadt n\u00fat **Icon T\u00ecm ki\u1ebfm \/ Menu Mobile** \u1edf g\u00f3c tr\u00e1i.\n\n> **T\u1ea1i sao l\u1ea1i c\u00f3 quy t\u1eafc g\u1ed9p `@media (max-width: 1023px)`?**\n> \n> N\u1ebfu b\u1ea1n vi\u1ebft ri\u00eang m\u1ed9t c\u1ee5c code \u1ea9n `.top-navbar` trong Tablet, r\u1ed3i l\u1ea1i copy-paste ch\u00ednh c\u1ee5c code \u0111\u00f3 xu\u1ed1ng Mobile, file CSS c\u1ee7a b\u1ea1n s\u1ebd b\u1ecb l\u1eb7p code g\u1ea5p \u0111\u00f4i! \u0110\u1eb7t nh\u1eefng \u0111i\u1ec3m chung c\u1ee7a c\u1ea3 hai thi\u1ebft b\u1ecb c\u1ea3m \u1ee9ng (Tablet + Mobile) v\u00e0o `@media (max-width: 1023px)` gi\u00fap b\u1ea1n ch\u1ec9 c\u1ea7n vi\u1ebft 1 l\u1ea7n m\u00e0 ch\u1ea1y tr\u00ean c\u1ea3 iPad l\u1eabn iPhone!\n\n## 2. Gi\u1ea3i ph\u1eabu Header Shopee tr\u00ean \u0110i\u1ec7n tho\u1ea1i: T\u1ed1i gi\u1ea3n \u0111\u1ec3 T\u1ed1i \u01b0u!\n\nTr\u00ean m\u00e0n h\u00ecnh \u0111i\u1ec7n tho\u1ea1i nh\u1ecf h\u1eb9p (`375px` \u2013 `430px`), ng\u01b0\u1eddi d\u00f9ng \u0111i\u1ec1u khi\u1ec3n b\u1eb1ng **ng\u00f3n tay c\u00e1i (Thumb Zone)** ch\u1ee9 kh\u00f4ng ph\u1ea3i con tr\u1ecf chu\u1ed9t ch\u00ednh x\u00e1c `1px`. Do \u0111\u00f3, Shopee th\u1ef1c hi\u1ec7n cu\u1ed9c \"\u0111\u1ea1i ph\u1eabu\" Header nh\u01b0 sau:\n\n1.  **Bi\u1ebfn h\u00ecnh t\u1eeb 2 t\u1ea7ng (`120px`) th\u00e0nh 1 t\u1ea7ng duy nh\u1ea5t (`68px`):**\n    \n    To\u00e0n b\u1ed9 ch\u1eef _K\u00eanh Ng\u01b0\u1eddi B\u00e1n, T\u1ea3i \u1ee9ng d\u1ee5ng, Th\u00f4ng b\u00e1o_ tr\u00ean `.top-navbar` v\u00e0 d\u00e3y t\u1eeb kh\u00f3a g\u1ee3i \u00fd `.header-search-suggest-list` \u0111\u01b0\u1ee3c \u1ea9n \u0111i b\u1eb1ng `display: none;`.\n    \n2.  **B\u1ed5 sung Thanh S\u1eafp X\u1ebfp Mobile (`header__sort-bar`) b\u00e1m d\u00ednh d\u01b0\u1edbi \u0111\u00e1y Header:**\n    \n    Tr\u00ean PC, thanh S\u1eafp x\u1ebfp (_Ph\u1ed5 bi\u1ebfn | M\u1edbi nh\u1ea5t | B\u00e1n ch\u1ea1y | Gi\u00e1_) n\u1eb1m m\u00e3i trong c\u1ed9t 10 ph\u1ea7n c\u1ee7a trang Danh m\u1ee5c. Nh\u01b0ng tr\u00ean Mobile & Tablet, Shopee g\u1eafn lu\u00f4n m\u1ed9t thanh 4 tab **Ph\u1ed5 bi\u1ebfn | M\u1edbi nh\u1ea5t | B\u00e1n ch\u1ea1y | Gi\u00e1** cao `46px` n\u1eb1m ngay s\u00e1t d\u01b0\u1edbi \u0111\u00e1y Header cam \u0111\u1ec3 ng\u01b0\u1eddi d\u00f9ng l\u01b0\u1edbt b\u1eb1ng 1 ng\u00f3n tay c\u1ef1c ti\u1ec7n!\n    \n\n## 3. Th\u1ef1c h\u00e0nh: T\u1ea1o File `responsive.css` & Bi\u1ebfn H\u00f3a Header Shopee \u0110a Thi\u1ebft B\u1ecb\n\n\u0110\u1ec3 code kh\u00f4ng b\u1ecb l\u1eabn l\u1ed9n v\u1edbi g\u1ea7n 1.000 d\u00f2ng CSS Desktop trong `main.css`, ch\u00fang ta s\u1ebd t\u1ea1o m\u1ed9t file ri\u00eang bi\u1ec7t mang t\u00ean **`.\/assets\/css\/responsive.css`** v\u00e0 nh\u00fang n\u00f3 \u1edf **d\u00f2ng cu\u1ed1i c\u00f9ng** trong th\u1ebb `<head>` (\u0111\u1ec3 n\u00f3 c\u00f3 quy\u1ec1n ghi \u0111\u00e8 l\u00ean c\u00e1c thu\u1ed9c t\u00ednh c\u1ee7a `main.css`).\n\n### B\u01b0\u1edbc 1: Nh\u00fang `responsive.css` v\u00e0 b\u1ed5 sung `header__sort-bar` v\u00e0o `index.html`\n\n1.  M\u1edf file `index.html`, t\u00ecm \u0111\u1ebfn th\u1ebb `<head>` \u1edf \u0111\u1ea7u trang v\u00e0 th\u00eam d\u00f2ng `<link>` t\u1edbi file `responsive.css` ngay d\u01b0\u1edbi `main.css`:\n    \n\nHTML\n\n```\n    <!-- 3. C\u00e1c file CSS n\u1ed9i b\u1ed9 c\u1ee7a d\u1ef1 \u00e1n (responsive.css lu\u00f4n n\u1eb1m cu\u1ed1i c\u00f9ng!) -->\n    <link rel=\"stylesheet\" href=\".\/assets\/css\/reset.css\">\n    <link rel=\"stylesheet\" href=\".\/assets\/css\/base.css\">\n    <link rel=\"stylesheet\" href=\".\/assets\/css\/main.css\">\n    <link rel=\"stylesheet\" href=\".\/assets\/css\/responsive.css\">\n<\/head>\n\n```\n\n2.  Ti\u1ebfp theo, v\u1eabn trong `index.html`, t\u00ecm \u0111\u1ebfn th\u1ebb `<header class=\"shopee-header\">`. Ngay b\u00ean d\u01b0\u1edbi th\u1ebb \u0111\u00f3ng `<\/div>` c\u1ee7a `.header-with-search` (tr\u01b0\u1edbc khi \u0111\u00f3ng `<\/header>`), b\u1ea1n h\u00e3y b\u1ed5 sung th\u00eam **Thanh S\u1eafp X\u1ebfp Mobile (`.header-sort-bar`)** (M\u1eb7c \u0111\u1ecbnh tr\u00ean PC thanh n\u00e0y s\u1ebd \u1ea9n \u0111i, ch\u1ec9 t\u1ef1 \u0111\u1ed9ng hi\u1ec7n ra khi m\u00e0n h\u00ecnh nh\u1ecf h\u01a1n `1024px`!):\n    \n\nHTML\n\n```\n        <!-- Khu v\u1ef1c Logo, T\u00ecm ki\u1ebfm v\u00e0 Gi\u1ecf h\u00e0ng -->\n        <div class=\"header-with-search\">\n            <!-- N\u00fat Menu \/ T\u00ecm ki\u1ebfm nhanh ch\u1ec9 hi\u1ec7n tr\u00ean Mobile (< 740px) -->\n            <label for=\"mobile-search-checkbox\" class=\"header-mobile-search\">\n                <i class=\"header-mobile-search-icon fa-solid fa-bars\"><\/i>\n            <\/label>\n\n            <!-- (Gi\u1eef nguy\u00ean .header-logo, .header-search-wrapper, .header-cart b\u00ean trong) -->\n            ...\n        <\/div>\n\n    <\/div>\n\n    <!-- THANH L\u1eccC NHANH D\u00c0NH RI\u00caNG CHO MOBILE & TABLET (M\u1eb7c \u0111\u1ecbnh \u1ea9n tr\u00ean PC) -->\n    <ul class=\"header-sort-bar\">\n        <li class=\"header-sort-item\">\n            <a href=\"#\" class=\"header-sort-link\">Li\u00ean quan<\/a>\n        <\/li>\n        <li class=\"header-sort-item header-sort-item--active\">\n            <a href=\"#\" class=\"header-sort-link\">M\u1edbi nh\u1ea5t<\/a>\n        <\/li>\n        <li class=\"header-sort-item\">\n            <a href=\"#\" class=\"header-sort-link\">B\u00e1n ch\u1ea1y<\/a>\n        <\/li>\n        <li class=\"header-sort-item\">\n            <a href=\"#\" class=\"header-sort-link\">\n                Gi\u00e1 <i class=\"fa-solid fa-sort\" style=\"margin-left: 4px; font-size: 1.2rem;\"><\/i>\n            <\/a>\n        <\/li>\n    <\/ul>\n\n<\/header>\n\n```\n\n### B\u01b0\u1edbc 2: Vi\u1ebft CSS m\u1eb7c \u0111\u1ecbnh cho `.header-sort-bar` trong `.\/assets\/css\/main.css`\n\nTr\u01b0\u1edbc khi vi\u1ebft Media Queries, h\u00e3y m\u1edf cu\u1ed1i file `main.css` v\u00e0 th\u00eam \u0111o\u1ea1n CSS \u0111\u1ecbnh h\u00ecnh cho thanh `.header-sort-bar` c\u00f9ng n\u00fat `.header-mobile-search` (m\u1eb7c \u0111\u1ecbnh \u0111\u1eb7t `display: none` tr\u00ean Desktop):\n\nCSS\n\n```\n\/* B\u1ed5 sung v\u00e0o cu\u1ed1i file .\/assets\/css\/main.css *\/\n\n\/* --- CH\u01af\u01a0NG 9 (B\u00c0I 25): C\u00c1C COMPONENT H\u1ed6 TR\u1ee2 MOBILE \/ TABLET HEADER --- *\/\n\n\/* N\u00fat Menu g\u00f3c tr\u00e1i tr\u00ean Mobile (M\u1eb7c \u0111\u1ecbnh \u1ea9n tr\u00ean PC & Tablet) *\/\n.header-mobile-search {\n    padding: 0 1.2rem;\n    display: none;\n    cursor: pointer;\n}\n\n.header-mobile-search-icon {\n    font-size: 2.4rem;\n    color: var(--white-color);\n}\n\n\/* Thanh S\u1eafp X\u1ebfp Mobile & Tablet b\u00e1m d\u01b0\u1edbi \u0111\u00e1y Header (M\u1eb7c \u0111\u1ecbnh \u1ea9n tr\u00ean PC) *\/\n.header-sort-bar {\n    position: fixed;\n    top: var(--header-height);\n    left: 0;\n    right: 0;\n    height: 4.6rem;                 \/* Cao 46px chu\u1ea9n Mobile App *\/\n    background-color: var(--white-color);\n    border-top: 1px solid #e5e5e5;\n    border-bottom: 1px solid #e5e5e5;\n    display: none;                  \/* \u1ea8n tr\u00ean m\u00e0n h\u00ecnh PC l\u1edbn *\/\n    z-index: calc(var(--z-sticky) - 1);\n}\n\n.header-sort-item {\n    flex: 1;                        \/* Chia \u0111\u1ec1u 4 tab m\u1ed7i tab r\u1ed9ng \u0111\u00fang 25% m\u00e0n h\u00ecnh! *\/\n}\n\n.header-sort-link {\n    color: var(--text-primary);\n    font-size: 1.4rem;\n    display: block;\n    height: 100%;\n    line-height: 4.6rem;\n    text-align: center;\n    position: relative;\n}\n\n\/* V\u1ebd v\u1ea1ch ng\u0103n c\u00e1ch d\u1ecdc gi\u1eefa 4 tab b\u1eb1ng ::before *\/\n.header-sort-link::before {\n    content: \"\";\n    position: absolute;\n    top: 50%;\n    left: 0;\n    height: 60%;\n    border-left: 1px solid #e5e5e5;\n    transform: translateY(-50%);\n}\n\n\/* X\u00f3a v\u1ea1ch ng\u0103n c\u00e1ch \u1edf tab \u0111\u1ea7u ti\u00ean b\u00ean tr\u00e1i *\/\n.header-sort-item:first-child .header-sort-link::before {\n    display: none;\n}\n\n\/* Tr\u1ea1ng th\u00e1i Tab \u0111ang ch\u1ecdn (Active): Ch\u1eef cam + G\u1ea1ch ch\u00e2n cam d\u00e0y 2px \u1edf \u0111\u00e1y *\/\n.header-sort-item--active .header-sort-link {\n    color: var(--shopee-primary);\n    border-bottom: 2px solid var(--shopee-primary);\n    font-weight: 500;\n}\n\n```\n\n### B\u01b0\u1edbc 3: T\u1ea1o file `.\/assets\/css\/responsive.css` v\u00e0 thi\u1ebft l\u1eadp 3 Breakpoints chu\u1ea9n m\u1ef1c!\n\nB\u00e2y gi\u1edd, h\u00e3y t\u1ea1o m\u1ed9t file m\u1edbi t\u00ean l\u00e0 **`responsive.css`** n\u1eb1m trong th\u01b0 m\u1ee5c `.\/assets\/css\/` v\u00e0 g\u00f5 \u0111o\u1ea1n code Responsive \u0111\u1ea7u ti\u00ean c\u1ee7a d\u1ef1 \u00e1n:\n\nCSS\n\n```\n\/* .\/assets\/css\/responsive.css *\/\n\n\/* ================================================================= *\/\n\/* 1. MOBILE & TABLET CHUNG (M\u00e0n h\u00ecnh nh\u1ecf h\u01a1n ho\u1eb7c b\u1eb1ng 1023px)      *\/\n\/* ================================================================= *\/\n@media (max-width: 1023px) {\n    \/* Thu g\u1ecdn chi\u1ec1u cao bi\u1ebfn Header t\u1eeb 120px xu\u1ed1ng 68px! *\/\n    :root {\n        --header-height: 6.8rem;\n        --navbar-height: 0px;\n    }\n\n    \/* Th\u00eam kho\u1ea3ng \u0111\u1ec7m 2 b\u00ean m\u00e9p m\u00e0n h\u00ecnh \u0111\u1ec3 ch\u1eef kh\u00f4ng d\u00ednh s\u00e1t vi\u1ec1n \u0111i\u1ec7n tho\u1ea1i\/iPad *\/\n    .grid-container {\n        padding-left: 1.2rem;\n        padding-right: 1.2rem;\n    }\n\n    \/* \u1ea8n c\u00e1c th\u00e0nh ph\u1ea7n ch\u1ec9 d\u00e0nh cho PC: Top Navbar, T\u1eeb kh\u00f3a Hot Trend & Thanh Sort Bar PC *\/\n    .top-navbar,\n    .header-search-suggest-list,\n    .home-filter {\n        display: none;\n    }\n\n    \/* C\u1ed1 \u0111\u1ecbnh Header v\u00e0 \u0111\u1ea9y ph\u1ea7n th\u00e2n trang xu\u1ed1ng b\u00f9 l\u1ea1i 68px (Header) + 46px (Sort bar) *\/\n    .shopee-header {\n        position: fixed;\n        top: 0;\n        left: 0;\n        right: 0;\n    }\n\n    .shopee-body {\n        margin-top: calc(var(--header-height) + 4.6rem); \/* 68px + 46px = 114px *\/\n    }\n\n    .header-search-wrapper {\n        margin-top: 0;              \/* X\u00f3a margin-top 12px c\u1ee7a b\u1ea3n PC \u0111\u1ec3 c\u0103n ch\u00ednh gi\u1eefa 68px *\/\n    }\n\n    \/* B\u1eadt Thanh S\u1eafp X\u1ebfp 4 c\u1ed9t d\u00e0nh cho Mobile & Tablet *\/\n    .header-sort-bar {\n        display: flex;\n    }\n\n    \/* Chuy\u1ec3n h\u1ed9p Gi\u1ecf h\u00e0ng mini sang tr\u00e0n ngang to\u00e0n m\u00e0n h\u00ecnh d\u01b0\u1edbi \u0111\u00e1y Header *\/\n    .header-cart-list {\n        position: fixed;\n        top: var(--header-height);\n        right: 0;\n        left: 0;\n        width: 100%;\n        padding: 0 calc((100% - 64.4rem) \/ 2);\n    }\n\n    \/* \u1ea8n m\u0169i t\u00ean tam gi\u00e1c c\u1ee7a Gi\u1ecf h\u00e0ng khi \u1edf d\u1ea1ng Full-width tr\u00ean Mobile\/Tablet *\/\n    .header-cart-list::before {\n        display: none;\n    }\n}\n\n\/* ================================================================= *\/\n\/* 2. RI\u00caNG CHO TABLET \/ IPAD (T\u1eeb 740px \u0111\u1ebfn 1023px)                  *\/\n\/* ================================================================= *\/\n@media (min-width: 740px) and (max-width: 1023px) {\n    .header-with-search {\n        padding: 0;\n    }\n\n    \/* Thu g\u1ecdn khung Logo v\u00e0 Gi\u1ecf h\u00e0ng tr\u00ean iPad \u0111\u1ec3 nh\u01b0\u1eddng ch\u1ed7 cho thanh T\u00ecm ki\u1ebfm *\/\n    .header-logo {\n        width: 14rem;\n    }\n\n    .header-logo-svg {\n        width: 12rem;\n    }\n\n    .header-cart {\n        width: 7rem;\n        justify-content: flex-end;\n    }\n}\n\n\/* ================================================================= *\/\n\/* 3. RI\u00caNG CHO MOBILE \/ \u0110I\u1ec6N THO\u1ea0I (Nh\u1ecf h\u01a1n ho\u1eb7c b\u1eb1ng 739px)        *\/\n\/* ================================================================= *\/\n@media (max-width: 739px) {\n    .header-with-search {\n        justify-content: space-between;\n        padding: 0 0.4rem;\n        gap: 0.8rem;\n    }\n\n    \/* Hi\u1ec7n n\u00fat Icon Menu g\u00f3c tr\u00e1i tr\u00ean \u0110i\u1ec7n tho\u1ea1i *\/\n    .header-mobile-search {\n        display: block;\n    }\n\n    \/* \u1ea8n b\u1edbt kh\u1ed1i Logo l\u1edbn 200px \u0111\u1ec3 Thanh T\u00ecm Ki\u1ebfm bung r\u1ed9ng tr\u00ean m\u00e0n h\u00ecnh h\u1eb9p 375px! *\/\n    .header-logo {\n        display: none;\n    }\n\n    \/* Thu g\u1ecdn k\u00edch th\u01b0\u1edbc n\u00fat k\u00ednh l\u00fap v\u00e0 \u00f4 input cho v\u1eeba ng\u00f3n tay tr\u00ean Mobile *\/\n    .header-search {\n        height: 3.6rem;\n    }\n\n    .header-search-input {\n        padding: 0 0.8rem;\n        font-size: 1.3rem;\n    }\n\n    .header-search-btn {\n        width: 4.8rem;\n        height: 3rem;\n    }\n\n    \/* L\u1ecbch s\u1eed t\u00ecm ki\u1ebfm bung r\u1ed9ng 100% chi\u1ec1u ngang m\u00e0n h\u00ecnh \u0111i\u1ec7n tho\u1ea1i *\/\n    .header-search-history {\n        position: fixed;\n        top: var(--header-height);\n        left: 0;\n        width: 100%;\n        box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);\n    }\n\n    .header-cart {\n        width: auto;\n        padding: 0 0.8rem;\n    }\n}\n\n```\n\n![image.png](https:\/\/images.viblo.asia\/c06b84d9-f3a0-41d7-85c2-b1ed5d6fc25e.png)\n\n## 4. Gi\u1ea3i m\u00e3 d\u00f2ng code: 3 Tuy\u1ec7t k\u1ef9 Responsive \"nh\u1ecf m\u00e0 c\u00f3 v\u00f5\" trong B\u00e0i 25\n\n1.  **Ghi \u0111\u00e8 Bi\u1ebfn CSS (`:root`) b\u00ean trong `@media (max-width: 1023px)`:**\n    \n    -   \u0110\u00e2y l\u00e0 kho\u1ea3nh kh\u1eafc b\u1ea1n th\u1ea5y quy\u1ebft \u0111\u1ecbnh d\u00f9ng bi\u1ebfn `--header-height` v\u00e0 h\u00e0m `calc()` t\u1eeb **B\u00e0i 02** ph\u00e1t huy t\u00e1c d\u1ee5ng k\u1ef3 di\u1ec7u nh\u01b0 th\u1ebf n\u00e0o!\n        \n    -   Tr\u00ean PC, `--header-height` l\u00e0 `120px` v\u00e0 `--navbar-height` l\u00e0 `34px`. Khi v\u00e0o `@media (max-width: 1023px)`, ch\u00fang ta ch\u1ec9 c\u1ea7n khai b\u00e1o l\u1ea1i:\n        \n        `--header-height: 6.8rem; --navbar-height: 0px;`\n        \n    -   Ngay l\u1eadp t\u1ee9c, bi\u1ebfn `--header-with-search-height: calc(var(--header-height) - var(--navbar-height))` t\u1ef1 \u0111\u1ed9ng t\u00ednh l\u1ea1i th\u00e0nh `68px - 0px = 68px`! To\u00e0n b\u1ed9 thanh `.header-sort-bar` v\u00e0 h\u1ed9p `.header-cart-list` (v\u1ed1n d\u00f9ng `top: var(--header-height)`) c\u0169ng **t\u1ef1 \u0111\u1ed9ng d\u1ecbch chuy\u1ec3n l\u00ean m\u1ed1c `68px`** m\u00e0 kh\u00f4ng ph\u1ea3i \u0111i s\u1eeda t\u1eebng d\u00f2ng `top: 120px`!\n        \n2.  **\u0110\u1ed5i h\u1ed9p L\u1ecbch s\u1eed t\u00ecm ki\u1ebfm & Gi\u1ecf h\u00e0ng t\u1eeb `position: absolute` sang `position: fixed` tr\u00ean Mobile:**\n    \n    -   Tr\u00ean PC (`1200px`), h\u1ed9p Gi\u1ecf h\u00e0ng r\u1ed9ng `400px` n\u1eb1m `position: absolute` ngay d\u01b0\u1edbi icon xe \u0111\u1ea9y.\n        \n    -   Nh\u01b0ng tr\u00ean m\u00e0n h\u00ecnh \u0111i\u1ec7n tho\u1ea1i iPhone SE (`320px`) hay iPhone 13 (`375px`), m\u1ed9t chi\u1ebfc h\u1ed9p c\u1ee9ng `400px` s\u1ebd **\u0111\u00e2m th\u1ee7ng m\u00e9p tr\u00e1i m\u00e0n h\u00ecnh**! B\u1eb1ng c\u00e1ch \u0111\u1ed5i sang `position: fixed; top: var(--header-height); left: 0; right: 0; width: 100%;`, h\u1ed9p Gi\u1ecf h\u00e0ng v\u00e0 L\u1ecbch s\u1eed t\u00ecm ki\u1ebfm l\u1eadp t\u1ee9c bi\u1ebfn th\u00e0nh m\u1ed9t ng\u0103n k\u00e9o tr\u1ea3i r\u1ed9ng v\u1eeba kh\u00edt 100% m\u00e0n h\u00ecnh \u0111i\u1ec7n tho\u1ea1i!\n        \n3.  **Th\u1ee7 thu\u1eadt v\u1ebd v\u1ea1ch ng\u0103n c\u00e1ch chi\u1ec1u cao 60% (`height: 60%`) tr\u00ean `.header-sort-link::before`:**\n    \n    -   Thay v\u00ec c\u1ed1 \u0111\u1ecbnh chi\u1ec1u cao v\u1ea1ch ng\u0103n c\u00e1ch b\u1eb1ng `px` nh\u01b0 \u1edf B\u00e0i 03, tr\u00ean thanh `.header-sort-bar` (`46px`), ch\u00fang ta \u0111\u1eb7t `height: 60%; top: 50%; transform: translateY(-50%);`. D\u00f9 sau n\u00e0y chi\u1ec1u cao thanh Sort Bar thay \u0111\u1ed5i, v\u1ea1ch k\u1ebb d\u1ecdc v\u1eabn lu\u00f4n b\u1eb1ng \u0111\u00fang 60% chi\u1ec1u cao c\u1ee7a thanh v\u00e0 c\u0103n gi\u1eefa ho\u00e0n h\u1ea3o!\n        \n\n## 5. B\u00e0i t\u1eadp tr\u1ea3i nghi\u1ec7m \"K\u00e9o th\u1ea3 m\u00e0n h\u00ecnh\" & H\u1eb9n g\u1eb7p \u1edf B\u00e0i 26!\n\nH\u00e3y l\u01b0u l\u1ea1i to\u00e0n b\u1ed9 c\u00e1c file (`index.html`, `main.css`, `responsive.css`), m\u1edf tr\u00ecnh duy\u1ec7t l\u00ean v\u00e0 nh\u1ea5n **`F12` (ho\u1eb7c `Ctrl + Shift + M`)** \u0111\u1ec3 b\u1eadt ch\u1ebf \u0111\u1ed9 Responsive!\n\n1.  H\u00e3y th\u1eed n\u1eafm c\u1ea1nh ph\u1ea3i c\u1ee7a m\u00e0n h\u00ecnh tr\u00ecnh duy\u1ec7t v\u00e0 **k\u00e9o thu nh\u1ecf t\u1eeb t\u1eeb** t\u1eeb `1200px` xu\u1ed1ng d\u01b0\u1edbi m\u1ed1c **`1023px`** (Ch\u1ebf \u0111\u1ed9 iPad \/ Tablet):\n    \n    -   L\u1eadp t\u1ee9c thanh Top Navbar v\u00e0 d\u00e3y t\u1eeb kh\u00f3a g\u1ee3i \u00fd bi\u1ebfn m\u1ea5t!\n        \n    -   Thanh Header thu g\u1ecdn v\u1ec1 `68px`, b\u00ean d\u01b0\u1edbi xu\u1ea5t hi\u1ec7n ngay thanh 4 tab **Li\u00ean quan | M\u1edbi nh\u1ea5t | B\u00e1n ch\u1ea1y | Gi\u00e1** s\u1eafc n\u00e9t!\n        \n2.  Ti\u1ebfp t\u1ee5c k\u00e9o nh\u1ecf xu\u1ed1ng d\u01b0\u1edbi m\u1ed1c **`739px`** (Ch\u1ebf \u0111\u1ed9 \u0110i\u1ec7n tho\u1ea1i iPhone `375px`):\n    \n    -   Kh\u1ed1i Logo l\u1edbn t\u1ef1 \u0111\u1ed9ng \u1ea9n \u0111i, nh\u01b0\u1eddng ch\u1ed7 cho n\u00fat **Menu 3 g\u1ea1ch (`\u2630`)** \u1edf g\u00f3c tr\u00e1i, thanh T\u00ecm ki\u1ebfm \u1edf gi\u1eefa bung r\u1ed9ng tho\u1ea3i m\u00e1i v\u00e0 icon Gi\u1ecf h\u00e0ng c\u00f3 s\u1ed1 `3` n\u1eb1m g\u1ecdn \u1edf g\u00f3c ph\u1ea3i \u2014 y h\u1ec7t giao di\u1ec7n Mobile Web c\u1ee7a Shopee th\u1eadt!\n        \n\n**Th\u1eed th\u00e1ch kh\u00e1m ph\u00e1 d\u00e0nh cho b\u1ea1n tr\u01b0\u1edbc khi sang B\u00e0i 26:**\n\n1.  H\u00e3y th\u1eed b\u1ea5m chu\u1ed9t v\u00e0o \u00f4 t\u00ecm ki\u1ebfm khi \u0111ang \u1edf m\u00e0n h\u00ecnh Mobile (`375px`) \u0111\u1ec3 xem h\u1ed9p **L\u1ecbch s\u1eed t\u00ecm ki\u1ebfm** gi\u1edd \u0111\u00e2y x\u1ed5 xu\u1ed1ng tr\u00e0n \u0111\u1ec1u 100% chi\u1ec1u ngang m\u00e0n h\u00ecnh \u0111i\u1ec7n tho\u1ea1i \u0111\u1eb9p m\u1eaft nh\u01b0 th\u1ebf n\u00e0o!\n    \n2.  B\u1ea1n c\u00f3 nh\u1edb k\u1ef9 thu\u1eadt **Checkbox Hack** \u1edf B\u00e0i 16 kh\u00f4ng? H\u00e3y th\u1eed t\u1ea1o m\u1ed9t `<input type=\"checkbox\" id=\"mobile-search-checkbox\">` \u0111\u1ec3 khi b\u1ea5m v\u00e0o n\u00fat Icon g\u00f3c tr\u00e1i (`.header-mobile-search`), m\u1ed9t thanh Menu tr\u01b0\u1ee3t t\u1eeb m\u00e9p tr\u00e1i m\u00e0n h\u00ecnh ra (`Mobile Drawer Sidebar`) s\u1ebd xu\u1ea5t hi\u1ec7n nh\u00e9!\n    \n\n\u1ede **B\u00e0i 26**, ch\u00fang ta s\u1ebd ti\u1ebfp t\u1ee5c cu\u1ed9c \"\u0111\u1ea1i ph\u1eabu Responsive\" xu\u1ed1ng ph\u1ea7n th\u00e2n trang: **T\u1ed1i \u01b0u C\u1ee5m Hero Banner, Thanh 8 Icon D\u1ecbch V\u1ee5 tr\u01b0\u1ee3t ngang b\u1eb1ng ng\u00f3n tay (`overflow-x: auto` + `scroll-snap-type`) v\u00e0 L\u01b0\u1edbi S\u1ea3n Ph\u1ea9m t\u1ef1 \u0111\u1ed9ng co t\u1eeb `6 C\u1ed9t` (PC) $\\rightarrow$ `4 C\u1ed9t` (Tablet) $\\rightarrow$ `2 C\u1ed9t` (Mobile)**!\n\n4 -> 2 c\u1ed9t) ngay ch\u1ee9?\" query=\"H\u00e3y gi\u00fap t\u00f4i so\u1ea1n ti\u1ebfp B\u00e0i 26 trong series: T\u1ed1i \u01b0u Responsive cho ph\u1ea7n Th\u00e2n trang Shopee tr\u00ean Tablet & Mobile (Hero Banner, Vu\u1ed1t ngang danh m\u1ee5c b\u1eb1ng CSS Scroll Snap v\u00e0 L\u01b0\u1edbi s\u1ea3n ph\u1ea9m 6 -> 4 -> 2 c\u1ed9t).\"\/>","published_at":"2026-11-14T11:49:05.000000Z","scheduled_publish_at":null,"is_published":true,"is_shared":false,"updated_at":"2026-11-14T22:59:02.000000Z","edited_at":"2026-11-14T11:52:47.000000Z","translation_source":null,"trend_at":null,"promoted_at":null,"reading_time":12,"points":0,"views_count":10,"clips_count":0,"comments_count":0,"rated_value":null,"promoted":false,"trending":false,"is_draft":false,"is_public":true,"locale_code":"vi","is_video":false,"thumbnail_url":"https:\/\/images.viblo.asia\/f7ef0aa0-579f-49fa-91bf-d875550386f3.png","user":{"data":{"id":184801,"url":"https:\/\/viblo.asia\/u\/Tuanhuu1201","avatar":"765295b3-c266-4756-8f0b-6b149861811c.png","name":"Thanh H\u1ea3i Hu\u1ef3nh","username":"Tuanhuu1201","followers_count":1,"reputation":7,"posts_count":26,"banned_at":null,"level_partner":null,"following":false}},"tags":{"data":[{"slug":"htmlcss","name":"HTML\/CSS"}]},"commentators":{"data":[]}},{"id":110658,"title":"How I Make Custom Reaction GIFs for Team Chats with a Text-to-GIF AI","slug":"vbLbjzmPLnk","url":"https:\/\/viblo.asia\/p\/how-i-make-custom-reaction-gifs-for-team-chats-with-a-text-to-gif-ai-vbLbjzmPLnk","user_id":231809,"moderation":null,"transliterated":"how-i-make-custom-reaction-gifs-for-team-chats-with-a-text-to-gif-ai","contents_short":"My team's reaction GIFs were always the same three recycled clips, until someone dared us to make our own. It turns out the fastest way is to simply describe the animation you want and let an AI generate the frames.\n\nWhat I wanted\n\n- A looping clip of a laptop exploding with confetti for release days.\n- A custom sticker of our team mascot nodding approvingly.\n- Something small enough to survive...","contents":"My team's reaction GIFs were always the same three recycled clips, until someone dared us to make our own. It turns out the fastest way is to simply describe the animation you want and let an AI generate the frames.\n\n## What I wanted\n\n- A looping clip of a laptop exploding with confetti for release days.\n- A custom sticker of our team mascot nodding approvingly.\n- Something small enough to survive Slack's compression.\n\n## How it went\n\nI type a short scene description, pick a style, and adjust how long each frame holds. I use [Text to GIF](https:\/\/texttogif.com\/) for exactly this: describe the scene, choose timing per frame, and export a loop that fits right into chat.\n\n## What I learned\n\n- One clear action per scene animates far better than a crowded description.\n- Short loops (8 to 12 frames) read better in chat than long ones.\n- Stylized motion beats realism at small sizes; nobody can tell at 120 pixels wide anyway.\n\nTen minutes of experimenting produced a small set of custom reactions the team now uses daily. If your channels feel stale, this is a fun afternoon project.","published_at":"2026-11-14T11:18:14.000000Z","scheduled_publish_at":null,"is_published":true,"is_shared":false,"updated_at":"2026-11-14T22:39:02.000000Z","edited_at":"2026-11-14T11:18:14.000000Z","translation_source":null,"trend_at":null,"promoted_at":null,"reading_time":0,"points":0,"views_count":13,"clips_count":0,"comments_count":0,"rated_value":null,"promoted":false,"trending":false,"is_draft":false,"is_public":true,"locale_code":"vi","is_video":false,"thumbnail_url":"https:\/\/images.viblo.asia\/c7fdaf50-a65b-4a0f-bc97-6ef6e2a876c0.png","user":{"data":{"id":231809,"url":"https:\/\/viblo.asia\/u\/willcz0913","avatar":null,"name":"will","username":"willcz0913","followers_count":0,"reputation":0,"posts_count":3,"banned_at":null,"level_partner":null,"following":false}},"tags":{"data":[{"slug":"anh-gif","name":"\u1ea3nh gif"}]},"commentators":{"data":[]}},{"id":110657,"title":"Turn Text into Animated GIFs with AI: A Practical Workflow","slug":"18J2ewxq4YK","url":"https:\/\/viblo.asia\/p\/turn-text-into-animated-gifs-with-ai-a-practical-workflow-18J2ewxq4YK","user_id":231809,"moderation":"pending","transliterated":"turn-text-into-animated-gifs-with-ai-a-practical-workflow","contents_short":"Custom reaction GIFs used to mean digging through stock libraries and hoping something close enough existed. A more direct workflow is to describe the scene in plain language and let an AI generator animate it for you.\n\nWhy generate GIFs from text\n\n- Stock GIFs rarely match a specific product demo, in-joke, or niche reaction.\n- Hand-animating frames in a timeline takes hours for a few seconds o...","contents":"Custom reaction GIFs used to mean digging through stock libraries and hoping something close enough existed. A more direct workflow is to describe the scene in plain language and let an AI generator animate it for you.\n\n## Why generate GIFs from text\n\n- Stock GIFs rarely match a specific product demo, in-joke, or niche reaction.\n- Hand-animating frames in a timeline takes hours for a few seconds of motion.\n- Text-to-GIF generators turn a sentence into a looping animation in seconds.\n\n## The workflow\n\n1. Describe the scene, for example: a cat typing on a laptop, meme style.\n2. Pick a visual style and the number of frames.\n3. Adjust per-frame timing, then export the loop.\n\nI tried this with [Text to GIF](https:\/\/texttogif.com\/) and had a usable looping reaction GIF in a couple of minutes, with no timeline editing required.\n\n## Tips for better results\n\n- Keep descriptions short and concrete; one clear action per scene animates more cleanly.\n- Fewer frames loop better for stickers and chat reactions.\n- Preview at the size you will actually post; small loops tolerate stylized motion better than photorealism.\n\nIf your team ships a lot of chat stickers or short social clips, generating GIFs straight from a text prompt is worth a spot in your toolkit.","published_at":"2026-11-14T11:14:56.000000Z","scheduled_publish_at":null,"is_published":true,"is_shared":false,"updated_at":"2026-11-14T11:16:21.000000Z","edited_at":"2026-11-14T11:16:21.000000Z","translation_source":null,"trend_at":null,"promoted_at":null,"reading_time":1,"points":0,"views_count":1,"clips_count":0,"comments_count":0,"rated_value":null,"promoted":false,"trending":false,"is_draft":false,"is_public":true,"locale_code":"vi","is_video":false,"thumbnail_url":"https:\/\/images.viblo.asia\/3340abce-5cfd-4468-811c-632586e17b3d.png","user":{"data":{"id":231809,"url":"https:\/\/viblo.asia\/u\/willcz0913","avatar":null,"name":"will","username":"willcz0913","followers_count":0,"reputation":0,"posts_count":3,"banned_at":null,"level_partner":null,"following":false}},"tags":{"data":[{"slug":"anh-gif","name":"\u1ea3nh gif"}]},"commentators":{"data":[]}},{"id":110655,"title":"\u0110\u1ed3ng b\u1ed9 l\u1eddi tho\u1ea1i v\u00e0 ti\u1ebfng n\u1ec1n trong m\u1ed9t l\u1ea7n render: quy tr\u00ecnh l\u00e0m video gi\u1ea3i th\u00edch k\u1ef9 thu\u1eadt","slug":"wd43EpPjLX9","url":"https:\/\/viblo.asia\/p\/dong-bo-loi-thoai-va-tieng-nen-trong-mot-lan-render-quy-trinh-lam-video-giai-thich-ky-thuat-wd43EpPjLX9","user_id":231953,"moderation":null,"transliterated":"dong-bo-loi-thoai-va-tieng-nen-trong-mot-lan-render-quy-trinh-lam-video-giai-thich-ky-thuat","contents_short":"M\u1ed9t video gi\u1ea3i th\u00edch k\u1ef9 thu\u1eadt th\u01b0\u1eddng th\u1ea5t b\u1ea1i \u1edf ch\u1ed7 ng\u01b0\u1eddi xem kh\u00f4ng ng\u1edd: h\u00ecnh th\u00ec r\u00f5, nh\u01b0ng \u00e2m thanh th\u00ec kh\u00f4ng thu\u1ed9c v\u1ec1 h\u00ecnh. Gi\u1ecdng n\u00f3i nh\u01b0 \u0111\u01b0\u1ee3c d\u00e1n l\u00ean tr\u00ean, ph\u00f2ng th\u00ec im b\u1ea5t th\u01b0\u1eddng, ho\u1eb7c ti\u1ebfng click chu\u1ed9t kh\u00f4ng kh\u1edbp v\u1edbi thao t\u00e1c tr\u00ean giao di\u1ec7n. V\u1edbi clip d\u00e0i 15\u201325 gi\u00e2y \u2014 v\u1eeba \u0111\u1ee7 \u0111\u1ec3 ch\u1ec9 m\u1ed9t thao t\u00e1c, m\u1ed9t kh\u00e1i ni\u1ec7m, m\u1ed9t c\u1ea3nh b\u00e1o \u2014 t\u00f4i kh\u00f4ng c\u00f2n t\u00e1ch ph\u1ea7n h\u00ecnh v\u00e0 ph\u1ea7n ti\u1ebfng th\u00e0nh hai c\u00f4ng \u0111o\u1ea1n xa ...","contents":"M\u1ed9t video gi\u1ea3i th\u00edch k\u1ef9 thu\u1eadt th\u01b0\u1eddng th\u1ea5t b\u1ea1i \u1edf ch\u1ed7 ng\u01b0\u1eddi xem kh\u00f4ng ng\u1edd: h\u00ecnh th\u00ec r\u00f5, nh\u01b0ng \u00e2m thanh th\u00ec kh\u00f4ng thu\u1ed9c v\u1ec1 h\u00ecnh. Gi\u1ecdng n\u00f3i nh\u01b0 \u0111\u01b0\u1ee3c d\u00e1n l\u00ean tr\u00ean, ph\u00f2ng th\u00ec im b\u1ea5t th\u01b0\u1eddng, ho\u1eb7c ti\u1ebfng click chu\u1ed9t kh\u00f4ng kh\u1edbp v\u1edbi thao t\u00e1c tr\u00ean giao di\u1ec7n. V\u1edbi clip d\u00e0i 15\u201325 gi\u00e2y \u2014 v\u1eeba \u0111\u1ee7 \u0111\u1ec3 ch\u1ec9 m\u1ed9t thao t\u00e1c, m\u1ed9t kh\u00e1i ni\u1ec7m, m\u1ed9t c\u1ea3nh b\u00e1o \u2014 t\u00f4i kh\u00f4ng c\u00f2n t\u00e1ch ph\u1ea7n h\u00ecnh v\u00e0 ph\u1ea7n ti\u1ebfng th\u00e0nh hai c\u00f4ng \u0111o\u1ea1n xa nhau. T\u00f4i vi\u1ebft ch\u00fang trong c\u00f9ng m\u1ed9t brief, r\u1ed3i render m\u1ed9t l\u1ea7n.\n\nB\u00e0i n\u00e0y l\u00e0 quy tr\u00ecnh t\u00f4i d\u00f9ng khi l\u00e0m c\u00e1c clip ng\u1eafn cho t\u00e0i li\u1ec7u k\u1ef9 thu\u1eadt: changelog, trang docs, ho\u1eb7c m\u1ed9t \u0111o\u1ea1n nh\u00fang trong b\u00e0i vi\u1ebft. M\u1ee5c ti\u00eau kh\u00f4ng ph\u1ea3i l\u00e0 phim. M\u1ee5c ti\u00eau l\u00e0 ng\u01b0\u1eddi xem nghe \u0111\u00fang c\u00e2u t\u00f4i \u0111\u00e3 vi\u1ebft, trong m\u1ed9t kh\u00f4ng gian nghe tin \u0111\u01b0\u1ee3c, kh\u1edbp v\u1edbi nh\u1eefng g\u00ec \u0111ang x\u1ea3y ra tr\u00ean m\u00e0n h\u00ecnh.\n\n## Vi\u1ebft l\u1eddi tho\u1ea1i tr\u01b0\u1edbc, v\u00e0 vi\u1ebft \u0111\u00fang t\u1eebng ch\u1eef\n\nT\u00f4i kh\u00f4ng nh\u1edd c\u00f4ng c\u1ee5 \u201cn\u00f3i v\u00e0i c\u00e2u th\u00e2n thi\u1ec7n v\u1ec1 API\u201d. T\u00f4i vi\u1ebft s\u1eb5n c\u00e2u, b\u1eb1ng \u0111\u00fang ng\u00f4n ng\u1eef s\u1ebd xu\u1ea5t hi\u1ec7n trong clip, r\u1ed3i d\u00e1n nguy\u00ean v\u0103n v\u00e0o prompt. M\u1ed9t clip 20 gi\u00e2y ch\u1ec9 c\u1ea7n m\u1ed9t ho\u1eb7c hai c\u00e2u. N\u1ebfu t\u00f4i nh\u1ed3i m\u1ed9t \u0111o\u1ea1n v\u0103n, gi\u1ecdng \u0111\u1ecdc s\u1ebd ch\u1ea1y \u0111ua v\u1edbi h\u00ecnh, v\u00e0 ng\u01b0\u1eddi xem kh\u00f4ng k\u1ecbp nh\u00ecn.\n\nV\u00ed d\u1ee5 cho m\u1ed9t clip v\u1ec1 tr\u1ea1ng th\u00e1i l\u1ed7i:\n\n> T\u1eeb danh s\u00e1ch tr\u1ed1ng, b\u1ea5m New, \u0111i\u1ec1n t\u00ean, r\u1ed3i Save. N\u1ebfu h\u00e0ng xu\u1ea5t hi\u1ec7n hai l\u1ea7n, \u0111\u00f3 ch\u00ednh l\u00e0 l\u1ed7i \u0111ang \u0111\u01b0\u1ee3c t\u00e1i hi\u1ec7n.\n\nT\u00f4i ghi lu\u00f4n c\u00e1ch \u0111\u1ecdc: t\u1ed1c \u0111\u1ed9 v\u1eeba ph\u1ea3i, kh\u00f4ng nh\u1ea5n nh\u01b0 qu\u1ea3ng c\u00e1o, kh\u00f4ng th\u00eam c\u00e2u ch\u00e8n. N\u1ebfu c\u00f3 thu\u1eadt ng\u1eef vi\u1ebft t\u1eaft, t\u00f4i ch\u00fa th\u00edch c\u00e1ch \u0111\u1ecdc m\u1ed9t l\u1ea7n, \u0111\u1ec3 l\u1ea7n render sau kh\u00f4ng \u0111\u1ed5i c\u00e1ch ph\u00e1t \u00e2m.\n\nKhi c\u00f3 th\u1ec3, t\u00f4i thu s\u1eb5n m\u1ed9t take gi\u1ecdng c\u1ee7a m\u00ecnh \u2014 \u0111i\u1ec7n tho\u1ea1i, ph\u00f2ng y\u00ean, \u0111\u00fang c\u00e2u tr\u00ean \u2014 v\u00e0 \u0111\u00ednh k\u00e8m nh\u01b0 tham chi\u1ebfu \u00e2m thanh. Take \u0111\u00f3 kh\u00f4ng c\u1ea7n \u0111\u1eb9p. N\u00f3 gi\u1eef l\u1eddi v\u00e0 nh\u1ecbp. Trong prompt t\u00f4i n\u00f3i r\u00f5: \u0111\u00e2y l\u00e0 gi\u1ecdng v\u00e0 \u0111\u00fang nh\u1eefng l\u1eddi n\u00e0y, \u0111\u1eebng vi\u1ebft l\u1ea1i.\n\n## Ti\u1ebfng n\u1ec1n l\u00e0 m\u1ed9t c\u00e2u, kh\u00f4ng ph\u1ea3i s\u1ef1 im l\u1eb7ng\n\nPh\u00f2ng im ho\u00e0n to\u00e0n nghe nh\u01b0 l\u1ed7i, kh\u00f4ng nh\u01b0 s\u1ef1 chuy\u00ean nghi\u1ec7p. T\u00f4i m\u00f4 t\u1ea3 ti\u1ebfng n\u1ec1n b\u1eb1ng m\u1ed9t c\u00e2u kh\u1edbp v\u1edbi kh\u00f4ng gian trong h\u00ecnh: \u201cph\u00f2ng l\u00e0m vi\u1ec7c nh\u1ecf, ti\u1ebfng qu\u1ea1t m\u00e1y t\u00ednh r\u1ea5t nh\u1eb9, kh\u00f4ng nh\u1ea1c\u201d. Ho\u1eb7c: \u201ckh\u00f4ng gian thu \u00e2m kh\u00f4, g\u1ea7n micro, kh\u00f4ng v\u1ecdng\u201d. T\u00f4i tr\u00e1nh ch\u1ea5t ch\u1ed3ng t\u00ednh t\u1eeb, v\u00e0 kh\u00f4ng \u0111\u1ec3 nh\u1ea1c n\u1ec1n chen v\u00e0o khi m\u1ee5c ti\u00eau l\u00e0 l\u1eddi tho\u1ea1i k\u1ef9 thu\u1eadt.\n\nN\u1ebfu t\u00f4i \u0111\u00e3 c\u00f3 v\u00e0i gi\u00e2y ti\u1ebfng ph\u00f2ng t\u1eeb m\u1ed9t l\u1ea7n ghi h\u00ecnh c\u0169, t\u00f4i \u0111\u00ednh k\u00e8m file \u0111\u00f3 v\u00e0 y\u00eau c\u1ea7u d\u00f9ng l\u00e0m n\u1ec1n, gi\u1eef d\u01b0\u1edbi l\u1eddi tho\u1ea1i, kh\u00f4ng thay b\u1eb1ng nh\u1ea1c. \u0110\u00e2y l\u00e0 m\u1ed9t trong nh\u1eefng c\u00e1ch d\u00f9ng audio reference \u1ed5n \u0111\u1ecbnh nh\u1ea5t. C\u00f4ng c\u1ee5 t\u00f4i \u0111ang d\u00f9ng, [Try Seedance 3.0](https:\/\/tryseedance3.com\/), cho ph\u00e9p g\u1eafn t\u1ed1i \u0111a 50 tham chi\u1ebfu h\u00ecnh, video v\u00e0 \u00e2m thanh trong m\u1ed9t l\u1ea7n t\u1ea1o, nh\u01b0ng v\u1edbi clip gi\u1ea3i th\u00edch t\u00f4i th\u01b0\u1eddng ch\u1ec9 c\u1ea7n v\u00e0i \u1ea3nh giao di\u1ec7n, m\u1ed9t clip chuy\u1ec3n \u0111\u1ed9ng ng\u1eafn n\u1ebfu c\u00f3, v\u00e0 m\u1ed9t file gi\u1ecdng ho\u1eb7c ti\u1ebfng ph\u00f2ng.\n\n## H\u00ecnh ph\u1ea3i ch\u1ecbu tr\u00e1ch nhi\u1ec7m v\u1edbi l\u1eddi\n\nL\u1eddi tho\u1ea1i kh\u00f4ng \u0111\u01b0\u1ee3c n\u00f3i v\u1ec1 m\u1ed9t n\u00fat m\u00e0 h\u00ecnh l\u1ea1i \u0111ang \u1edf m\u00e0n kh\u00e1c. T\u00f4i \u0111\u00ednh c\u00e1c \u1ea3nh ch\u1ee5p m\u00e0n h\u00ecnh theo \u0111\u00fang th\u1ee9 t\u1ef1 b\u01b0\u1edbc, v\u00e0 vi\u1ebft inventory \u1edf \u0111\u1ea7u prompt: \u1ea3nh 1 l\u00e0 m\u00e0n b\u1eaft \u0111\u1ea7u, \u1ea3nh 2 l\u00e0 m\u00e0n sau khi b\u1ea5m New, \u1ea3nh 3 l\u00e0 tr\u1ea1ng th\u00e1i l\u1ed7i. N\u1ebfu c\u00f3 m\u1ed9t \u0111o\u1ea1n ghi m\u00e0n h\u00ecnh x\u1ea5u nh\u01b0ng \u0111\u00fang qu\u1ef9 \u0111\u1ea1o chu\u1ed9t, t\u00f4i d\u00f9ng n\u00f3 l\u00e0m tham chi\u1ebfu chuy\u1ec3n \u0111\u1ed9ng, kh\u00f4ng ph\u1ea3i l\u00e0m b\u1ea3n cu\u1ed1i.\n\nPrompt m\u00f4 t\u1ea3 m\u1ed9t shot li\u00ean t\u1ee5c, v\u00ec \u0111\u00f3 l\u00e0 th\u1ee9 t\u00f4i render \u0111\u01b0\u1ee3c m\u1ed9t c\u00e1ch \u0111\u00e1ng tin c\u1eady: m\u1ed9t take t\u1ed1i \u0111a kho\u1ea3ng 30 gi\u00e2y, \u0111\u1ed9 ph\u00e2n gi\u1ea3i t\u1ed1i \u0111a 1080p, h\u00ecnh v\u00e0 ti\u1ebfng ra c\u00f9ng m\u1ed9t l\u1ea7n. T\u00f4i vi\u1ebft nh\u1ecbp b\u1eb1ng ng\u00f4n ng\u1eef th\u01b0\u1eddng, kh\u00f4ng b\u1eb1ng timeline ph\u1ee9c t\u1ea1p: \u201cc\u00e2u n\u00f3i v\u1ec1 n\u00fat New ph\u1ea3i r\u01a1i \u0111\u00fang l\u00fac con tr\u1ecf t\u1edbi n\u00fat \u0111\u00f3; sau c\u00e2u cu\u1ed1i, gi\u1eef im v\u00e0i nh\u1ecbp r\u1ed3i k\u1ebft th\u00fac, kh\u00f4ng th\u00eam l\u1eddi\u201d.\n\nT\u00f4i \u0111\u1eb7c bi\u1ec7t c\u1ea5m c\u00f4ng c\u1ee5 \u201cl\u00e0m \u0111\u1eb9p\u201d giao di\u1ec7n. Video gi\u1ea3i th\u00edch m\u00e0 \u0111\u1ed5i m\u00e0u n\u00fat, b\u1ecba th\u00eam thanh \u0111i\u1ec1u h\u01b0\u1edbng, ho\u1eb7c l\u00e0m m\u01b0\u1ee3t m\u1ed9t l\u1ed7i \u0111ang c\u1ea7n t\u00e1i hi\u1ec7n, th\u00ec kh\u00f4ng d\u00f9ng \u0111\u01b0\u1ee3c. N\u1ebfu take \u0111\u1ea7u ti\u00ean restyle UI, t\u00f4i ch\u1ec9 g\u1eedi l\u1ea1i m\u1ed9t ch\u1ec9 d\u1eabn: gi\u1eef nguy\u00ean giao di\u1ec7n theo \u1ea3nh tham chi\u1ebfu, kh\u00f4ng stylize.\n\n## V\u00f2ng s\u1eeda: m\u1ed7i l\u1ea7n ch\u1ec9 \u0111\u1ed5i ph\u1ea7n ti\u1ebfng ho\u1eb7c ph\u1ea7n kh\u1edbp nh\u1ecbp\n\nTake \u0111\u1ea7u t\u00f4i nghe tr\u01b0\u1edbc, r\u1ed3i m\u1edbi xem. L\u1eddi c\u00f3 \u0111\u00fang kh\u00f4ng, c\u00f3 nh\u1ea1c l\u1ea1 kh\u00f4ng, ti\u1ebfng ph\u00f2ng c\u00f3 \u1ed5n kh\u00f4ng, mi\u1ec7ng v\u00e0 gi\u1ecdng c\u00f3 g\u1ea7n nhau kh\u00f4ng. Take hai th\u01b0\u1eddng ch\u1ec9 s\u1eeda m\u1ed9t th\u1ee9: gi\u1ecdng b\u1ecb tr\u1ec5, ti\u1ebfng ph\u00f2ng qu\u00e1 m\u1ecfng, ho\u1eb7c m\u1ed9t t\u1eeb b\u1ecb nu\u1ed1t. T\u00f4i \u0111\u00ednh take g\u1ea7n \u0111\u00fang v\u00e0 vi\u1ebft \u0111\u00fang thay \u0111\u1ed5i, ch\u1ee9 kh\u00f4ng vi\u1ebft l\u1ea1i to\u00e0n b\u1ed9 brief. N\u1ebfu v\u1eeba \u0111\u1ed5i l\u1eddi v\u1eeba \u0111\u1ed5i g\u00f3c m\u00e1y trong c\u00f9ng m\u1ed9t l\u1ea7n, t\u00f4i s\u1ebd kh\u00f4ng bi\u1ebft v\u00ec sao take m\u1edbi l\u1ea1i l\u1ec7ch.\n\nT\u00e0i kho\u1ea3n m\u1edbi tr\u00ean trang t\u00f4i d\u1eabn \u1edf tr\u00ean c\u00f3 credit mi\u1ec5n ph\u00ed, \u0111\u1ee7 \u0111\u1ec3 ch\u1ea1y v\u00f2ng ba l\u1ea7n n\u00e0y tr\u00ean m\u1ed9t clip th\u1eadt, tr\u01b0\u1edbc khi \u00e1p d\u1ee5ng cho c\u1ea3 m\u1ed9t chu\u1ed7i changelog. T\u00f4i l\u01b0u prompt, \u1ea3nh v\u00e0 file gi\u1ecdng trong c\u00f9ng th\u01b0 m\u1ee5c v\u1edbi m\u00e3 issue ho\u1eb7c s\u1ed1 phi\u00ean b\u1ea3n docs, \u0111\u1ec3 sau n\u00e0y c\u1ea7n render l\u1ea1i th\u00ec kh\u00f4ng ph\u1ea3i \u0111o\u00e1n.\n\nM\u1ed9t clip gi\u1ea3i th\u00edch k\u1ef9 thu\u1eadt \u0111\u1ea1t khi ng\u01b0\u1eddi xem c\u00f3 th\u1ec3 v\u1eeba nh\u00ecn thao t\u00e1c v\u1eeba nghe \u0111\u00fang c\u00e2u \u0111\u00e3 \u0111\u01b0\u1ee3c vi\u1ebft s\u1eb5n, trong m\u1ed9t c\u0103n ph\u00f2ng nghe ra l\u00e0 th\u1eadt, v\u00e0 kh\u00f4ng c\u00f3 g\u00ec kh\u00e1c tranh v\u1edbi gi\u1ecdng n\u00f3i. \u0110\u00f3 l\u00e0 vi\u1ec7c c\u00f3 th\u1ec3 thi\u1ebft k\u1ebf tr\u00ean gi\u1ea5y, \u0111\u00ednh file, v\u00e0 ki\u1ec3m tra ngay t\u1eeb l\u1ea7n ph\u00e1t \u0111\u1ea7u ti\u00ean \u2014 kh\u00f4ng ph\u1ea3i ph\u1ea7n \u201cs\u1ebd s\u1eeda audio sau\u201d th\u01b0\u1eddng b\u1ecb b\u1ecf d\u1edf.","published_at":"2026-11-14T11:03:16.000000Z","scheduled_publish_at":null,"is_published":true,"is_shared":false,"updated_at":"2026-11-14T21:30:02.000000Z","edited_at":"2026-11-14T11:02:50.000000Z","translation_source":null,"trend_at":null,"promoted_at":null,"reading_time":7,"points":0,"views_count":13,"clips_count":0,"comments_count":0,"rated_value":null,"promoted":false,"trending":false,"is_draft":false,"is_public":true,"locale_code":"vi","is_video":false,"thumbnail_url":"https:\/\/images.viblo.asia\/b6e1e339-a6a1-4647-a6f7-43869f8d33bb.png","user":{"data":{"id":231953,"url":"https:\/\/viblo.asia\/u\/seedancejack","avatar":null,"name":"Jack","username":"seedancejack","followers_count":0,"reputation":0,"posts_count":1,"banned_at":null,"level_partner":null,"following":false}},"tags":{"data":[{"slug":"ai-video","name":"ai video"},{"slug":"audio","name":"audio"},{"slug":"documentation","name":"Documentation"},{"slug":"technical-writing","name":"Technical Writing"}]},"commentators":{"data":[]}},{"id":109678,"title":"[Vibe Coding Th\u1ef1c Chi\u1ebfn #14] T\u1ed1i \u01afu VPS, \u0110\u00e1nh Ch\u1ec9 M\u1ee5c Database & T\u0103ng T\u1ed1c To\u00e0n Di\u1ec7n Cho H\u1ec7 Th\u1ed1ng EzyPlatform!","slug":"AoJe8j2D41j","url":"https:\/\/viblo.asia\/p\/vibe-coding-thuc-chien-14-toi-uu-vps-danh-chi-muc-database-tang-toc-toan-dien-cho-he-thong-ezyplatform-AoJe8j2D41j","user_id":212424,"moderation":null,"transliterated":"vibe-coding-thuc-chien-14-toi-uu-vps-danh-chi-muc-database-tang-toc-toan-dien-cho-he-thong-ezyplatform","contents_short":"[Vibe Coding Th\u1ef1c Chi\u1ebfn #14] T\u1ed1i \u01afu VPS, \u0110\u00e1nh Ch\u1ec9 M\u1ee5c Database & T\u0103ng T\u1ed1c To\u00e0n Di\u1ec7n Cho H\u1ec7 Th\u1ed1ng EzyPlatform!\n\nT\u00e1c gi\u1ea3: Lee\nH\u1ea1 t\u1ea7ng th\u1ef1c nghi\u1ec7m: VPS Ubuntu 22.04 LTS (157.66.47.xxx - 2 vCPU, 2GB RAM)\nPortal Admin: https:\/\/1576647xxx-admin.interdata.io.vn\nC\u00f4ng c\u1ee5 \u0111o l\u01b0\u1eddng: htop, MySQL Slow Query Log, wrk load test, EzyPlatform DataIndex\nM\u1ee5c ti\u00eau: T\u0103ng t\u1ed1c \u0111\u1ed9 ph\u1ea3n h\u1ed3i g\u1ea5p 8.5 l\u1ea7n, gi\u1ea3m \u0111\u1ed9 tr\u1ec5 tru...","contents":"# [Vibe Coding Th\u1ef1c Chi\u1ebfn #14] T\u1ed1i \u01afu VPS, \u0110\u00e1nh Ch\u1ec9 M\u1ee5c Database & T\u0103ng T\u1ed1c To\u00e0n Di\u1ec7n Cho H\u1ec7 Th\u1ed1ng EzyPlatform!\n\n> **T\u00e1c gi\u1ea3:** Lee  \n> **H\u1ea1 t\u1ea7ng th\u1ef1c nghi\u1ec7m:** VPS Ubuntu 22.04 LTS (`157.66.47.xxx` - 2 vCPU, 2GB RAM)  \n> **Portal Admin:** `https:\/\/1576647xxx-admin.interdata.io.vn`  \n> **C\u00f4ng c\u1ee5 \u0111o l\u01b0\u1eddng:** `htop`, MySQL Slow Query Log, `wrk` load test, EzyPlatform DataIndex  \n> **M\u1ee5c ti\u00eau:** T\u0103ng t\u1ed1c \u0111\u1ed9 ph\u1ea3n h\u1ed3i g\u1ea5p 8.5 l\u1ea7n, gi\u1ea3m \u0111\u1ed9 tr\u1ec5 truy v\u1ea5n t\u1eeb 1,240ms xu\u1ed1ng 3.8ms, g\u00e1nh t\u1ea3i 700+ req\/s tr\u00ean VPS 2GB RAM!  \n\n---\n\nTrong h\u00e0nh tr\u00ecnh Vibe Coding, c\u00f3 m\u1ed9t kho\u1ea3nh kh\u1eafc m\u00e0 b\u1ea5t k\u1ef3 ai c\u0169ng s\u1ebd ph\u1ea3i \u0111\u1ed1i m\u1eb7t:  \nWebsite c\u1ee7a b\u1ea1n sau khi \u0111\u01b0\u1ee3c AI d\u1ef1ng giao di\u1ec7n lung linh, t\u00edch h\u1ee3p \u0111\u1ee7 gi\u1ecf h\u00e0ng, thanh to\u00e1n VietQR v\u00e0 \u0111\u0103ng nh\u1eadp Google... b\u1ed7ng m\u1ed9t ng\u00e0y \u0111\u1eb9p tr\u1eddi \u0111\u01b0\u1ee3c tung ra th\u1ecb tr\u01b0\u1eddng. Kh\u00e1ch h\u00e0ng b\u1eaft \u0111\u1ea7u \u00f9a v\u00e0o xem h\u00e0ng, v\u00e0i ch\u1ee5c ng\u01b0\u1eddi c\u00f9ng b\u1ea5m \u0111\u1eb7t b\u00e1nh sinh nh\u1eadt trong m\u1ed9t khung gi\u1edd Flash Sale.\n\nV\u00e0 r\u1ed3i... **B\u00d9M! \ud83d\udca5**  \nCPU VPS nh\u1ea3y d\u1ef1ng l\u00ean **98.4%**, RAM ch\u1ea1m \u0111\u1ec9nh, trang web quay m\u00f2ng m\u00f2ng r\u1ed3i b\u00e1o l\u1ed7i `504 Gateway Time-out`. Kh\u00e1ch h\u00e0ng b\u1ef1c b\u1ed9i b\u1ecf \u0111i, c\u00f2n b\u1ea1n th\u00ec cu\u1ed1ng cu\u1ed3ng m\u1edf terminal v\u1edbi c\u1ea3m gi\u00e1c b\u1ea5t l\u1ef1c!\n\n![Terminal htop CPU 98% v\u00e0 MySQL Slow Query ph\u00e1t hi\u1ec7n c\u00e2u l\u1ec7nh ngh\u1ebdn](https:\/\/images.viblo.asia\/57fd8acf-ddea-4fc2-a6ab-a9e1a7764efe.png)\n\nPh\u1ea3n x\u1ea1 \u0111\u1ea7u ti\u00ean c\u1ee7a nhi\u1ec1u anh em l\u00fac n\u00e0y l\u00e0: *\"Ch\u1eafc do VPS 2GB RAM y\u1ebfu qu\u00e1, ph\u1ea3i n\u1ea1p ti\u1ec1n n\u00e2ng c\u1ea5p l\u00ean g\u00f3i 4 Core 8GB RAM th\u00f4i!\"*.  \nKhoan \u0111\u00e3 b\u1ea1n \u01a1i! Vi\u1ec7c n\u00e2ng c\u1ea5p ph\u1ea7n c\u1ee9ng ch\u1ec9 gi\u1ed1ng nh\u01b0 b\u1ea1n c\u01a1i n\u1edbi m\u1ed9t c\u00e1i th\u00f9ng n\u01b0\u1edbc b\u1ecb r\u00f2 r\u1ec9: T\u1ed1n k\u00e9m g\u1ea5p 3-4 l\u1ea7n chi ph\u00ed h\u00e0ng th\u00e1ng nh\u01b0ng b\u1ea3n ch\u1ea5t v\u1ea5n \u0111\u1ec1 v\u1eabn n\u1eb1m \u1edf ch\u1ed7 **h\u1ec7 th\u1ed1ng ch\u01b0a \u0111\u01b0\u1ee3c t\u1ed1i \u01b0u h\u00f3a t\u1eeb g\u1ed1c**.\n\nH\u00f4m nay, m\u00ecnh s\u1ebd c\u00f9ng anh em th\u1ef1c chi\u1ebfn gi\u1ea3i ph\u1eabu v\u00e0 t\u1ed1i \u01b0u h\u00f3a to\u00e0n di\u1ec7n h\u1ec7 th\u1ed1ng EzyPlatform tr\u00ean VPS `157.66.47.xxx`. Ch\u00fang ta s\u1ebd bi\u1ebfn m\u1ed9t m\u00e1y ch\u1ee7 gi\u00e1 r\u1ebb 2 vCPU \/ 2GB RAM th\u00e0nh m\u1ed9t \"c\u1ed7 m\u00e1y t\u1ed1c \u0111\u1ed9\" c\u00f3 kh\u1ea3 n\u0103ng x\u1eed l\u00fd m\u01b0\u1ee3t m\u00e0 **h\u01a1n 700 requests\/gi\u00e2y**, k\u00e9o th\u1eddi gian truy v\u1ea5n d\u1eef li\u1ec7u t\u1eeb 1.2 gi\u00e2y xu\u1ed1ng ch\u1ec9 c\u00f2n **3.8 mili-gi\u00e2y**! \ud83d\ude80\n\n---\n\n## I. Ki\u1ebfn Tr\u00fac T\u1ed1i \u01afu Hi\u1ec7u N\u0103ng 4 T\u1ea7ng Trong EzyPlatform\n\nMu\u1ed1n h\u1ec7 th\u1ed1ng ch\u1ecbu t\u1ea3i kh\u1ee7ng, nguy\u00ean t\u1eafc v\u00e0ng c\u1ee7a k\u1ef9 s\u01b0 backend l\u00e0: **Kh\u00f4ng bao gi\u1edd \u0111\u1ec3 m\u1ecdi request ch\u1ea1y th\u1eb3ng v\u00e0o Database!**\n\nEzyPlatform cung c\u1ea5p m\u1ed9t ki\u1ebfn tr\u00fac \u0111\u1ec7m 4 t\u1ea7ng c\u1ef1c k\u1ef3 th\u00f4ng minh, ch\u1eb7n \u0111\u1ee9ng \u0111\u1ebfn 85% truy v\u1ea5n ngay t\u1eeb c\u00e1c t\u1ea7ng ngo\u00e0i tr\u01b0\u1edbc khi ch\u00fang k\u1ecbp \"ch\u1ea1m\" v\u00e0o \u0111\u0129a c\u1ee9ng:\n\n![Ki\u1ebfn Tr\u00fac Cache \u0110a T\u1ea7ng & Gi\u1ea3m T\u1ea3i Cho M\u00e1y Ch\u1ee7 EzyPlatform](https:\/\/images.viblo.asia\/d1a9d7e6-58ac-427b-bc0d-e82339b65064.png)\n\n1. **T\u1ea7ng 1 - Browser HTTP Cache (0ms Latency):**  \n   To\u00e0n b\u1ed9 h\u00ecnh \u1ea3nh b\u00e1nh, file CSS, JavaScript t\u0129nh \u0111\u01b0\u1ee3c g\u1eafn th\u1ebb `Cache-Control: public, max-age=31536000` k\u00e8m `ETag`. Khi kh\u00e1ch b\u1ea5m chuy\u1ec3n trang, tr\u00ecnh duy\u1ec7t t\u1ef1 l\u1ea5y t\u1eeb b\u1ed9 nh\u1edb m\u00e1y h\u1ecd ra render ngay l\u1eadp t\u1ee9c, ti\u00eau t\u1ed1n \u0111\u00fang **0 byte b\u0103ng th\u00f4ng VPS**.\n2. **T\u1ea7ng 2 - Nginx Reverse Proxy Micro-Cache (2ms Latency):**  \n   Nginx \u0111\u00f3ng vai tr\u00f2 \"ng\u01b0\u1eddi g\u00e1c c\u1ed5ng\", l\u01b0u cache ng\u1eafn h\u1ea1n (10 ph\u00fat) cho c\u00e1c trang danh m\u1ee5c s\u1ea3n ph\u1ea9m c\u00f4ng khai. Khi 100 kh\u00e1ch c\u00f9ng v\u00e0o xem danh m\u1ee5c b\u00e1nh ng\u1ecdt, Nginx tr\u1ea3 l\u1eddi tr\u1ef1c ti\u1ebfp t\u1eeb RAM c\u1ee7a h\u1ec7 \u0111i\u1ec1u h\u00e0nh m\u00e0 kh\u00f4ng th\u00e8m chuy\u1ec3n request v\u00e0o JVM.\n3. **T\u1ea7ng 3 - JVM In-Memory Cache (EzyCacheManager):**  \n   Nh\u1eefng d\u1eef li\u1ec7u c\u1ea5u h\u00ecnh, c\u00e2y danh m\u1ee5c v\u00e0 th\u00f4ng tin c\u1eeda h\u00e0ng \u0111\u01b0\u1ee3c n\u1ea1p s\u1eb5n v\u00e0o b\u1ed9 nh\u1edb Heap c\u1ee7a Java. T\u1ef7 l\u1ec7 Hit Rate \u0111\u1ea1t tr\u00ean **94%**, lo\u1ea1i b\u1ecf ho\u00e0n to\u00e0n vi\u1ec7c ph\u1ea3i query l\u1ea1i MySQL.\n4. **T\u1ea7ng 4 - Indexed MySQL Engine (DataIndex):**  \n   \u0110\u1ed1i v\u1edbi nh\u1eefng truy v\u1ea5n b\u1eaft bu\u1ed9c ph\u1ea3i ch\u1ea1m \u0111\u0129a (nh\u01b0 l\u1ecdc l\u1ecbch s\u1eed \u0111\u01a1n h\u00e0ng, ki\u1ec3m tra s\u1ed1 d\u01b0 \u0111i\u1ec3m Loyalty), h\u1ec7 th\u1ed1ng ch\u1ec9 m\u1ee5c **Composite Index** gi\u00fap database t\u00ecm ra d\u1eef li\u1ec7u trong ch\u01b0a \u0111\u1ea7y 4ms.\n\n---\n\n## II. B\u1ea3n Ch\u1ea5t C\u01a1 S\u1edf D\u1eef Li\u1ec7u: Full Table Scan vs B-Tree Composite Index\n\nH\u00e3y nh\u00ecn v\u00e0o log slow query \u1edf h\u00ecnh \u0111\u1ea7u b\u00e0i:\n```sql\nSELECT * FROM ezy_article_orders WHERE user_id = 1042 ORDER BY id DESC LIMIT 15;\n# Query_time: 1.240815s  Rows_examined: 100,000\n```\nT\u1ea1i sao m\u1ed9t c\u00e2u l\u1ec7nh \u0111\u01a1n gi\u1ea3n ch\u1ec9 l\u1ea5y 15 \u0111\u01a1n h\u00e0ng g\u1ea7n nh\u1ea5t c\u1ee7a kh\u00e1ch h\u00e0ng m\u00e0 l\u1ea1i ng\u1ed1n t\u1edbi **1.24 gi\u00e2y** v\u00e0 qu\u00e9t s\u1ea1ch **100,000 b\u1ea3n ghi**?\n\nC\u00e2u tr\u1ea3 l\u1eddi n\u1eb1m \u1edf c\u01a1 ch\u1ebf t\u00ecm ki\u1ebfm c\u1ee7a Database:\n\n![So s\u00e1nh Full Table Scan O(N) v\u00e0 Composite B-Tree Index Seek O(log N)](https:\/\/images.viblo.asia\/9862d8fe-7e31-4fc7-884d-71fd5ce49f14.png)\n\n- **Khi kh\u00f4ng c\u00f3 Index (Full Table Scan - O(N)):** MySQL bu\u1ed9c ph\u1ea3i ch\u1ea1y t\u1eeb \u0111\u1ea7u b\u1ea3ng \u0111\u1ebfn cu\u1ed1i b\u1ea3ng, \u0111\u1ecdc 100,000 d\u00f2ng t\u1eeb \u0111\u0129a v\u00e0o b\u1ed9 nh\u1edb, l\u1ecdc ra c\u00e1c d\u00f2ng c\u00f3 `user_id = 1042`, sau \u0111\u00f3 th\u1ef1c hi\u1ec7n thu\u1eadt to\u00e1n s\u1eafp x\u1ebfp (Using filesort) \u0111\u1ec3 l\u1ea5y ra 15 d\u00f2ng m\u1edbi nh\u1ea5t. \u0110\u00e2y ch\u00ednh l\u00e0 \"k\u1ebb s\u00e1t th\u1ee7\" nu\u1ed1t tr\u1ecdn 100% CPU c\u1ee7a VPS!\n- **Khi c\u00f3 Composite Index (Index Seek - O(log N)):** Ch\u00fang ta t\u1ea1o m\u1ed9t ch\u1ec9 m\u1ee5c B-Tree k\u1ebft h\u1ee3p `(user_id ASC, id DESC)`. L\u00fac n\u00e0y, MySQL nh\u1ea3y th\u1eb3ng t\u1edbi v\u1ecb tr\u00ed c\u1ee7a `user_id = 1042` tr\u00ean c\u00e2y ch\u1ec9 m\u1ee5c, \u0111i l\u00f9i \u0111\u00fang 15 nh\u00e1nh l\u00e0 l\u1ea5y \u0111\u01b0\u1ee3c ngay k\u1ebft qu\u1ea3. S\u1ed1 d\u00f2ng ph\u1ea3i ki\u1ec3m tra gi\u1ea3m t\u1eeb 100,000 xu\u1ed1ng \u0111\u00fang **15 d\u00f2ng**. T\u1ed1c \u0111\u1ed9 truy v\u1ea5n t\u0103ng v\u1ecdt **326 l\u1ea7n**!\n\n---\n\n## III. Th\u1ef1c Chi\u1ebfn 4 B\u01b0\u1edbc T\u1ed1i \u01afu H\u1ec7 Th\u1ed1ng Tr\u00ean VPS `157.66.47.xxx`\n\n### B\u01b0\u1edbc 1: B\u1eadt MySQL Slow Query Log \u0111\u1ec3 \"b\u1eaft b\u1ec7nh\"\n\n\u0110\u1ea7u ti\u00ean, b\u1ea1n SSH v\u00e0o VPS v\u00e0 m\u1edf file c\u1ea5u h\u00ecnh MySQL:\n```bash\nsudo nano \/etc\/mysql\/mysql.conf.d\/mysqld.cnf\n```\nTh\u00eam ho\u1eb7c b\u1ecf comment c\u00e1c d\u00f2ng sau:\n```ini\nslow_query_log = 1\nslow_query_log_file = \/var\/log\/mysql\/mysql-slow.log\nlong_query_time = 0.5\nlog_queries_not_using_indexes = 1\n```\nL\u01b0u l\u1ea1i v\u00e0 kh\u1edfi \u0111\u1ed9ng l\u1ea1i MySQL:\n```bash\nsudo systemctl restart mysql\n```\nT\u1eeb gi\u1edd, b\u1ea5t k\u1ef3 c\u00e2u query n\u00e0o ch\u1ea1y qu\u00e1 0.5s ho\u1eb7c kh\u00f4ng ch\u1ecbu d\u00f9ng index \u0111\u1ec1u s\u1ebd b\u1ecb ghi s\u1ed5 v\u00e0o file log \u0111\u1ec3 b\u1ea1n d\u1ec5 d\u00e0ng \"t\u00fam g\u00e1y\".\n\n---\n\n### B\u01b0\u1edbc 2: Thi\u1ebft l\u1eadp ch\u1ec9 m\u1ee5c th\u00f4ng minh trong EzyPlatform Admin (DataIndex)\n\nThay v\u00ec ph\u1ea3i g\u00f5 l\u1ec7nh SQL ph\u1ee9c t\u1ea1p trong MySQL CLI, EzyPlatform cung c\u1ea5p giao di\u1ec7n qu\u1ea3n l\u00fd **DataIndex** tr\u1ef1c quan ngay t\u1ea1i `https:\/\/1576647xxx-admin.interdata.io.vn\/database\/indexes`:\n\n![Giao di\u1ec7n Qu\u1ea3n L\u00fd Ch\u1ec9 M\u1ee5c DataIndex trong EzyPlatform Admin](https:\/\/images.viblo.asia\/389d6d34-bfc2-4775-9730-73cab8317939.png)\n\nCh\u00fang ta l\u1ea7n l\u01b0\u1ee3t b\u1ed5 sung c\u00e1c Composite Index quan tr\u1ecdng:\n1. **B\u1ea3ng \u0111\u01a1n h\u00e0ng (`ezy_article_orders`):** \u0110\u00e1nh index t\u1ed5 h\u1ee3p `(user_id, id)` theo chi\u1ec1u gi\u1ea3m d\u1ea7n \u0111\u1ec3 t\u1ed1i \u01b0u h\u00f3a m\u00e0n h\u00ecnh xem l\u1ecbch s\u1eed mua h\u00e0ng.\n2. **B\u1ea3ng s\u1ea3n ph\u1ea9m (`ezy_article_products`):** \u0110\u00e1nh index `(category_id, status)` \u0111\u1ec3 t\u0103ng t\u1ed1c \u0111\u1ed9 l\u1ecdc danh m\u1ee5c b\u00e1nh ng\u1ecdt \u0111ang m\u1edf b\u00e1n.\n3. **B\u1ea3ng t\u00edch \u0111i\u1ec3m (`bakery_loyalty_account`):** \u0110\u00e1nh `UNIQUE BTREE` tr\u00ean c\u1ed9t `user_id`.\n\nNgay sau khi apply index, th\u1eddi gian th\u1ef1c thi c\u00e2u query l\u1eadp t\u1ee9c t\u1ee5t t\u1eeb **1,240ms xu\u1ed1ng c\u00f2n 3.8ms**!\n\n---\n\n### B\u01b0\u1edbc 3: C\u1ea5u h\u00ecnh Cache Nginx & Gzip n\u00e9n d\u1eef li\u1ec7u\n\nM\u1edf file c\u1ea5u h\u00ecnh Nginx c\u1ee7a domain b\u00e1n b\u00e1nh:\n```nginx\n# B\u1eadt gzip n\u00e9n HTML, CSS, JS, JSON\ngzip on;\ngzip_vary on;\ngzip_min_length 1024;\ngzip_proxied any;\ngzip_types text\/plain text\/css text\/xml application\/json application\/javascript application\/xml+rss image\/svg+xml;\n\n# T\u1ed1i \u01b0u cache cho static asset\nlocation ~* \\.(jpg|jpeg|png|gif|ico|webp|svg|css|js|woff2)$ {\n    expires 365d;\n    add_header Cache-Control \"public, no-transform, max-age=31536000\";\n    access_log off;\n}\n```\nSau \u0111\u00f3 reload Nginx:\n```bash\nsudo nginx -t && sudo systemctl reload nginx\n```\n\n---\n\n### B\u01b0\u1edbc 4: Tinh ch\u1ec9nh b\u1ed9 nh\u1edb JVM (Memory Tuning) cho VPS RAM 2GB\n\n\u0110\u00e2y l\u00e0 b\u01b0\u1edbc s\u1ed1ng c\u00f2n nh\u1ea5t \u0111\u1ed1i v\u1edbi c\u00e1c \u1ee9ng d\u1ee5ng ch\u1ea1y tr\u00ean n\u1ec1n t\u1ea3ng Java\/EzyPlatform.\n\nM\u1eb7c \u0111\u1ecbnh, n\u1ebfu b\u1ea1n kh\u00f4ng gi\u1edbi h\u1ea1n b\u1ed9 nh\u1edb, JVM c\u00f3 th\u1ec3 ph\u00ecnh to chi\u1ebfm h\u1ebft RAM v\u1eadt l\u00fd. Khi RAM h\u1ec7 th\u1ed1ng c\u1ea1n ki\u1ec7t, Linux Kernel s\u1ebd k\u00edch ho\u1ea1t c\u01a1 ch\u1ebf **OOM Killer (Out Of Memory Killer)** v\u00e0 ti\u1ebfn tr\u00ecnh Java c\u1ee7a b\u1ea1n s\u1ebd b\u1ecb \"ch\u00e9m ch\u1ebft\" t\u1ee9c t\u01b0\u1edfi m\u00e0 kh\u00f4ng k\u1ecbp \u0111\u1ec3 l\u1ea1i b\u1ea5t k\u1ef3 d\u00f2ng log n\u00e0o!\n\n\u0110\u1ec3 kh\u1eafc ph\u1ee5c, ch\u00fang ta t\u1ea1o file service Systemd chu\u1ea9n t\u1ea1i `\/etc\/systemd\/system\/ezyplatform.service`:\n\n![File C\u1ea5u H\u00ecnh D\u1ecbch V\u1ee5 Systemd JVM Memory Tuning T\u1ed1i \u01afu](https:\/\/images.viblo.asia\/f03579e9-da2a-4449-8728-b9af6bf225f4.png)\n\n```ini\n[Unit]\nDescription=EzyPlatform Standalone Application Engine\nAfter=network.target mysql.service\n\n[Service]\nType=simple\nUser=ezy\nWorkingDirectory=\/home\/ezy\/ezyplatform\nEnvironment=\"JAVA_HOME=\/usr\/lib\/jvm\/java-17-openjdk-amd64\"\nExecStart=\/usr\/bin\/java \\\n  -server \\\n  -Xms1024m -Xmx1536m \\\n  -XX:+UseG1GC \\\n  -XX:MaxGCPauseMillis=100 \\\n  -XX:+ExitOnOutOfMemoryError \\\n  -jar ezyplatform.jar\nRestart=on-failure\nRestartSec=10\nLimitNOFILE=65535\n\n[Install]\nWantedBy=multi-user.target\n```\n\n> [!TIP]\n> **Gi\u1ea3i m\u00e3 c\u00f4ng th\u1ee9c v\u00e0ng:**  \n> VPS c\u00f3 2,048 MB RAM. Ch\u00fang ta c\u1ea5p cho JVM t\u1ed1i \u0111a **1,536 MB (`-Xmx1536m`)**, gi\u1eef l\u1ea1i **512 MB an to\u00e0n** cho MySQL v\u00e0 c\u00e1c ti\u1ebfn tr\u00ecnh n\u1ec1n c\u1ee7a h\u1ec7 \u0111i\u1ec1u h\u00e0nh.  \n> C\u1edd `-XX:+UseG1GC` (Garbage-First Garbage Collector) chia nh\u1ecf Heap th\u00e0nh nhi\u1ec1u v\u00f9ng v\u00e0 thu gom r\u00e1c si\u00eau nhanh v\u1edbi \u0111\u1ed9 tr\u1ec5 t\u1ed1i \u0111a ch\u1ec9 100ms (`MaxGCPauseMillis=100`), gi\u00fap web ph\u1ea3n h\u1ed3i m\u01b0\u1ee3t m\u00e0 kh\u00f4ng bao gi\u1edd b\u1ecb \"kh\u1ef1ng\".\n\n---\n\n## IV. K\u1ebft Qu\u1ea3 \u0110o L\u01b0\u1eddng Th\u1ef1c T\u1ebf: wrk Load Test Tr\u01b0\u1edbc & Sau T\u1ed1i \u01afu \ud83d\udccb\n\n\u0110\u1ec3 ch\u1ee9ng minh s\u1ee9c m\u1ea1nh c\u1ee7a vi\u1ec7c t\u1ed1i \u01b0u h\u00f3a, m\u00ecnh s\u1eed d\u1ee5ng c\u00f4ng c\u1ee5 benchmark chuy\u00ean nghi\u1ec7p `wrk`, gi\u1ea3 l\u1eadp **100 ng\u01b0\u1eddi d\u00f9ng \u0111\u1ed3ng th\u1eddi b\u1eafn li\u00ean t\u1ee5c request trong 30 gi\u00e2y**:\n\n```bash\nwrk -t4 -c100 -d30s https:\/\/tiembanh.interdata.io.vn\/api\/v1\/products\n```\n\nK\u1ebft qu\u1ea3 so s\u00e1nh tr\u1ef1c quan \u0111\u1ebfn kinh ng\u1ea1c:\n\n![K\u1ebft qu\u1ea3 Load Test v\u1edbi wrk So S\u00e1nh Tr\u01b0\u1edbc v\u00e0 Sau Khi T\u1ed1i \u01afu](https:\/\/images.viblo.asia\/c9597c1b-fc9a-42cd-a54b-81b2b7c3d879.png)\n\n| Ch\u1ec9 s\u1ed1 hi\u1ec7u n\u0103ng | Tr\u01b0\u1edbc khi t\u1ed1i \u01b0u | Sau khi t\u1ed1i \u01b0u | M\u1ee9c \u0111\u1ed9 c\u1ea3i thi\u1ec7n |\n| :--- | :---: | :---: | :---: |\n| **Requests \/ gi\u00e2y (RPS)** | 82.14 req\/s | **715.60 req\/s** | \ud83d\ude80 **T\u0103ng g\u1ea5p 8.7 l\u1ea7n** |\n| **\u0110\u1ed9 tr\u1ec5 trung b\u00ecnh (Latency Avg)** | 1,215.42 ms | **14.18 ms** | \u26a1 **Nhanh g\u1ea5p 85 l\u1ea7n** |\n| **\u0110\u1ed9 tr\u1ec5 cao nh\u1ea5t (Latency p99)** | 2,480.00 ms | **42.20 ms** | \ud83c\udfaf **\u1ed4n \u0111\u1ecbnh tuy\u1ec7t \u0111\u1ed1i** |\n| **L\u1ed7i Socket Timeout** | 89 l\u1ed7i | **0 l\u1ed7i (100% OK)** | \ud83d\udee1\ufe0f **Tri\u1ec7t ti\u00eau l\u1ed7i s\u1eadp m\u1ea1ng** |\n| **T\u1ea3i CPU m\u00e1y ch\u1ee7** | 98.4% (\u0110\u1ecf r\u1ef1c) | **18.2% (\u00cam \u00e1i)** | \u2744\ufe0f **Ti\u1ebft ki\u1ec7m 80% t\u00e0i nguy\u00ean** |\n\nCh\u1ec9 v\u1edbi m\u1ed9t v\u00e0i tinh ch\u1ec9nh chu\u1ea9n x\u00e1c, m\u00e1y ch\u1ee7 VPS gi\u00e1 r\u1ebb \u0111\u00e3 c\u00f3 th\u1ec3 g\u00e1nh m\u01b0\u1ee3t m\u00e0 h\u01a1n **42,000 l\u01b0\u1ee3t xem s\u1ea3n ph\u1ea9m m\u1ed7i ph\u00fat**!\n\n---\n\n## V. 3 B\u1eaby L\u1ed7i Kinh \u0110i\u1ec3n Khi T\u1ed1i \u01afu Hi\u1ec7u N\u0103ng & C\u00e1ch Tr\u00e1nh \ud83d\ude21\n\nTrong qu\u00e1 tr\u00ecnh t\u1ed1i \u01b0u, c\u00f3 nh\u1eefng \"con dao hai l\u01b0\u1ee1i\" m\u00e0 n\u1ebfu kh\u00f4ng c\u1ea9n th\u1eadn, b\u1ea1n s\u1ebd bi\u1ebfn l\u1ee3n l\u00e0nh th\u00e0nh l\u1ee3n qu\u00e8:\n\n### 1. B\u1eaby #1: \u0110\u00e1nh Index v\u00f4 t\u1ed9i v\u1ea1 v\u00e0o m\u1ecdi c\u1ed9t trong b\u1ea3ng \ud83d\ude23\n- **Sai l\u1ea7m:** Th\u1ea5y b\u1ea3ng c\u00f3 10 c\u1ed9t th\u00ec t\u1ea1o lu\u00f4n 10 c\u00e1i index \u0111\u01a1n l\u1ebb v\u1edbi hy v\u1ecdng query c\u1ed9t n\u00e0o c\u0169ng nhanh.\n- **H\u1eadu qu\u1ea3:** M\u1ed7i khi c\u00f3 thao t\u00e1c `INSERT` ho\u1eb7c `UPDATE` \u0111\u01a1n h\u00e0ng, MySQL ph\u1ea3i c\u1eadp nh\u1eadt l\u1ea1i to\u00e0n b\u1ed9 10 c\u00e2y B-Tree tr\u00ean \u1ed5 c\u1ee9ng, khi\u1ebfn t\u1ed1c \u0111\u1ed9 ghi d\u1eef li\u1ec7u t\u1ee5t d\u1ed1c th\u00ea th\u1ea3m.\n- **Nguy\u00ean t\u1eafc:** Ch\u1ec9 \u0111\u00e1nh index tr\u00ean c\u00e1c c\u1ed9t th\u01b0\u1eddng xuy\u00ean xu\u1ea5t hi\u1ec7n trong m\u1ec7nh \u0111\u1ec1 `WHERE`, `JOIN` v\u00e0 `ORDER BY`. \u01afu ti\u00ean **Composite Index** thay v\u00ec t\u1ea1o nhi\u1ec1u index \u0111\u01a1n.\n\n### 2. B\u1eaby #2: \u0110\u1eb7t JVM Max Heap qu\u00e1 s\u00e1t t\u1ed5ng RAM v\u1eadt l\u00fd \u26a0\ufe0f\n- **Sai l\u1ea7m:** VPS c\u00f3 2GB RAM th\u00ec \u0111\u1eb7t lu\u00f4n `-Xmx2000m`.\n- **H\u1eadu qu\u1ea3:** JVM ch\u1ec9 t\u00ednh b\u1ed9 nh\u1edb Heap, ngo\u00e0i ra c\u00f2n t\u1ed1n b\u1ed9 nh\u1edb Metaspace, Thread Stack v\u00e0 Buffer. C\u1ed9ng th\u00eam MySQL v\u00e0 Nginx ch\u1ea1y ng\u1ea7m, t\u1ed5ng RAM v\u01b0\u1ee3t qu\u00e1 2GB v\u00e0 Linux OOM Killer l\u1eadp t\u1ee9c d\u1eebng h\u1ec7 th\u1ed1ng.\n- **Nguy\u00ean t\u1eafc:** Lu\u00f4n \u0111\u1ec3 d\u00e0nh \u00edt nh\u1ea5t **25% - 30% RAM** cho h\u1ec7 \u0111i\u1ec1u h\u00e0nh v\u00e0 MySQL.\n\n### 3. B\u1eaby #3: Cache nh\u1ea7m c\u1ea3 API ch\u1ee9a th\u00f4ng tin ri\u00eang t\u01b0 c\u1ee7a kh\u00e1ch h\u00e0ng \ud83d\udca1\n- **Sai l\u1ea7m:** C\u1ea5u h\u00ecnh Nginx cache m\u1ecdi request b\u1eaft \u0111\u1ea7u b\u1eb1ng `\/api\/`.\n- **H\u1eadu qu\u1ea3:** Kh\u00e1ch h\u00e0ng A \u0111\u0103ng nh\u1eadp v\u00e0o gi\u1ecf h\u00e0ng, Nginx l\u01b0u cache trang \u0111\u00f3. Kh\u00e1ch h\u00e0ng B v\u00e0o sau th\u00ec... nh\u00ecn th\u1ea5y lu\u00f4n t\u00ean v\u00e0 gi\u1ecf b\u00e1nh c\u1ee7a kh\u00e1ch h\u00e0ng A! \ud83d\ude31\n- **Nguy\u00ean t\u1eafc:** Ch\u1ec9 cache c\u00e1c endpoint c\u00f4ng khai (Catalog, Public Articles). Tuy\u1ec7t \u0111\u1ed1i b\u1ecf qua cache (`proxy_cache_bypass`) \u0111\u1ed1i v\u1edbi c\u00e1c request c\u00f3 ch\u1ee9a header `Authorization` ho\u1eb7c cookie phi\u00ean \u0111\u0103ng nh\u1eadp.\n\n---\n\n## VI. T\u1ed5ng K\u1ebft & L\u1ed9 Tr\u00ecnh B\u00e0i Ti\u1ebfp Theo\n\nT\u1ed1i \u01b0u h\u00f3a kh\u00f4ng ph\u1ea3i l\u00e0 m\u1ed9t c\u00f4ng vi\u1ec7c ph\u1ee9c t\u1ea1p \u0111\u00f2i h\u1ecfi m\u00e1y ch\u1ee7 tri\u1ec7u \u0111\u00f4. V\u1edbi t\u01b0 duy ph\u00e2n t\u00edch \u0111\u00fang \u0111\u1eafn:\n1. T\u00ecm ra n\u00fat th\u1eaft c\u1ed5 chai qua Slow Query Log.\n2. B\u1ebb g\u00e3y \u0111i\u1ec3m ngh\u1ebdn b\u1eb1ng Composite Index (DataIndex).\n3. Ch\u1eb7n t\u1ea3i t\u1eeb xa b\u1eb1ng Cache \u0111a t\u1ea7ng.\n4. \u0110\u00f3ng khung t\u00e0i nguy\u00ean an to\u00e0n cho JVM.\n\nB\u1ea1n \u0111\u00e3 ho\u00e0n to\u00e0n l\u00e0m ch\u1ee7 h\u1ec7 th\u1ed1ng c\u1ee7a m\u00ecnh, s\u1eb5n s\u00e0ng \u0111\u00f3n ti\u1ebfp h\u00e0ng v\u1ea1n l\u01b0\u1ee3t truy c\u1eadp m\u00e0 kh\u00f4ng lo s\u1eadp ngu\u1ed3n!\n\n---\n\n### \u23ed\ufe0f B\u00e0i #15: Tr\u1ee3 L\u00fd \u1ea2o B\u00e1n H\u00e0ng 24\/7: T\u00edch H\u1ee3p AI Chatbot T\u1ef1 \u0110\u1ed9ng T\u01b0 V\u1ea5n & T\u1ea1o \u0110\u01a1n V\u1edbi EzyAI!\nWebsite ch\u1ea1y si\u00eau t\u1ed1c r\u1ed3i, nh\u01b0ng kh\u00e1ch h\u00e0ng v\u00e0o l\u00fac n\u1eeda \u0111\u00eam c\u1ea7n t\u01b0 v\u1ea5n: *\"B\u00e1nh kem n\u00e0y c\u00f3 v\u1ecb socola kh\u00f4ng?\", \"Cho t\u00f4i \u0111\u1eb7t b\u00e1nh giao v\u00e0o 8h t\u1ed1i nay\"* th\u00ec ai s\u1ebd tr\u1ea3 l\u1eddi?\n\n\u1ede b\u00e0i vi\u1ebft ti\u1ebfp theo, ch\u00fang ta s\u1ebd b\u01b0\u1edbc l\u00ean m\u1ed9t t\u1ea7m cao m\u1edbi c\u1ee7a Vibe Coding:\n- T\u00edch h\u1ee3p **AI Chatbot 24\/7** th\u00f4ng minh v\u1edbi b\u1ed9 plugin `EzyAI` & `EzyChat`.\n- N\u1ea1p \"n\u00e3o b\u1ed9\" tri th\u1ee9c (Knowledge Base) v\u1ec1 to\u00e0n b\u1ed9 th\u1ef1c \u0111\u01a1n b\u00e1nh ng\u1ecdt cho chatbot.\n- C\u1ea5p quy\u1ec1n cho Chatbot t\u1ef1 \u0111\u1ed9ng tra c\u1ee9u t\u1ed3n kho v\u00e0 t\u1ea1o link \u0111\u01a1n h\u00e0ng thanh to\u00e1n VietQR ngay trong khung chat!\n\nC\u1ea3m \u01a1n anh em \u0111\u00e3 \u0111\u1ed3ng h\u00e0nh. \u0110\u1eebng qu\u00ean b\u1ea5m **Upvote** \ud83d\udc4d v\u00e0 **Bookmark** \ud83d\udd16 b\u00e0i vi\u1ebft \u0111\u1ec3 l\u01b0u l\u1ea1i c\u1ea9m nang t\u1ed1i \u01b0u VPS n\u00e0y nh\u00e9!","published_at":"2026-11-14T10:11:32.000000Z","scheduled_publish_at":null,"is_published":true,"is_shared":false,"updated_at":"2026-11-14T15:46:02.000000Z","edited_at":"2026-10-25T17:32:58.000000Z","translation_source":null,"trend_at":null,"promoted_at":null,"reading_time":13,"points":0,"views_count":10,"clips_count":0,"comments_count":0,"rated_value":null,"promoted":false,"trending":false,"is_draft":false,"is_public":true,"locale_code":"vi","is_video":false,"thumbnail_url":"https:\/\/images.viblo.asia\/bf8c6fb8-7f54-4b0b-99be-e9452b9f5087.png","user":{"data":{"id":212424,"url":"https:\/\/viblo.asia\/u\/arya.it","avatar":"5f571f56-1f15-4d86-abea-d37a3ae5bcd5.png","name":"Duong Nguyen","username":"arya.it","followers_count":0,"reputation":24,"posts_count":13,"banned_at":null,"level_partner":null,"following":false}},"tags":{"data":[{"slug":"database","name":"Database"},{"slug":"java","name":"Java"},{"slug":"mysql","name":"MySql"},{"slug":"performance","name":"Performance"}]},"commentators":{"data":[]}},{"id":110639,"title":"A PM Portfolio Reviewers Actually Trust: The Evidence-First Checklist","slug":"XP4WE98kL7G","url":"https:\/\/viblo.asia\/p\/a-pm-portfolio-reviewers-actually-trust-the-evidence-first-checklist-XP4WE98kL7G","user_id":231934,"moderation":"pending","transliterated":"a-pm-portfolio-reviewers-actually-trust-the-evidence-first-checklist","contents_short":"Most early-career PM portfolios fail for a boring reason: they describe work instead of showing decisions. A reviewer skimming your case study wants to see what you knew, what you chose and why, and how you would know whether it worked. Adjectives like \"impactful\" and \"user-centric\" do not survive that skim. Evidence does.\n\nBelow is a practical checklist built around the dimensions reviewers ac...","contents":"Most early-career PM portfolios fail for a boring reason: they describe work instead of showing decisions. A reviewer skimming your case study wants to see what you knew, what you chose and why, and how you would know whether it worked. Adjectives like \"impactful\" and \"user-centric\" do not survive that skim. Evidence does.\n\nBelow is a practical checklist built around the dimensions reviewers actually probe.\n\n## 1. Lead with the decision, not the feature list\nOpen with the decision you made and the constraint behind it. \"We had two weeks and one engineer, so we cut onboarding to a single screen\" is reviewable. \"I owned onboarding\" is not. State the scope, the trade-off, and what you deliberately did not do.\n\n## 2. Show an evidence trail\nFor every claim, ask: what would a skeptical reviewer need to see? Link to the artifact \u2014 a one-page brief, a metric definition, a SQL query, a decision log. If the evidence is a conversation, write the takeaway and the date. A portfolio is not a highlight reel; it is an audit trail you can defend.\n\n## 3. Document AI decisions explicitly\nIf your project used an LLM or a model, show the decision, not just the output. What did you ask the model to do? What did you keep human? What failure mode worried you? Reviewers are wary of \"we used AI\" as a finish line. They trust candidates who can explain model boundaries, fallback behavior, and why a simpler rule would not have worked.\n\n## 4. Include an evaluation plan\nA feature without an evaluation plan is a guess. Write down the metric, the baseline, the check window, and the decision rule. \"If activation does not move within two weeks, we revert\" is stronger than \"we monitored results.\" For AI features, add a small evaluation set: inputs, expected behavior, and a rubric for judging outputs. A CSV template with a rubric definition is enough to show you think in testable terms.\n\n## 5. Use metrics that match the stage\nDo not bolt revenue onto a concept project. Match the metric to the maturity: task completion and time-to-value for a prototype; adoption and retention for a live feature. Define each metric in one line so a non-specialist can follow. If you cannot measure it, say what proxy you would use and why.\n\n## 6. Protect project integrity\nLabel simulated work as simulated. Do not imply users, revenue, launches, or employer experience you did not have. This is not a small formatting point; it is the difference between a portfolio that earns trust and one that collapses under one follow-up question. A clearly labeled simulated brief with a PRD, metrics, SQL, and an evaluation plan is more convincing than a vague \"real\" project.\n\n## 7. Make it reviewable in 90 seconds\nStructure the case study so a reviewer can find the decision, the evidence, and the result without scrolling through context. Use headings, short paragraphs, and a one-line summary at the top. Put the weakest section last. Presentation is not decoration; it is respect for the reviewer's time.\n\n## A quick way to pressure-test your draft\nThe [OfferKu](https:\/\/chanpinjingli.org\/en) free AI PM portfolio check applies a 12-point rubric across evidence, AI decisions, evaluation, metrics, project integrity, and presentation. It returns dimension scores and up to three priority revisions in about five minutes. No name and no material upload are required, and individual answers are not sent to analytics. If you want line-level edits, the async human review is a clearly scoped paid one-off with prioritized edits and one rescore of the same material.\n\nRun the check, fix the top three issues, and rescore. The loop matters more than the first score.","published_at":"2026-11-14T09:06:31.000000Z","scheduled_publish_at":null,"is_published":true,"is_shared":false,"updated_at":"2026-11-14T09:07:02.000000Z","edited_at":"2026-11-14T09:05:26.000000Z","translation_source":null,"trend_at":null,"promoted_at":null,"reading_time":2,"points":0,"views_count":1,"clips_count":0,"comments_count":0,"rated_value":null,"promoted":false,"trending":false,"is_draft":false,"is_public":true,"locale_code":"vi","is_video":false,"thumbnail_url":"https:\/\/images.viblo.asia\/fc962197-0d4c-4491-a1a0-0700e4e7b531.png","user":{"data":{"id":231934,"url":"https:\/\/viblo.asia\/u\/jay_offerku","avatar":null,"name":"Jay","username":"jay_offerku","followers_count":0,"reputation":0,"posts_count":1,"banned_at":null,"level_partner":null,"following":false}},"tags":{"data":[{"slug":"product-management","name":"product management"},{"slug":"portfolio","name":"Portfolio"}]},"commentators":{"data":[]}},{"id":110638,"title":"Ghi nh\u1eadt k\u00fd \u0111\u1ed9 \u1ed3n c\u1ee7a train horn b\u1eb1ng ESP32 (I2S) v\u00e0 MQTT tr\u00ean Linux","slug":"ym400bEd491","url":"https:\/\/viblo.asia\/p\/ghi-nhat-ky-do-on-cua-train-horn-bang-esp32-i2s-va-mqtt-tren-linux-ym400bEd491","user_id":231932,"moderation":null,"transliterated":"ghi-nhat-ky-do-on-cua-train-horn-bang-esp32-i2s-va-mqtt-tren-linux","contents_short":"M\u00ecnh c\u1ea7n log l\u1ea1i nh\u1eefng c\u00fa h\u00fa ng\u1eafn c\u1ee7a m\u1ed9t train horn pneumatik \u2014 kh\u00f4ng ph\u1ea3i \u0111\u1ec3 \u201c\u0111o cho vui m\u1ed9t l\u1ea7n\u201d, m\u00e0 \u0111\u1ec3 so s\u00e1nh repeatability: c\u00f9ng van, c\u00f9ng \u00e1p su\u1ea5t, c\u00f9ng kho\u1ea3ng c\u00e1ch, l\u1ea7n n\u00e0y RMS l\u1ec7ch bao nhi\u00eau so v\u1edbi l\u1ea7n tr\u01b0\u1edbc. \u0110o b\u1eb1ng \u0111i\u1ec7n tho\u1ea1i r\u1ed3i k\u00e9o file v\u00e0o Audacity th\u00ec l\u00e0m \u0111\u01b0\u1ee3c, nh\u01b0ng v\u1edbi d\u00e3y th\u1eed v\u00e0i ch\u1ee5c l\u1ea7n th\u00ec m\u00ecnh mu\u1ed1n m\u1ed9t logger nh\u1ecf, t\u1ef1 \u0111\u1ea9y s\u1ed1 v\u1ec1 m\u00e1y Linux.\n\nB\u00e0i n\u00e0y ch\u1ec9 n\u00f3i ph\u1ea7n thu\u2013ghi\u2013l\u01b0u. K...","contents":"M\u00ecnh c\u1ea7n log l\u1ea1i nh\u1eefng c\u00fa h\u00fa ng\u1eafn c\u1ee7a m\u1ed9t *train horn* pneumatik \u2014 kh\u00f4ng ph\u1ea3i \u0111\u1ec3 \u201c\u0111o cho vui m\u1ed9t l\u1ea7n\u201d, m\u00e0 \u0111\u1ec3 so s\u00e1nh repeatability: c\u00f9ng van, c\u00f9ng \u00e1p su\u1ea5t, c\u00f9ng kho\u1ea3ng c\u00e1ch, l\u1ea7n n\u00e0y RMS l\u1ec7ch bao nhi\u00eau so v\u1edbi l\u1ea7n tr\u01b0\u1edbc. \u0110o b\u1eb1ng \u0111i\u1ec7n tho\u1ea1i r\u1ed3i k\u00e9o file v\u00e0o Audacity th\u00ec l\u00e0m \u0111\u01b0\u1ee3c, nh\u01b0ng v\u1edbi d\u00e3y th\u1eed v\u00e0i ch\u1ee5c l\u1ea7n th\u00ec m\u00ecnh mu\u1ed1n m\u1ed9t logger nh\u1ecf, t\u1ef1 \u0111\u1ea9y s\u1ed1 v\u1ec1 m\u00e1y Linux.\n\nB\u00e0i n\u00e0y ch\u1ec9 n\u00f3i ph\u1ea7n thu\u2013ghi\u2013l\u01b0u. Kh\u00f4ng \u0111\u1ee5ng GPIO \u0111i\u1ec1u khi\u1ec3n van (c\u00e1i \u0111\u00f3 l\u00e0 m\u1ea1ch l\u1ef1c, diode ng\u01b0\u1ee3c, gi\u1edbi h\u1ea1n \u0111\u1ed9 r\u1ed9ng xung \u2014 chuy\u1ec7n kh\u00e1c).\n\n## Ph\u1ea7n c\u1ee9ng: ESP32 + micro I2S\n\nM\u00ecnh d\u00f9ng ESP32-WROOM v\u00e0 micro MEMS I2S INMP441 (l\u1ecbch WS\/SCK\/SD r\u00f5, kh\u00f4ng ph\u1ea3i ADC 12-bit c\u1ee7a ESP, v\u1ed1n \u1ed3n v\u00e0 kh\u00f4ng tuy\u1ebfn t\u00ednh). N\u1ed1i \u0111i\u1ec3n h\u00ecnh:\n\n* WS (LRCL) \u2192 GPIO25\n* SCK (BCLK) \u2192 GPIO26\n* SD (DOUT) \u2192 GPIO27\n* L\/R \u2192 GND (k\u00eanh tr\u00e1i), VDD 3,3 V, GND chung\n\nMicro n\u00e0y ra PCM 24-bit trong khung 32-bit; firmware ch\u1ec9 l\u1ea5y 24 bit cao, b\u1ecf padding. T\u1ea7n s\u1ed1 l\u1ea5y m\u1eabu 48 kHz l\u00e0 \u0111\u1ee7 cho spektrum c\u1ee7a m\u1ed9t *train horn* (n\u0103ng l\u01b0\u1ee3ng h\u1eefu \u00edch n\u1eb1m d\u01b0\u1edbi v\u00e0i kHz; 48 kHz cho kho\u1ea3ng tr\u1ed1ng khi l\u00e0m decimation ho\u1eb7c c\u1eeda s\u1ed5 FFT sau n\u00e0y).\n\nM\u1ed9t \u0111o\u1ea1n Arduino\/ESP-IDF t\u1ed1i gi\u1ea3n \u2014 I2S master, 32-bit slot, t\u00ednh RMS tr\u00ean khung 100 ms r\u1ed3i \u0111\u1ed5i sang dBFS:\n\n```cpp\n\/\/ khung 100 ms @ 48 kHz, mono\nconst int N = 4800;\nint32_t buf[N];\nsize_t n_bytes = 0;\ni2s_read(I2S_NUM_0, buf, sizeof(buf), &n_bytes, portMAX_DELAY);\nint samples = n_bytes \/ 4;\n\ndouble acc = 0;\nint32_t peak = 0;\nfor (int i = 0; i < samples; ++i) {\n    int32_t s = buf[i] >> 8;          \/\/ 24-bit c\u00f3 d\u1ea5u\n    acc += (double)s * (double)s;\n    if (abs(s) > peak) peak = abs(s);\n}\ndouble rms = sqrt(acc \/ samples);\n\/\/ full-scale 24-bit \u2248 8388607\nfloat dbfs = 20.0f * log10f((rms + 1.0) \/ 8388607.0f);\nfloat dbfs_peak = 20.0f * log10f((peak + 1.0) \/ 8388607.0f);\n```\n\ndBFS **kh\u00f4ng** ph\u1ea3i dB SPL. Mu\u1ed1n x\u1ea5p x\u1ec9 m\u1ee9c \u00e2m thanh ph\u1ea3i c\u00f3 offset hi\u1ec7u chu\u1ea9n (m\u1ee5c d\u01b0\u1edbi). M\u00ecnh **kh\u00f4ng** g\u1eafn A-weighting tr\u00ean ESP32: filter t\u1ed1n CPU, v\u00e0 v\u1edbi t\u00edn hi\u1ec7u ng\u1eafn, ph\u1ed5 h\u1eb9p nh\u01b0 h\u00fa h\u01a1i, A-weighting \u00edt thay \u0111\u1ed5i k\u1ebft lu\u1eadn \u201cl\u1ea7n n\u00e0y c\u00f3 m\u1ea1nh h\u01a1n l\u1ea7n tr\u01b0\u1edbc kh\u00f4ng\u201d. N\u1ebfu sau n\u00e0y c\u1ea7n dBA, m\u00ecnh t\u00ednh offline tr\u00ean m\u00e1y Linux t\u1eeb PCM th\u00f4 (xu\u1ea5t th\u00eam WAV khi v\u01b0\u1ee3t ng\u01b0\u1ee1ng).\n\n## \u0110\u1ea9y s\u1ed1 v\u1ec1 Linux b\u1eb1ng MQTT\n\nESP32 n\u1ed1i Wi-Fi, client `PubSubClient` ho\u1eb7c `esp-mqtt`, topic v\u00ed d\u1ee5 `lab\/horn\/rms`. Payload JSON, QoS 1, retain **t\u1eaft** (m\u00ecnh kh\u00f4ng mu\u1ed1n broker gi\u1eef m\u00e3i m\u1ed9t m\u1ee9c dB c\u0169):\n\n```json\n{\"ts\": 1731571200, \"dbfs\": -18.4, \"peak\": -6.1, \"over\": false}\n```\n\n`over` b\u1eadt khi `dbfs` v\u01b0\u1ee3t ng\u01b0\u1ee1ng (v\u00ed d\u1ee5 \u221220 dBFS sau khi \u0111\u00e3 canh offset). Tr\u00ean Debian\/Ubuntu m\u00ecnh ch\u1ec9 c\u1ea7n Mosquitto:\n\n```bash\nsudo apt install mosquitto mosquitto-clients\nsudo systemctl enable --now mosquitto\nmosquitto_sub -t 'lab\/horn\/#' -v\n```\n\nM\u1ed9t subscriber Python nh\u1ecf ghi CSV, \u0111\u1ed3ng th\u1eddi t\u1ea1o file \u201cs\u1ef1 ki\u1ec7n\u201d khi `over` l\u00e0 true (\u0111\u1ec3 sau n\u00e0y c\u1eaft \u0111o\u1ea1n WAV t\u01b0\u01a1ng \u1ee9ng n\u1ebfu m\u00ecnh c\u00f3 stream PCM ri\u00eang, ho\u1eb7c ch\u1ec9 \u0111\u1ec3 \u0111\u00e1nh d\u1ea5u m\u1ed1c th\u1eed):\n\n```python\nimport csv, json, time\nimport paho.mqtt.client as mqtt\n\nout = open(\"horn_log.csv\", \"a\", newline=\"\")\nw = csv.writer(out)\n\ndef on_message(c, u, msg):\n    d = json.loads(msg.payload)\n    row = [d.get(\"ts\") or time.time(), d[\"dbfs\"], d[\"peak\"], d[\"over\"]]\n    w.writerow(row)\n    out.flush()\n    if d[\"over\"]:\n        print(\"burst\", row)\n\nmqtt.Client().connect(\"127.0.0.1\", 1883, 60)\n# ... subscribe lab\/horn\/rms, loop_forever()\n```\n\nV\u1edbi chu\u1ed7i d\u00e0i h\u01a1n, c\u00f3 th\u1ec3 nh\u00e9t Telegraf MQTT consumer v\u00e0o InfluxDB\/Grafana \u2014 kh\u00f4ng b\u1eaft bu\u1ed9c cho m\u1ed9t b\u00e0n th\u1eed v\u00e0i bu\u1ed5i.\n\nL\u01b0u \u00fd th\u1ef1c t\u1ebf: Wi-Fi tr\u00ean ESP32 c\u00f3 jitter v\u00e0i ch\u1ee5c ms, n\u00ean `ts` m\u00ecnh l\u1ea5y t\u1eeb epoch c\u1ee7a ESP (NTP qua `configTime`) ch\u1ee9 kh\u00f4ng tin d\u1ea5u th\u1eddi gian l\u00fac message t\u1edbi broker. M\u1ea5t g\u00f3i QoS 1 v\u1eabn c\u00f2n, nh\u01b0ng n\u1ebfu log \u201cc\u00f3\/kh\u00f4ng c\u00f3 c\u00fa h\u00fa\u201d th\u00ec \u0111\u1eebng d\u1ef1a v\u00e0o MQTT l\u00e0m c\u1ea3m bi\u1ebfn an to\u00e0n \u2014 \u0111\u00e2y ch\u1ec9 l\u00e0 nh\u1eadt k\u00fd.\n\n## Hi\u1ec7u chu\u1ea9n cho \u0111\u1ee1 t\u1ef1 l\u1eeba m\u00ecnh\n\nM\u1ed9t con s\u1ed1 dBFS m\u1ed9t m\u00ecnh kh\u00f4ng n\u00f3i \u0111\u01b0\u1ee3c \u201cbao nhi\u00eau \u0111ecibel trong ph\u00f2ng\u201d. C\u00e1ch m\u00ecnh l\u00e0m, \u0111\u1ee7 d\u00f9ng cho so s\u00e1nh t\u01b0\u01a1ng \u0111\u1ed1i:\n\n1. C\u1ed1 \u0111\u1ecbnh kho\u1ea3ng c\u00e1ch micro\u2013mi\u1ec7ng loa (m\u00ecnh k\u1eb9p 1 m, kh\u00f4ng c\u1ea7m tay).\n2. Ph\u00e1t m\u1ed9t ngu\u1ed3n tham chi\u1ebfu \u1ed5n \u0111\u1ecbnh (loa \u0111o, ho\u1eb7c calibrator 94 dB \/ 1 kHz n\u1ebfu c\u00f3). Ghi `dbfs` trung b\u00ecnh v\u00e0i gi\u00e2y, suy ra `offset_spl = 94 - dbfs_ref` r\u1ed3i c\u1ed9ng v\u00e0o m\u1ecdi m\u1eabu sau.\n3. N\u1ebfu kh\u00f4ng c\u00f3 calibrator, \u00edt nh\u1ea5t gi\u1eef nguy\u00ean gain, kh\u00f4ng \u0111\u1ee5ng preamp, kh\u00f4ng \u0111\u1ed5i micro gi\u1eefa c\u00e1c l\u1ea7n \u2014 l\u00fac \u0111\u00f3 ch\u1ec9 so \u0111\u01b0\u1ee3c **delta** dB, kh\u00f4ng so \u0111\u01b0\u1ee3c v\u1edbi b\u1ea3ng th\u00f4ng s\u1ed1 nh\u00e0 s\u1ea3n xu\u1ea5t.\n\nC\u1eeda s\u1ed5 100 ms \u1ed5n cho c\u00fa h\u00fa 200\u2013400 ms: \u0111\u1ee7 v\u00e0i khung n\u1eb1m tr\u1ecdn trong xung, kh\u00f4ng b\u1ecb m\u1ed9t khung \u201cnu\u1ed1t\u201d c\u1ea3 kho\u1ea3ng l\u1eb7ng tr\u01b0\u1edbc\/sau. C\u1eeda s\u1ed5 1 s s\u1ebd l\u00e0m RMS tr\u00f4ng y\u1ebfu h\u01a1n th\u1ef1c t\u1ebf v\u00ec pha tr\u1ed9n v\u1edbi im l\u1eb7ng.\n\n## Ch\u1ed7 c\u1ee7a thi\u1ebft b\u1ecb c\u1ea7m tay trong setup n\u00e0y\n\nLogger ESP32 h\u1ee3p khi *train horn* n\u1eb1m c\u1ed1 \u0111\u1ecbnh tr\u00ean b\u00e0n (b\u00ecnh h\u01a1i + van). \u0110\u1ec3 th\u1eed ngo\u00e0i hi\u1ec7n tr\u01b0\u1eddng, kh\u00f4ng k\u00e9o d\u00e2y MQTT, nhi\u1ec1u ng\u01b0\u1eddi d\u00f9ng lu\u00f4n lo\u1ea1i c\u1ea7m tay: [Horn Guns](https:\/\/hornguns.com\/) b\u00e1n portable train horn guns, t\u1ee9c train horns ch\u1ea1y pin (battery powered) v\u00e0 kh\u00f4ng d\u00e2y (cordless). M\u00ecnh v\u1eabn \u0111\u1ec3 logger I2S trong lab v\u00ec c\u1ea7n chu\u1ed7i s\u1ed1 l\u1eb7p l\u1ea1i \u0111\u01b0\u1ee3c; c\u00f2n ngo\u00e0i b\u00e3i th\u00ec m\u1ed9t mi\u1ec7ng h\u00fa c\u1ea7m tay ti\u1ec7n h\u01a1n l\u00e0 mang theo ESP v\u00e0 broker. Hai vi\u1ec7c kh\u00f4ng thay th\u1ebf nhau.\n\n## Vi\u1ec7c m\u00ecnh c\u00f2n ch\u01b0a ch\u1eafc\n\n\u0110\u00e3 c\u00f3 ai \u1edf \u0111\u00e2y thu INMP441 \u1edf 48 kHz r\u1ed3i downsample offline, th\u1ea5y ph\u1ed5 m\u00e9o \u1edf ph\u1ea7n th\u1ea5p (d\u01b0\u1edbi 100 Hz) v\u00ec high-pass n\u1ed9i c\u1ee7a MEMS kh\u00f4ng? V\u00e0 offset hi\u1ec7u chu\u1ea9n c\u1ee7a c\u00e1c b\u1ea1n gi\u1eef \u1ed5n trong v\u00e0i ng\u00e0y, hay ph\u1ea3i canh l\u1ea1i m\u1ed7i l\u1ea7n c\u1eafm USB UART (ngu\u1ed3n 5 V \u0111\u1ed5i ripple)? N\u1ebfu c\u00f3 firmware ch\u1ec9 xu\u1ea5t MQTT khi v\u01b0\u1ee3t ng\u01b0\u1ee1ng (\u0111\u1ec3 ti\u1ebft pin), m\u00ecnh c\u0169ng mu\u1ed1n tham kh\u1ea3o c\u00e1ch c\u00e1c b\u1ea1n debounce ph\u00eda ph\u1ea7n m\u1ec1m cho s\u1ef1 ki\u1ec7n \u201cb\u1eaft \u0111\u1ea7u h\u00fa \/ h\u1ebft h\u00fa\u201d, kh\u00f4ng ph\u1ea3i cho ti\u1ebfp \u0111i\u1ec3m c\u01a1.","published_at":"2026-11-14T08:43:09.000000Z","scheduled_publish_at":null,"is_published":true,"is_shared":false,"updated_at":"2026-11-14T18:58:02.000000Z","edited_at":"2026-11-14T08:44:48.000000Z","translation_source":null,"trend_at":null,"promoted_at":null,"reading_time":5,"points":0,"views_count":11,"clips_count":0,"comments_count":0,"rated_value":null,"promoted":false,"trending":false,"is_draft":false,"is_public":true,"locale_code":"vi","is_video":false,"thumbnail_url":"https:\/\/images.viblo.asia\/cbf655b0-8e63-487f-982a-8653ca49f21e.png","user":{"data":{"id":231932,"url":"https:\/\/viblo.asia\/u\/horngunnotes","avatar":null,"name":"Horn Notes","username":"horngunnotes","followers_count":0,"reputation":0,"posts_count":1,"banned_at":null,"level_partner":null,"following":false}},"tags":{"data":[{"slug":"esp32","name":"esp32"},{"slug":"iot","name":"IoT"},{"slug":"linux","name":"Linux"},{"slug":"mqtt","name":"mqtt"},{"slug":"i2s","name":"i2s"}]},"commentators":{"data":[]}},{"id":110637,"title":"Preserving Room Structure While Exploring New Styles: Practical Image-to-Image Pipelines for Pre-Renovation Previews","slug":"AY4qQqa54Pw","url":"https:\/\/viblo.asia\/p\/preserving-room-structure-while-exploring-new-styles-practical-image-to-image-pipelines-for-pre-renovation-previews-AY4qQqa54Pw","user_id":231579,"moderation":"pending","transliterated":"preserving-room-structure-while-exploring-new-styles-practical-image-to-image-pipelines-for-pre-renovation-previews","contents_short":"Preserving Room Structure While Exploring New Styles: Practical Image-to-Image Pipelines for Pre-Renovation Previews\n\nVisualizing a kitchen remodel before committing thousands of dollars to demolition is a persistent friction point for homeowners and contractors. Traditional 3D architectural rendering requires exact laser measurements, hours of manual 3D modeling, and custom texture mapping. Co...","contents":"# Preserving Room Structure While Exploring New Styles: Practical Image-to-Image Pipelines for Pre-Renovation Previews\n\nVisualizing a kitchen remodel before committing thousands of dollars to demolition is a persistent friction point for homeowners and contractors. Traditional 3D architectural rendering requires exact laser measurements, hours of manual 3D modeling, and custom texture mapping. Conversely, unconstrained text-to-image diffusion models hallucinate new walls, remove load-bearing columns, and shift window placements, making them useless for practical remodeling discussions.\n\nTo solve this problem in production web applications, developers combine image-conditioned diffusion models with structured control layers to alter finishes while locking spatial boundaries.\n\nHere is a breakdown of how the image-to-image processing pipeline works, how it preserves room geometry, and the architectural trade-offs involved.\n\n---\n\n## 1. Extracting Geometric Constraints: Depth and Edge Conditioning\n\nWhen a user uploads a photo of an existing kitchen, the first step is separating structural layout from surface textures. If you run standard Stable Diffusion img2img with high denoising strength, the model alters cabinetry dimensions and wall locations. If you run it with low denoising strength, colors shift slightly, but cabinet doors and countertop materials remain largely unchanged.\n\nTo break this trade-off, the pipeline extracts structural guidance maps:\n\n1. **Relative Depth Estimation:** A model like MiDaS or ZoeDepth computes a normalized depth map from the RGB image. This retains foreground elements (kitchen islands, barstools) versus midground countertops and background wall cabinetry.\n2. **Structural Lineart Detection:** A Canny or lineart preprocessor identifies cabinet frame edges, crown molding, and window panes.\n\nBy feeding these depth and edge tensors into ControlNet or an image-conditioned adapter with conditioning weights between 0.65 and 0.85, the diffusion model is strongly constrained to keep the physical walls, ceiling junctions, and cabinet footprints consistent during generation.\n\n---\n\n## 2. Style-Specific Prompting and Latent Guidance\n\nOnce geometric boundaries are locked, the generation stage applies specific design styles. Instead of asking the user to write complex AI prompts, the application maps selectable design styles to curated latent guidance prompts and negative tokens:\n\n- **Scandinavian:** Light white oak cabinets, clean quartz surfaces, neutral matte fixtures, warm diffused morning lighting.\n- **Modern Minimalist:** Flat-panel slab cabinets, integrated handle channels, continuous waterfall edge quartz countertops, recessed task lighting.\n- **Japandi:** Fluted wood details, subtle earthy plaster wall textures, organic stone accents, muted beige tones.\n- **Farmhouse:** Shaker-style cabinet faces, apron-front ceramic sinks, brushed brass hardware, open shelving.\n\nNegative prompts penalize distorted plumbing, warped perspective lines, floating lighting fixtures, and blurry textures.\n\n---\n\n## 3. Real-World Application: The Pre-Renovation Decision Pack\n\nIn our web implementation at [AI Kitchen Preview](https:\/\/aikitchenpreview.com), the goal is not to produce fabrication-ready CAD drawings, but to solve the communication gap between homeowners and remodelers.\n\nHomeowners upload an existing photo, run multiple style variations against the identical perspective, and assemble a visual decision pack. This pack lets families and designers discuss concrete aesthetic choices\u2014such as light oak vs. matte dark charcoal cabinets\u2014before spending money on samples or hiring contractors.\n\n---\n\n## 4. Technical Trade-offs and Production Limitations\n\nBuilding image-to-image remodeling workflows surfaces several practical limitations:\n\n- **No Fabrication Measurements:** 2D diffusion does not understand standard millimeter tolerances for appliances or cabinet clearance. Generated previews show visual style, not building plans.\n- **Input Lighting Bias:** Mobile phone photos taken with harsh yellow incandescent bulbs can skew diffusion color balance. Applying automatic white-balance normalization prior to feature extraction stabilizes output consistency.\n- **Hardware Latency:** Running multi-pass diffusion with ControlNet conditioning takes 8 to 15 seconds per preview on a dedicated Nvidia A10G or L4 GPU. Implementing a queue system with progressive WebSocket updates prevents user drop-off.\n\n---\n\n## Conclusion\n\nCombining depth conditioning, structural line detection, and curated style prompts turns generative diffusion into a practical decision tool for pre-renovation planning. By anchoring spatial geometry, users can test remodeling concepts on their actual room photo before signing contracts or buying materials.","published_at":"2026-11-14T08:18:17.000000Z","scheduled_publish_at":null,"is_published":true,"is_shared":false,"updated_at":"2026-11-14T08:19:04.000000Z","edited_at":"2026-11-14T08:07:18.000000Z","translation_source":null,"trend_at":null,"promoted_at":null,"reading_time":3,"points":0,"views_count":1,"clips_count":0,"comments_count":0,"rated_value":null,"promoted":false,"trending":false,"is_draft":false,"is_public":true,"locale_code":"vi","is_video":false,"thumbnail_url":"https:\/\/images.viblo.asia\/4876dc6f-2f4d-4612-b20e-29d86952449f.png","user":{"data":{"id":231579,"url":"https:\/\/viblo.asia\/u\/bobo20260130","avatar":null,"name":"\u5434\u6ce2","username":"bobo20260130","followers_count":0,"reputation":0,"posts_count":1,"banned_at":null,"level_partner":null,"following":false}},"tags":{"data":[{"slug":"ai","name":"AI"},{"slug":"machine-learning","name":"Machine Learning"}]},"commentators":{"data":[]}},{"id":110523,"title":"Command Box b\u00e0i h\u1ecdc 13: Ki\u1ec3m tra r\u00e0ng bu\u1ed9c \u0111\u1ea7u v\u00e0o (Validation) ngay t\u1eeb t\u1ea7ng CLI tr\u01b0\u1edbc khi th\u1ef1c thi","slug":"2vJPdvpoJeK","url":"https:\/\/viblo.asia\/p\/command-box-bai-hoc-13-kiem-tra-rang-buoc-dau-vao-validation-ngay-tu-tang-cli-truoc-khi-thuc-thi-2vJPdvpoJeK","user_id":184792,"moderation":null,"transliterated":"command-box-bai-hoc-13-kiem-tra-rang-buoc-dau-vao-validation-ngay-tu-tang-cli-truoc-khi-thuc-thi","contents_short":"\u1ede B\u00e0i h\u1ecdc 12, ch\u00fang ta \u0111\u00e3 bi\u1ebft c\u00e1ch d\u00f9ng pflag v\u00e0 Cobra \u0111\u1ec3 b\u1eaft c\u00e1c l\u1ed7i c\u00fa ph\u00e1p c\u01a1 b\u1ea3n nh\u01b0 truy\u1ec1n thi\u1ebfu c\u1edd, sai ki\u1ec3u s\u1ed1 nguy\u00ean (int) hay xung \u0111\u1ed9t gi\u1eefa hai c\u1edd (MarkFlagsMutuallyExclusive). Nh\u01b0ng trong th\u1ef1c t\u1ebf v\u1eadn h\u00e0nh Backend,  \"\u0111\u00fang ki\u1ec3u d\u1eef li\u1ec7u\" ch\u01b0a bao gi\u1edd \u0111\u1ed3ng ngh\u0129a v\u1edbi \"h\u1ee3p l\u1ec7 \u0111\u1ec3 ch\u1ea1y\".\n\nH\u00e3y t\u01b0\u1edfng t\u01b0\u1ee3ng b\u1ea1n ch\u1ea1y l\u1ec7nh cbox db restore --file=.\/backup2026.sql --workers=500 --env=prod. V\u1ec1 m\u1eb7t c...","contents":"\u1ede B\u00e0i h\u1ecdc 12, ch\u00fang ta \u0111\u00e3 bi\u1ebft c\u00e1ch d\u00f9ng `pflag` v\u00e0 Cobra \u0111\u1ec3 b\u1eaft c\u00e1c l\u1ed7i c\u00fa ph\u00e1p c\u01a1 b\u1ea3n nh\u01b0 truy\u1ec1n thi\u1ebfu c\u1edd, sai ki\u1ec3u s\u1ed1 nguy\u00ean (`int`) hay xung \u0111\u1ed9t gi\u1eefa hai c\u1edd (`MarkFlagsMutuallyExclusive`). Nh\u01b0ng trong th\u1ef1c t\u1ebf v\u1eadn h\u00e0nh Backend,  **\"\u0111\u00fang ki\u1ec3u d\u1eef li\u1ec7u\" ch\u01b0a bao gi\u1edd \u0111\u1ed3ng ngh\u0129a v\u1edbi \"h\u1ee3p l\u1ec7 \u0111\u1ec3 ch\u1ea1y\"**.\n\nH\u00e3y t\u01b0\u1edfng t\u01b0\u1ee3ng b\u1ea1n ch\u1ea1y l\u1ec7nh `cbox db restore --file=.\/backup_2026.sql --workers=500 --env=prod`. V\u1ec1 m\u1eb7t c\u00fa ph\u00e1p, m\u1ecdi th\u1ee9 \u0111\u1ec1u \u0111\u00fang:  `.\/backup_2026.sql` l\u00e0 m\u1ed9t chu\u1ed7i (`string`),  `500` l\u00e0 m\u1ed9t s\u1ed1 nguy\u00ean (`int`). Nh\u01b0ng khi ti\u1ebfn tr\u00ecnh ch\u1ea1y \u0111\u01b0\u1ee3c 3 b\u01b0\u1edbc: \u0111\u00e3 k\u1ebft n\u1ed1i v\u00e0o Database Production, \u0111\u00e3 x\u00f3a b\u1ea3ng c\u0169 (`DROP TABLE`), \u0111\u1ebfn b\u01b0\u1edbc \u0111\u1ecdc file dump th\u00ec ph\u00e1t hi\u1ec7n ra \u0111\u01b0\u1eddng d\u1eabn `.\/backup_2026.sql` b\u1ecb g\u00f5 sai ch\u00ednh t\u1ea3 (file kh\u00f4ng t\u1ed3n t\u1ea1i) ho\u1eb7c s\u1ed1 l\u01b0\u1ee3ng `--workers=500` v\u01b0\u1ee3t qu\u00e1 gi\u1edbi h\u1ea1n `max_connections` c\u1ee7a MySQL l\u00e0m s\u1eadp lu\u00f4n c\u1ea3 c\u1ee5m DB!\n\nB\u00e0i h\u1ecdc s\u1ed1 13 s\u1ebd h\u01b0\u1edbng d\u1eabn b\u1ea1n x\u00e2y d\u1ef1ng **H\u1ec7 th\u1ed1ng ph\u00f2ng th\u1ee7 Validation \u0111a t\u1ea7ng ngay t\u1ea1i c\u1eeda ng\u00f5 CLI (Fail-Fast Gatekeeper)** \u2014 \u0111\u1ea3m b\u1ea3o m\u1ecdi tham s\u1ed1 \u0111\u1ea7u v\u00e0o ph\u1ea3i v\u01b0\u1ee3t qua b\u00e0i ki\u1ec3m tra ng\u1eef ngh\u0129a v\u00e0 m\u00f4i tr\u01b0\u1eddng th\u1ef1c t\u1ebf tr\u01b0\u1edbc khi b\u1ea5t k\u1ef3 d\u00f2ng code nghi\u1ec7p v\u1ee5 (`internal\/core`) n\u00e0o \u0111\u01b0\u1ee3c ph\u00e9p k\u00edch ho\u1ea1t.\n\n## 1. Tri\u1ebft l\u00fd \"Fail-Fast\": Ch\u1eb7n r\u00e1c tr\u01b0\u1edbc khi m\u1edf k\u1ebft n\u1ed1i m\u1ea1ng\n\nTrong thi\u1ebft k\u1ebf h\u1ec7 th\u1ed1ng ph\u00e2n t\u00e1n v\u00e0 CLI,  **Fail-Fast (Th\u1ea5t b\u1ea1i ngay l\u1eadp t\u1ee9c)** l\u00e0 nguy\u00ean t\u1eafc ti\u1ebft ki\u1ec7m t\u00e0i nguy\u00ean v\u00e0 b\u1ea3o v\u1ec7 an to\u00e0n d\u1eef li\u1ec7u h\u00e0ng \u0111\u1ea7u.\n\nN\u1ebfu m\u1ed9t c\u00e2u l\u1ec7nh ch\u1eafc ch\u1eafn s\u1ebd th\u1ea5t b\u1ea1i v\u00ec \u0111\u1ea7u v\u00e0o v\u00f4 l\u00fd, n\u00f3 ph\u1ea3i b\u00e1o l\u1ed7i trong **v\u00e0i mili-gi\u00e2y \u0111\u1ea7u ti\u00ean t\u1ea1i t\u1ea7ng CLI (`PreRunE`)**, ch\u1ee9 kh\u00f4ng ph\u1ea3i sau khi \u0111\u00e3 t\u1ed1n 5 gi\u00e2y kh\u1edfi t\u1ea1o k\u1ebft n\u1ed1i SSH, m\u1edf Transaction Database hay \u0111\u1ea9y m\u1ed9t n\u1eeda l\u00f4 b\u1ea3n tin v\u00e0o Kafka.\n\nH\u00e3y ph\u00e2n t\u00e1ch r\u00f5 **3 l\u1edbp ph\u1ec5u l\u1ecdc Validation** trong m\u1ed9t Command Box chu\u1ea9n m\u1ef1c:\n\n| L\u1edbp ki\u1ec3m tra (Validation Layer) | Th\u1eddi \u0111i\u1ec3m k\u00edch ho\u1ea1t | N\u1ed9i dung ki\u1ec3m tra c\u1ee5 th\u1ec3 | V\u00ed d\u1ee5 l\u1ed7i b\u1ecb ch\u1eb7n l\u1ea1i |\n| :--- | :--- | :--- | :--- |\n| **L\u1edbp 1: C\u00fa ph\u00e1p & Ki\u1ec3u** (Syntactic) | L\u00fac Cobra parse d\u00f2ng l\u1ec7nh (`Flags.Parse`) | Ki\u1ec3u d\u1eef li\u1ec7u (`int`, `bool`, `duration`), c\u1edd b\u1eaft bu\u1ed9c, s\u1ed1 l\u01b0\u1ee3ng args. | Truy\u1ec1n `--workers=abc` ho\u1eb7c thi\u1ebfu c\u1edd `--id`. |\n| **L\u1edbp 2: Ng\u1eef ngh\u0129a & Mi\u1ec1n gi\u00e1 tr\u1ecb** (Semantic) | Trong hook `PreRunE` (Ch\u1ea1y tr\u00ean RAM, 0ms I\/O) | \u0110\u1ecbnh d\u1ea1ng Regex (M\u00e3 \u0111\u01a1n h\u00e0ng, IP, Email), gi\u1edbi h\u1ea1n Min\/Max, logic ch\u00e9o gi\u1eefa c\u00e1c bi\u1ebfn. | Truy\u1ec1n `--workers=-5` ho\u1eb7c `--batch-size=999999` (v\u01b0\u1ee3t ng\u01b0\u1ee1ng an to\u00e0n). |\n| **L\u1edbp 3: M\u00f4i tr\u01b0\u1eddng c\u1ee5c b\u1ed9** (Pre-flight \/ OS Check) | Cu\u1ed1i hook `PreRunE` (Tr\u01b0\u1edbc khi g\u1ecdi `RunE`) | S\u1ef1 t\u1ed3n t\u1ea1i c\u1ee7a file\/th\u01b0 m\u1ee5c, quy\u1ec1n \u0111\u1ecdc\/ghi (`chmod`), \u0111\u1ecbnh d\u1ea1ng c\u1ed5ng m\u1ea1ng, dung l\u01b0\u1ee3ng \u1ed5 c\u1ee9ng tr\u1ed1ng. | Truy\u1ec1n `--file=.\/dump.sql` nh\u01b0ng file kh\u00f4ng t\u1ed3n t\u1ea1i ho\u1eb7c c\u00f3 dung l\u01b0\u1ee3ng 0 byte. |\n\n## 2. 4 K\u1ecbch b\u1ea3n \"Validation s\u1ed1ng c\u00f2n\" m\u00e0 m\u1ecdi Command Box ph\u1ea3i c\u00f3\n\n\u0110\u1eebng \u0111\u1ee3i xu\u1ed1ng t\u1edbi t\u1ea7ng Service hay Database m\u1edbi b\u1eaft \u0111\u1ea7u ki\u1ec3m tra. Ngay t\u1ea1i t\u1ea7ng `internal\/cli\/`, b\u1ea1n c\u1ea7n ch\u1eb7n \u0111\u1ee9ng 4 nh\u00f3m \u0111\u1ea7u v\u00e0o nguy hi\u1ec3m sau:\n\n### \u2460 Ki\u1ec3m tra ranh gi\u1edbi s\u1ed1 h\u1ecdc (Boundary & Range Validation)\n\nKhi m\u1ed9t l\u1ec7nh c\u00f3 c\u1edd `--concurrency`,  `--workers`,  `--batch-size` ho\u1eb7c `--limit`, ki\u1ec3u `int` c\u1ee7a Go cho ph\u00e9p truy\u1ec1n t\u1eeb s\u1ed1 \u00e2m h\u00e0ng t\u1ef7 \u0111\u1ebfn d\u01b0\u01a1ng h\u00e0ng t\u1ef7.\n\n-   N\u1ebfu ng\u01b0\u1eddi d\u00f9ng truy\u1ec1n `--workers=0` ho\u1eb7c `-1`: C\u00e1c v\u00f2ng l\u1eb7p Worker Pool d\u00f9ng `make(chan Job, workers)` s\u1ebd b\u1ecb treo c\u1ee9ng (Deadlock) v\u0129nh vi\u1ec5n!\n    \n-   N\u1ebfu ng\u01b0\u1eddi d\u00f9ng truy\u1ec1n `--batch-size=100000`: C\u00e2u l\u1ec7nh `INSERT` ho\u1eb7c `SELECT ... WHERE id IN (...)` s\u1ebd v\u01b0\u1ee3t ng\u01b0\u1ee1ng `max_allowed_packet` c\u1ee7a MySQL ho\u1eb7c l\u00e0m tr\u00e0n b\u1ed9 nh\u1edb RAM (OOM).\n    \n-   **Quy chu\u1ea9n:** Lu\u00f4n kh\u00f3a ch\u1eb7t kho\u1ea3ng gi\u00e1 tr\u1ecb h\u1ee3p l\u1ec7 (v\u00ed d\u1ee5:  `1 <= workers <= 64`,  `10 <= batchSize <= 5000`).\n    \n\n### \u2461 Ki\u1ec3m tra File v\u00e0 \u0110\u01b0\u1eddng d\u1eabn h\u1ec7 th\u1ed1ng (Filesystem Sanity Check)\n\nKhi nh\u1eadn m\u1ed9t \u0111\u01b0\u1eddng d\u1eabn qua c\u1edd `--file` ho\u1eb7c `--output-dir`, \u0111\u1eebng bao gi\u1edd ch\u1ec9 ki\u1ec3m tra `file != \"\"`. H\u00e3y ki\u1ec3m tra theo \u0111\u00fang m\u1ee5c \u0111\u00edch s\u1eed d\u1ee5ng:\n\n-   **N\u1ebfu l\u00e0 File \u0111\u1ea7u v\u00e0o (Input File):** File c\u00f3 t\u1ed3n t\u1ea1i tr\u00ean \u1ed5 c\u1ee9ng kh\u00f4ng (`os.Stat`)? N\u00f3 c\u00f3 ph\u1ea3i l\u00e0 m\u1ed9t file th\u1eadt hay ng\u01b0\u1eddi d\u00f9ng truy\u1ec1n nh\u1ea7m t\u00ean m\u1ed9t th\u01b0 m\u1ee5c (`info.IsDir()`)? File c\u00f3 b\u1ecb r\u1ed7ng (`info.Size() == 0`) kh\u00f4ng?\n    \n-   **N\u1ebfu l\u00e0 File c\u1ea5u h\u00ecnh nh\u1ea1y c\u1ea3m (SSH Key, `.env`):** Quy\u1ec1n truy c\u1eadp file c\u00f3 qu\u00e1 l\u1ecfng l\u1ebbo kh\u00f4ng? (V\u00ed d\u1ee5: file private key b\u1eaft bu\u1ed9c ph\u1ea3i c\u00f3 quy\u1ec1n `0600`).\n    \n\n### \u2462 Ki\u1ec3m tra \u0110\u1ecbnh d\u1ea1ng Nghi\u1ec7p v\u1ee5 (Format & Regex Validation)\n\nTrong h\u1ec7 th\u1ed1ng n\u1ed9i b\u1ed9 c\u1ee7a c\u00f4ng ty b\u1ea1n, c\u00e1c th\u1ef1c th\u1ec3 lu\u00f4n c\u00f3 quy t\u1eafc \u0111\u1ecbnh danh ri\u00eang:\n\n-   M\u00e3 \u0111\u01a1n h\u00e0ng lu\u00f4n b\u1eaft \u0111\u1ea7u b\u1eb1ng `ORD-` k\u00e8m 8 ch\u1eef s\u1ed1 (`^ORD-[0-9]{8}$`).\n    \n-   T\u00ean Kafka Topic ch\u1ec9 g\u1ed3m ch\u1eef th\u01b0\u1eddng, d\u1ea5u ch\u1ea5m v\u00e0 g\u1ea1ch ngang.\n    \n-   \u0110\u1ecba ch\u1ec9 k\u1ebft n\u1ed1i ph\u1ea3i \u0111\u00fang chu\u1ea9n `host:port` (ki\u1ec3m tra b\u1eb1ng `net.SplitHostPort`). Ki\u1ec3m tra ngay b\u1eb1ng Regex ho\u1eb7c h\u00e0m chu\u1ea9n \u1edf CLI s\u1ebd ng\u0103n ch\u1eb7n vi\u1ec7c b\u1eafn c\u00e1c c\u00e2u query v\u00f4 ngh\u0129a xu\u1ed1ng Database.\n    \n\n### \u2463 Gom nh\u00f3m l\u1ed7i (Error Aggregation) thay v\u00ec b\u1eaft s\u1eeda t\u1eebng l\u1ed7i m\u1ed9t\n\nB\u1ea1n c\u00f3 th\u1ea5y b\u1ef1c m\u00ecnh khi \u0111i\u1ec1n m\u1ed9t c\u00e1i form c\u00f3 3 ch\u1ed7 sai, nh\u01b0ng b\u1ea5m ch\u1ea1y l\u1ea7n 1 n\u00f3 ch\u1ec9 b\u00e1o l\u1ed7i d\u00f2ng s\u1ed1 1; s\u1eeda xong b\u1ea5m ch\u1ea1y l\u1ea7n 2 n\u00f3 m\u1edbi b\u00e1o l\u1ed7i d\u00f2ng s\u1ed1 2 kh\u00f4ng? V\u1edbi Golang (t\u1eeb Go 1.20+), h\u00e3y s\u1eed d\u1ee5ng **`errors.Join(errs...)`** trong h\u00e0m `Validate()` \u0111\u1ec3 ki\u1ec3m tra **to\u00e0n b\u1ed9 c\u00e1c c\u1edd c\u00f9ng m\u1ed9t l\u00fac** v\u00e0 in ra m\u1ed9t danh s\u00e1ch \u0111\u1ea7y \u0111\u1ee7 t\u1ea5t c\u1ea3 c\u00e1c \u0111i\u1ec3m ch\u01b0a h\u1ee3p l\u1ec7 trong \u0111\u00fang 1 l\u1ea7n ch\u1ea1y!\n\n## 3. Thi\u1ebft k\u1ebf Pattern `Validator` chu\u1ea9n m\u1ef1c cho Struct Options\n\n\u1ede B\u00e0i h\u1ecdc 4 v\u00e0 12, ch\u00fang ta \u0111\u00e3 gom to\u00e0n b\u1ed9 Flags c\u1ee7a m\u1ed9t l\u1ec7nh v\u00e0o trong m\u1ed9t struct `xxxOptions`. \u0110\u1ec3 code lu\u00f4n s\u1ea1ch s\u1ebd, h\u00e3y g\u1eafn th\u00eam m\u1ed9t ph\u01b0\u01a1ng th\u1ee9c **`Validate() error`** tr\u1ef1c ti\u1ebfp v\u00e0o struct \u0111\u00f3 v\u00e0 g\u1ecdi n\u00f3 m\u1ed9t c\u00e1ch t\u1ef1 \u0111\u1ed9ng trong `PreRunE`.\n\nPlaintext\n\n```\n[Ng\u01b0\u1eddi d\u00f9ng g\u00f5 l\u1ec7nh] \n       \u2502\n       \u25bc\n[Cobra Parse Flags] \u2500\u2500(Sai ki\u1ec3u int\/bool)\u2500\u2500\u25ba \u274c B\u00e1o l\u1ed7i c\u00fa ph\u00e1p ngay\n       \u2502\n       \u25bc\n[Hook: PreRunE] \u2500\u2500\u2500\u2500\u2500\u2500\u25ba G\u1ecdi opts.Validate() \u2500\u2500(Sai mi\u1ec1n gi\u00e1 tr\u1ecb \/ File kh\u00f4ng t\u1ed3n t\u1ea1i)\u2500\u2500\u25ba \u274c B\u00e1o l\u1ed7i ng\u1eef ngh\u0129a\n       \u2502\n       \u25bc (\u0110\u1ea7u v\u00e0o s\u1ea1ch 100%)\n[Hook: RunE] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba Kh\u1edfi t\u1ea1o DB\/Kafka & Ch\u1ea1y t\u1ea7ng Core (internal\/core)\n\n```\n\n## 4. Th\u1ef1c h\u00e0nh vi\u1ebft code: X\u00e2y d\u1ef1ng b\u1ed9 `Validate()` to\u00e0n di\u1ec7n trong Golang\n\nH\u00e3y xem c\u00e1ch tri\u1ec3n khai m\u1ed9t l\u1ec7nh `cbox db import` (n\u1ea1p d\u1eef li\u1ec7u t\u1eeb file SQL v\u00e0o Database v\u1edbi c\u01a1 ch\u1ebf ch\u1ea1y song song) \u0111\u01b0\u1ee3c trang b\u1ecb b\u1ed9 ki\u1ec3m tra \u0111\u1ea7u v\u00e0o \u0111a t\u1ea7ng kh\u00f4ng m\u1ed9t k\u1ebd h\u1edf:\n\nGo\n\n```\npackage db\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path\/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com\/spf13\/cobra\"\n)\n\n\/\/ Bi\u1ec3u th\u1ee9c ch\u00ednh quy ki\u1ec3m tra t\u00ean Database h\u1ee3p l\u1ec7 (ch\u1ec9 ch\u1eef c\u00e1i, s\u1ed1 v\u00e0 g\u1ea1ch d\u01b0\u1edbi)\nvar validDBNameRegex = regexp.MustCompile(`^[a-zA-Z0-9_]{3,64}$`)\n\ntype importOptions struct {\n\thostAddr   string\n\tdbName     string\n\tsqlFile    string\n\tworkers    int\n\tchunkSize  int\n\tdropTarget bool\n\tenv        string\n}\n\n\/\/ Validate th\u1ef1c hi\u1ec7n ki\u1ec3m tra to\u00e0n di\u1ec7n ng\u1eef ngh\u0129a v\u00e0 m\u00f4i tr\u01b0\u1eddng tr\u01b0\u1edbc khi ch\u1ea1y\n\/\/ S\u1eed d\u1ee5ng errors.Join \u0111\u1ec3 tr\u1ea3 v\u1ec1 T\u1ea4T C\u1ea2 c\u00e1c l\u1ed7i c\u00f9ng l\u00fac thay v\u00ec d\u1eebng \u1edf l\u1ed7i \u0111\u1ea7u ti\u00ean\nfunc (o *importOptions) Validate() error {\n\tvar errs []error\n\n\t\/\/ 1. Ki\u1ec3m tra \u0111\u1ecbnh d\u1ea1ng m\u1ea1ng (Host:Port)\n\thost, port, err := net.SplitHostPort(o.hostAddr)\n\tif err != nil || host == \"\" || port == \"\" {\n\t\terrs = append(errs, fmt.Errorf(\"--host (%q) kh\u00f4ng h\u1ee3p l\u1ec7, ph\u1ea3i c\u00f3 \u0111\u1ecbnh d\u1ea1ng 'hostname:port' (v\u00ed d\u1ee5: 127.0.0.1:3306)\", o.hostAddr))\n\t}\n\n\t\/\/ 2. Ki\u1ec3m tra quy t\u1eafc \u0111\u1eb7t t\u00ean Database b\u1eb1ng Regex\n\tif !validDBNameRegex.MatchString(o.dbName) {\n\t\terrs = append(errs, fmt.Errorf(\"--db (%q) ch\u1ee9a k\u00fd t\u1ef1 kh\u00f4ng h\u1ee3p l\u1ec7 (ch\u1ec9 cho ph\u00e9p ch\u1eef, s\u1ed1, g\u1ea1ch d\u01b0\u1edbi, d\u00e0i 3-64 k\u00fd t\u1ef1)\", o.dbName))\n\t}\n\n\t\/\/ 3. Ki\u1ec3m tra ranh gi\u1edbi s\u1ed1 h\u1ecdc (Range & Boundary Check)\n\tif o.workers < 1 || o.workers > 32 {\n\t\terrs = append(errs, fmt.Errorf(\"--workers (%d) n\u1eb1m ngo\u00e0i ng\u01b0\u1ee1ng an to\u00e0n (ch\u1ec9 cho ph\u00e9p t\u1eeb 1 \u0111\u1ebfn 32 lu\u1ed3ng)\", o.workers))\n\t}\n\tif o.chunkSize < 100 || o.chunkSize > 10000 {\n\t\terrs = append(errs, fmt.Errorf(\"--chunk-size (%d) ph\u1ea3i n\u1eb1m trong kho\u1ea3ng 100 \u0111\u1ebfn 10.000 b\u1ea3n ghi\", o.chunkSize))\n\t}\n\n\t\/\/ 4. Ki\u1ec3m tra th\u1ef1c t\u1ebf File tr\u00ean h\u1ec7 \u0111i\u1ec1u h\u00e0nh (Filesystem Pre-flight Check)\n\tif err := validateSQLFile(o.sqlFile); err != nil {\n\t\terrs = append(errs, err)\n\t}\n\n\t\/\/ 5. Ki\u1ec3m tra R\u00e0ng bu\u1ed9c Logic Nghi\u1ec7p v\u1ee5 Ch\u00e9o (Cross-field Business Rule)\n\tif o.env == \"prod\" && o.dropTarget {\n\t\terrs = append(errs, errors.New(\"ch\u1ed1t ch\u1eb7n an to\u00e0n: C\u1ea4M s\u1eed d\u1ee5ng c\u1edd '--drop-target' khi \u0111ang tr\u1ecf v\u00e0o m\u00f4i tr\u01b0\u1eddng 'prod'\"))\n\t}\n\n\t\/\/ G\u1ed9p to\u00e0n b\u1ed9 l\u1ed7i l\u1ea1i (n\u1ebfu m\u1ea3ng errs r\u1ed7ng, errors.Join s\u1ebd tr\u1ea3 v\u1ec1 nil)\n\treturn errors.Join(errs...)\n}\n\n\/\/ H\u00e0m ph\u1ee5 tr\u1ee3 ki\u1ec3m tra s\u00e2u m\u1ed9t file \u0111\u1ea7u v\u00e0o tr\u00ean \u1ed5 c\u1ee9ng\nfunc validateSQLFile(path string) error {\n\tcleanPath := filepath.Clean(path)\n\tinfo, err := os.Stat(cleanPath)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn fmt.Errorf(\"--file (%q) kh\u00f4ng t\u1ed3n t\u1ea1i tr\u00ean h\u1ec7 th\u1ed1ng\", path)\n\t\t}\n\t\treturn fmt.Errorf(\"kh\u00f4ng th\u1ec3 truy c\u1eadp --file (%q): %w\", path, err)\n\t}\n\n\tif info.IsDir() {\n\t\treturn fmt.Errorf(\"--file (%q) l\u00e0 m\u1ed9t th\u01b0 m\u1ee5c, vui l\u00f2ng truy\u1ec1n \u0111\u01b0\u1eddng d\u1eabn t\u1edbi file .sql\", path)\n\t}\n\n\tif info.Size() == 0 {\n\t\treturn fmt.Errorf(\"--file (%q) b\u1ecb r\u1ed7ng (0 byte), kh\u00f4ng c\u00f3 d\u1eef li\u1ec7u \u0111\u1ec3 import\", path)\n\t}\n\n\text := strings.ToLower(filepath.Ext(cleanPath))\n\tif ext != \".sql\" && ext != \".gz\" {\n\t\treturn fmt.Errorf(\"--file (%q) c\u00f3 ph\u1ea7n m\u1edf r\u1ed9ng '%s' kh\u00f4ng \u0111\u01b0\u1ee3c h\u1ed7 tr\u1ee3 (ch\u1ec9 nh\u1eadn .sql ho\u1eb7c .gz)\", path, ext)\n\t}\n\n\treturn nil\n}\n\n```\n\n### K\u1ebft n\u1ed1i `Validate()` v\u00e0o v\u00f2ng \u0111\u1eddi `PreRunE` c\u1ee7a Cobra:\n\nGo\n\n```\nfunc NewImportCmd() *cobra.Command {\n\topts := &importOptions{}\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"import\",\n\t\tShort: \"N\u1ea1p d\u1eef li\u1ec7u t\u1ed1c \u0111\u1ed9 cao t\u1eeb file SQL v\u00e0o Database\",\n\t\t\n\t\t\/\/ C\u1eecA NG\u00d5 CH\u1eb6N L\u1ed6I: Ch\u1ea1y tr\u01b0\u1edbc khi \u0111\u1ee5ng v\u00e0o b\u1ea5t k\u1ef3 k\u1ebft n\u1ed1i m\u1ea1ng hay DB n\u00e0o\n\t\tPreRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif err := opts.Validate(); err != nil {\n\t\t\t\treturn fmt.Errorf(\"d\u1eef li\u1ec7u \u0111\u1ea7u v\u00e0o ch\u01b0a h\u1ee3p l\u1ec7:\\n  \u2022 %s\",\n\t\t\t\t\tstrings.ReplaceAll(err.Error(), \"\\n\", \"\\n  \u2022 \"))\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\t\/\/ Khi code ch\u1ea1y t\u1edbi \u0111\u01b0\u1ee3c \u0111\u00e2y, b\u1ea1n y\u00ean t\u00e2m 100% l\u00e0:\n\t\t\t\/\/ - File .sql c\u00f3 th\u1eadt tr\u00ean \u1ed5 c\u1ee9ng v\u00e0 > 0 byte\n\t\t\t\/\/ - S\u1ed1 l\u01b0\u1ee3ng workers n\u1eb1m trong ng\u01b0\u1ee1ng 1..32\n\t\t\t\/\/ - Kh\u00f4ng bao gi\u1edd c\u00f3 chuy\u1ec7n x\u00f3a nh\u1ea7m b\u1ea3ng tr\u00ean Production!\n\t\t\tfmt.Printf(\"\u2714 Ki\u1ec3m tra \u0111\u1ea7u v\u00e0o ho\u00e0n t\u1ea5t! \u0110ang m\u1edf %d workers \u0111\u1ec3 n\u1ea1p %s...\\n\", opts.workers, opts.sqlFile)\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tcmd.Flags().StringVar(&opts.hostAddr, \"host\", \"127.0.0.1:3306\", \"\u0110\u1ecba ch\u1ec9 Database (host:port)\")\n\tcmd.Flags().StringVarP(&opts.dbName, \"db\", \"d\", \"\", \"T\u00ean Database \u0111\u00edch (B\u1eaft bu\u1ed9c)\")\n\tcmd.Flags().StringVarP(&opts.sqlFile, \"file\", \"f\", \"\", \"\u0110\u01b0\u1eddng d\u1eabn t\u1edbi file .sql ho\u1eb7c .gz (B\u1eaft bu\u1ed9c)\")\n\tcmd.Flags().IntVarP(&opts.workers, \"workers\", \"w\", 4, \"S\u1ed1 lu\u1ed3ng ch\u1ea1y song song (1-32)\")\n\tcmd.Flags().IntVar(&opts.chunkSize, \"chunk-size\", 1000, \"S\u1ed1 d\u00f2ng m\u1ed7i l\u00f4 (100-10000)\")\n\tcmd.Flags().StringVarP(&opts.env, \"env\", \"e\", \"local\", \"M\u00f4i tr\u01b0\u1eddng \u0111\u00edch (local|staging|prod)\")\n\tcmd.Flags().BoolVar(&opts.dropTarget, \"drop-target\", false, \"X\u00f3a s\u1ea1ch b\u1ea3ng c\u0169 tr\u01b0\u1edbc khi n\u1ea1p m\u1edbi\")\n\n\t_ = cmd.MarkFlagRequired(\"db\")\n\t_ = cmd.MarkFlagRequired(\"file\")\n\n\treturn cmd\n}\n\n```\n\n### Tr\u1ea3i nghi\u1ec7m th\u1ef1c t\u1ebf khi ng\u01b0\u1eddi d\u00f9ng truy\u1ec1n sai nhi\u1ec1u tham s\u1ed1 c\u00f9ng l\u00fac:\n\nKhi m\u1ed9t \u0111\u1ed3ng nghi\u1ec7p g\u00f5 b\u1eeba l\u1ec7nh sau tr\u00ean terminal:\n\nBash\n\n```\ncbox db import --host=localhost --db=\"order-db!\" --file=.\/missing.txt --workers=100 --env=prod --drop-target\n\n```\n\nThay v\u00ec ch\u1ea1y v\u00e0o DB r\u1ed3i ch\u1ebft b\u1ea5t \u0111\u1eafc k\u1ef3 t\u1eed, ho\u1eb7c b\u1eaft h\u1ecd s\u1eeda l\u1eaft nh\u1eaft 5 l\u1ea7n, Command Box ch\u1eb7n ngay l\u1eadp t\u1ee9c trong `0.002 gi\u00e2y` v\u00e0 in ra b\u1ea3ng t\u1ed5ng h\u1ee3p l\u1ed7i c\u1ef1c k\u1ef3 r\u00f5 r\u00e0ng:\n\nPlaintext\n\n```\n\u2716 L\u1ed6I: d\u1eef li\u1ec7u \u0111\u1ea7u v\u00e0o ch\u01b0a h\u1ee3p l\u1ec7:\n  \u2022 --host (\"localhost\") kh\u00f4ng h\u1ee3p l\u1ec7, ph\u1ea3i c\u00f3 \u0111\u1ecbnh d\u1ea1ng 'hostname:port' (v\u00ed d\u1ee5: 127.0.0.1:3306)\n  \u2022 --db (\"order-db!\") ch\u1ee9a k\u00fd t\u1ef1 kh\u00f4ng h\u1ee3p l\u1ec7 (ch\u1ec9 cho ph\u00e9p ch\u1eef, s\u1ed1, g\u1ea1ch d\u01b0\u1edbi, d\u00e0i 3-64 k\u00fd t\u1ef1)\n  \u2022 --workers (100) n\u1eb1m ngo\u00e0i ng\u01b0\u1ee1ng an to\u00e0n (ch\u1ec9 cho ph\u00e9p t\u1eeb 1 \u0111\u1ebfn 32 lu\u1ed3ng)\n  \u2022 --file (\".\/missing.txt\") kh\u00f4ng t\u1ed3n t\u1ea1i tr\u00ean h\u1ec7 th\u1ed1ng\n  \u2022 ch\u1ed1t ch\u1eb7n an to\u00e0n: C\u1ea4M s\u1eed d\u1ee5ng c\u1edd '--drop-target' khi \u0111ang tr\u1ecf v\u00e0o m\u00f4i tr\u01b0\u1eddng 'prod'\n\n```\n\n## 5. X\u00e2y d\u1ef1ng th\u01b0 vi\u1ec7n `internal\/validator` d\u00f9ng chung cho to\u00e0n d\u1ef1 \u00e1n\n\nKhi d\u1ef1 \u00e1n Command Box c\u1ee7a b\u1ea1n l\u00ean t\u1edbi 30-50 l\u1ec7nh con, \u0111\u1eebng vi\u1ebft l\u1ea1i h\u00e0m `validateSQLFile` hay Regex ki\u1ec3m tra m\u00e3 \u0111\u01a1n h\u00e0ng \u1edf t\u1eebng l\u1ec7nh m\u1ed9t. H\u00e3y gom c\u00e1c h\u00e0m ki\u1ec3m tra ph\u1ed5 bi\u1ebfn nh\u1ea5t v\u00e0o package `internal\/validator\/validator.go`:\n\n-   `validator.FileExistsNonEmpty(path string, allowedExts ...string) error`\n    \n-   `validator.DirWritable(path string) error`  _(Th\u1eed t\u1ea1o 1 file nh\u00e1p `.tmp` r\u1ed3i x\u00f3a ngay \u0111\u1ec3 ch\u1eafc ch\u1eafn user c\u00f3 quy\u1ec1n ghi v\u00e0o th\u01b0 m\u1ee5c \u0111\u00edch tr\u01b0\u1edbc khi ch\u1ea1y job xu\u1ea5t b\u00e1o c\u00e1o d\u00e0i 10 ph\u00fat!)_\n    \n-   `validator.InRange(name string, val, min, max int) error`\n    \n-   `validator.OneOf(name string, val string, allowed ...string) error`\n    \n-   `validator.ValidOrderID(id string) error`\n    \n\n### \ud83d\udca1 B\u00e0i t\u1eadp th\u1ef1c h\u00e0nh cu\u1ed1i b\u00e0i (D\u00e0nh cho \u0111\u1ed9c gi\u1ea3):\n\n> _H\u00e3y m\u1edf m\u1ed9t l\u1ec7nh trong Command Box c\u1ee7a b\u1ea1n c\u00f3 thao t\u00e1c xu\u1ea5t d\u1eef li\u1ec7u ra file (v\u00ed d\u1ee5 `--output=.\/reports\/result.json`). H\u00e3y th\u1eed vi\u1ebft m\u1ed9t h\u00e0m ki\u1ec3m tra trong `PreRunE` \u0111\u1ea3m b\u1ea3o r\u1eb1ng: **Th\u01b0 m\u1ee5c `.\/reports\/` ph\u1ea3i t\u1ed3n t\u1ea1i v\u00e0 user hi\u1ec7n t\u1ea1i c\u00f3 quy\u1ec1n ghi (Write Permission) v\u00e0o \u0111\u00f3** tr\u01b0\u1edbc khi ch\u1ea1y c\u00e2u query Database n\u1eb7ng. B\u1ea1n s\u1ebd kh\u00f4ng bao gi\u1edd c\u00f2n g\u1eb7p c\u1ea3nh ng\u1ed3i \u0111\u1ee3i tool ch\u1ea1y query m\u1ea5t 5 ph\u00fat r\u1ed3i \u0111\u1ebfn b\u01b0\u1edbc cu\u1ed1i c\u00f9ng m\u1edbi v\u0103ng l\u1ed7i `permission denied` kh\u00f4ng l\u01b0u \u0111\u01b0\u1ee3c file n\u1eefa!_","published_at":"2026-11-14T07:30:57.000000Z","scheduled_publish_at":null,"is_published":true,"is_shared":false,"updated_at":"2026-11-14T23:28:01.000000Z","edited_at":"2026-11-12T10:14:08.000000Z","translation_source":null,"trend_at":null,"promoted_at":null,"reading_time":8,"points":0,"views_count":16,"clips_count":0,"comments_count":0,"rated_value":null,"promoted":false,"trending":false,"is_draft":false,"is_public":true,"locale_code":"vi","is_video":false,"thumbnail_url":"https:\/\/images.viblo.asia\/1601416a-2c14-4121-ad19-317aa9e10721.png","user":{"data":{"id":184792,"url":"https:\/\/viblo.asia\/u\/nhh","avatar":"81df63c2-e6d9-43a9-85a4-8e807f120997.jpg","name":"Nguy\u1ec5n Huy Ho\u00e0ng","username":"nhh","followers_count":16,"reputation":184,"posts_count":259,"banned_at":null,"level_partner":null,"following":false}},"tags":{"data":[{"slug":"command","name":"command"}]},"commentators":{"data":[]}},{"id":110620,"title":"[Series 100 B\u00e0i H\u1ecdc HTML\/CSS Th\u1ef1c Chi\u1ebfn - B\u00e0i 24]: X\u00e2y D\u1ef1ng Giao Di\u1ec7n Trang Thanh To\u00e1n (Checkout Page) & B\u00ed Thu\u1eadt V\u1ebd \u0110\u01b0\u1eddng Vi\u1ec1n Phong B\u00ec Th\u01b0 K\u1ebb S\u1ecdc \u0110\u1ecf - Xanh B\u1eb1ng repeating-linear-gradient","slug":"G24B8qjGLz3","url":"https:\/\/viblo.asia\/p\/series-100-bai-hoc-htmlcss-thuc-chien-bai-24-xay-dung-giao-dien-trang-thanh-toan-checkout-page-bi-thuat-ve-duong-vien-phong-bi-thu-ke-soc-do-xanh-bang-repeating-linear-gradient-G24B8qjGLz3","user_id":184801,"moderation":null,"transliterated":"series-100-bai-hoc-htmlcss-thuc-chien-bai-24-xay-dung-giao-dien-trang-thanh-toan-checkout-page-bi-thuat-ve-duong-vien-phong-bi-thu-ke-soc-do-xanh-bang-repeating-linear-gradient","contents_short":"Ch\u00e0o m\u1eebng anh em \u0111\u00e3 b\u01b0\u1edbc sang B\u00e0i 24 trong chu\u1ed7i 100 B\u00e0i H\u1ecdc L\u00e0m Ch\u1ee7 HTML\/CSS Qua D\u1ef1 \u00c1n Clone Shopee!\n\n\u1ede B\u00e0i 23, ch\u00fang ta \u0111\u00e3 ho\u00e0n th\u00e0nh Trang Gi\u1ecf H\u00e0ng (Shopping Cart) v\u1edbi thanh t\u1ed5ng ti\u1ec1n b\u00e1m d\u00ednh \u1edf \u0111\u00e1y m\u00e0n h\u00ecnh. Khi ng\u01b0\u1eddi mua ki\u1ec3m tra xong gi\u1ecf h\u00e0ng v\u00e0 b\u1ea5m v\u00e0o n\u00fat \"Mua H\u00e0ng\", h\u1ecd s\u1ebd b\u01b0\u1edbc sang m\u00e0n h\u00ecnh cu\u1ed1i c\u00f9ng \u0111\u1ec3 ho\u00e0n t\u1ea5t giao d\u1ecbch:  Trang Thanh To\u00e1n (Checkout \/ Order Confirmation Page)!\n\nN\u1ebfu b\u1ea1...","contents":"Ch\u00e0o m\u1eebng anh em \u0111\u00e3 b\u01b0\u1edbc sang **B\u00e0i 24** trong chu\u1ed7i **100 B\u00e0i H\u1ecdc L\u00e0m Ch\u1ee7 HTML\/CSS Qua D\u1ef1 \u00c1n Clone Shopee**!\n\n\u1ede **B\u00e0i 23**, ch\u00fang ta \u0111\u00e3 ho\u00e0n th\u00e0nh **Trang Gi\u1ecf H\u00e0ng (`Shopping Cart`)** v\u1edbi thanh t\u1ed5ng ti\u1ec1n b\u00e1m d\u00ednh \u1edf \u0111\u00e1y m\u00e0n h\u00ecnh. Khi ng\u01b0\u1eddi mua ki\u1ec3m tra xong gi\u1ecf h\u00e0ng v\u00e0 b\u1ea5m v\u00e0o n\u00fat **\"Mua H\u00e0ng\"**, h\u1ecd s\u1ebd b\u01b0\u1edbc sang m\u00e0n h\u00ecnh cu\u1ed1i c\u00f9ng \u0111\u1ec3 ho\u00e0n t\u1ea5t giao d\u1ecbch:  **Trang Thanh To\u00e1n (`Checkout \/ Order Confirmation Page`)**!\n\nN\u1ebfu b\u1ea1n t\u1eebng \u0111\u1eb7t h\u00e0ng tr\u00ean Shopee, ch\u1eafc ch\u1eafn b\u1ea1n s\u1ebd \u1ea5n t\u01b0\u1ee3ng ngay l\u1eadp t\u1ee9c v\u1edbi 3 chi ti\u1ebft thi\u1ebft k\u1ebf mang t\u00ednh bi\u1ec3u t\u01b0\u1ee3ng t\u1ea1i m\u00e0n h\u00ecnh Thanh to\u00e1n:\n\n1.  **\u0110\u01b0\u1eddng vi\u1ec1n \"Phong B\u00ec Th\u01b0\" k\u1ebb s\u1ecdc ch\u00e9o \u0110\u1ecf - Xanh (`Airmail Envelope Border`):** N\u1eb1m ngay tr\u00ean \u0111\u1ec9nh khung **\u0110\u1ecba Ch\u1ec9 Nh\u1eadn H\u00e0ng**, g\u1ee3i li\u00ean t\u01b0\u1edfng \u0111\u1ebfn m\u1ed9t b\u01b0u ph\u1ea9m qu\u1ed1c t\u1ebf s\u1eafp \u0111\u01b0\u1ee3c g\u1eedi \u0111i. R\u1ea5t nhi\u1ec1u l\u1eadp tr\u00ecnh vi\u00ean m\u1edbi t\u01b0\u1edfng \u0111\u00e2y l\u00e0 m\u1ed9t t\u1ea5m \u1ea3nh `.png` c\u1eaft s\u1eb5n, nh\u01b0ng th\u1ef1c ch\u1ea5t c\u00e1c k\u1ef9 s\u01b0 Shopee \u0111\u00e3 v\u1ebd n\u00f3 100% b\u1eb1ng m\u1ed9t h\u00e0m to\u00e1n h\u1ecdc trong CSS mang t\u00ean **`repeating-linear-gradient`**!\n    \n2.  **D\u00f2ng k\u1ebft h\u1ee3p \"L\u1eddi Nh\u1eafn Cho Shop + Ph\u01b0\u01a1ng Th\u1ee9c V\u1eadn Chuy\u1ec3n\":** Chia \u0111\u00f4i khung d\u01b0\u1edbi c\u1ee7a \u0111\u01a1n h\u00e0ng b\u1eb1ng h\u1ec7 th\u1ed1ng \u0111\u01b0\u1eddng k\u1ebb n\u00e9t \u0111\u1ee9t (`dashed border`) tinh t\u1ebf nh\u01b0 m\u1ed9t t\u1edd h\u00f3a \u0111\u01a1n b\u01b0u \u0111i\u1ec7n.\n    \n3.  **Kh\u1ed1i Ph\u01b0\u01a1ng Th\u1ee9c Thanh To\u00e1n & B\u1ea3ng T\u1ed5ng K\u1ebft H\u00f3a \u0110\u01a1n (`Payment & Order Summary`):** K\u1ebft h\u1ee3p ch\u1ecdn tab ph\u01b0\u01a1ng th\u1ee9c thanh to\u00e1n (_ShopeePay, Th\u1ebb T\u00edn d\u1ee5ng, Thanh to\u00e1n khi nh\u1eadn h\u00e0ng - COD_) c\u00f3 g\u00f3c t\u00edch tam gi\u00e1c 3D v\u00e0 b\u1ea3ng t\u00ednh nh\u1ea9m 3 d\u00f2ng (_T\u1ed5ng ti\u1ec1n h\u00e0ng + Ph\u00ed v\u1eadn chuy\u1ec3n - Voucher gi\u1ea3m gi\u00e1 = T\u1ed5ng thanh to\u00e1n_).\n    \n\nH\u00e3y c\u00f9ng gi\u1ea3i ph\u1eabu v\u00e0 vi\u1ebft code tr\u1ecdn b\u1ed9 m\u00e0n h\u00ecnh Thanh to\u00e1n n\u00e0y ngay trong B\u00e0i 24!\n\n## 1. Gi\u1ea3i m\u00e3 b\u00ed thu\u1eadt: V\u1ebd \u0111\u01b0\u1eddng vi\u1ec1n Phong b\u00ec th\u01b0 k\u1ebb s\u1ecdc ch\u00e9o \u0110\u1ecf - Xanh b\u1eb1ng `repeating-linear-gradient`\n\nH\u00e3y nh\u00ecn v\u00e0o h\u1ecda ti\u1ebft \u0111\u01b0\u1eddng vi\u1ec1n s\u1ecdc ch\u00e9o **\u0110\u1ecf - Tr\u1eafng - Xanh - Tr\u1eafng** kinh \u0111i\u1ec3n c\u1ee7a nh\u1eefng chi\u1ebfc phong b\u00ec th\u01b0 \u0111\u01b0\u1eddng h\u00e0ng kh\u00f4ng (Airmail Envelope) d\u01b0\u1edbi \u0111\u00e2y:\n\n![image.png](https:\/\/images.viblo.asia\/ce305642-ed0b-4de1-9211-9609955e9ac9.png)\n\nTr\u00ean Shopee, thanh vi\u1ec1n \u1edf \u0111\u1ec9nh h\u1ed9p **\u0110\u1ecba Ch\u1ec9 Nh\u1eadn H\u00e0ng** cao \u0111\u00fang `3px` (`0.3rem`) v\u00e0 l\u1eb7p \u0111i l\u1eb7p l\u1ea1i chu k\u1ef3 4 d\u1ea3i m\u00e0u nghi\u00eang g\u00f3c `45deg`:`[V\u1ea1ch \u0110\u1ecf 33px]`  $\\rightarrow$  `[Kho\u1ea3ng Tr\u1eafng 9px]`  $\\rightarrow$  `[V\u1ea1ch Xanh D\u01b0\u01a1ng 33px]`  $\\rightarrow$  `[Kho\u1ea3ng Tr\u1eafng 9px]`... l\u1eb7p l\u1ea1i v\u00f4 t\u1eadn \u0111\u1ebfn h\u1ebft chi\u1ec1u r\u1ed9ng `1200px`!\n\nN\u1ebfu d\u00f9ng `linear-gradient` th\u00f4ng th\u01b0\u1eddng, m\u00e0u s\u1eafc s\u1ebd b\u1ecb **nh\u00f2e chuy\u1ec3n s\u1eafc t\u1eeb t\u1eeb (m\u1edd d\u1ea7n)** t\u1eeb \u0110\u1ecf sang Tr\u1eafng. V\u1eady l\u00e0m sao \u0111\u1ec3 t\u1ea1o ra **ranh gi\u1edbi s\u1eafc c\u1ea1nh (Hard Color Stops)** gi\u1eefa c\u00e1c v\u1ea1ch m\u00e0u v\u00e0 b\u1eaft ch\u00fang t\u1ef1 \u0111\u1ed9ng l\u1eb7p l\u1ea1i?\n\nPlaintext\n\n```\n0px               33px     42px               75px     84px\n|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588|\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591|\u2592\u2592\u2592\u2592\u2592\u2592\u2592\u2592\u2592\u2592\u2592\u2592\u2592\u2592\u2592\u2592\u2592\u2592|\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591|  --> L\u1eb7p l\u1ea1i chu k\u1ef3 84px v\u00f4 t\u1eadn!\n   \u0110\u1ece (#f18d9b)      TR\u1eaeNG     XANH (#6fa6d6)    TR\u1eaeNG\n (T\u1eeb 0px -> 33px)  (33->42px) (T\u1eeb 42px -> 75px) (75->84px)\n\n```\n\n> **Nguy\u00ean l\u00fd \"Hard Stop\" trong `repeating-linear-gradient`:**\n> \n> Khi m\u00e0u sau b\u1eaft \u0111\u1ea7u t\u1ea1i **\u0111\u00fang v\u1ecb tr\u00ed pixel m\u00e0 m\u00e0u tr\u01b0\u1edbc v\u1eeba k\u1ebft th\u00fac** (v\u00ed d\u1ee5: M\u00e0u \u0110\u1ecf k\u1ebft th\u00fac \u1edf `33px`, v\u00e0 M\u00e0u Tr\u1eafng c\u0169ng b\u1eaft \u0111\u1ea7u ngay t\u1ea1i `33px`), tr\u00ecnh duy\u1ec7t s\u1ebd kh\u00f4ng l\u00e0m nh\u00f2e m\u00e0u n\u1eefa m\u00e0 c\u1eaft m\u1ed9t \u0111\u01b0\u1eddng th\u1eb3ng s\u1eafc l\u1eb9m!\n\n| Giai \u0111o\u1ea1n trong chu k\u1ef3 84px | Khai b\u00e1o t\u1ecda \u0111\u1ed9 trong `repeating-linear-gradient(45deg, ...)` | \u00dd ngh\u0129a hi\u1ec3n th\u1ecb |\n| :--- | :--- | :--- |\n| **\u0110o\u1ea1n 1** <br>*(0px $\\rightarrow$ 33px)* | `#f18d9b 0, #f18d9b 33px` | V\u1ebd m\u1ed9t v\u1ea1ch ch\u00e9o m\u00e0u \u0110\u1ecf H\u1ed3ng d\u00e0i \u0111\u00fang 33px. |\n| **\u0110o\u1ea1n 2** <br>*(33px $\\rightarrow$ 42px)* | `transparent 33px, transparent 42px` | Ch\u1eb7n \u0111\u1ee9ng m\u00e0u \u0111\u1ecf t\u1ea1i m\u1ed1c 33px v\u00e0 t\u1ea1o Khe h\u1edf trong su\u1ed1t r\u1ed9ng 9px ($42 - 33 = 9$). |\n| **\u0110o\u1ea1n 3** <br>*(42px $\\rightarrow$ 75px)* | `#6fa6d6 42px, #6fa6d6 75px` | V\u1ebd ti\u1ebfp m\u1ed9t v\u1ea1ch ch\u00e9o m\u00e0u Xanh D\u01b0\u01a1ng d\u00e0i \u0111\u00fang 33px ($75 - 42 = 33$). |\n| **\u0110o\u1ea1n 4** <br>*(75px $\\rightarrow$ 84px)* | `transparent 75px, transparent 84px` | T\u1ea1o Khe h\u1edf trong su\u1ed1t r\u1ed9ng 9px k\u1ebft th\u00fac chu k\u1ef3 84px, sau \u0111\u00f3 t\u1ef1 \u0111\u1ed9ng nh\u00e2n b\u1ea3n l\u1eb7p l\u1ea1i \u0111\u1ebfn h\u1ebft m\u00e0n h\u00ecnh! |\n\n## 2. T\u01b0 duy b\u1ed1 c\u1ee5c B\u1ea3ng T\u1ed5ng K\u1ebft H\u00f3a \u0110\u01a1n (`Order Summary Grid`)\n\n\u1ede cu\u1ed1i trang Thanh to\u00e1n (tr\u01b0\u1edbc n\u00fat **\"\u0110\u1eb7t H\u00e0ng\"**), Shopee tr\u00ecnh b\u00e0y m\u1ed9t b\u1ea3ng t\u00ednh to\u00e1n chi ph\u00ed n\u1eb1m d\u1ed3n v\u1ec1 g\u00f3c b\u00ean ph\u1ea3i g\u1ed3m 3 d\u00f2ng:\n\n-   `T\u1ed5ng ti\u1ec1n h\u00e0ng:`  $\\longrightarrow$  `\u20ab4.740.000`\n    \n-   `Ph\u00ed v\u1eadn chuy\u1ec3n:`  $\\longrightarrow$  `\u20ab38.000`\n    \n-   `T\u1ed5ng c\u1ed9ng Voucher gi\u1ea3m gi\u00e1:`  $\\longrightarrow$  `-\u20ab50.000`\n    \n-   `T\u1ed5ng thanh to\u00e1n:`  $\\longrightarrow$  **`\u20ab4.728.000`**  _(C\u1ee1 ch\u1eef l\u1edbn `2.8rem` m\u00e0u cam r\u1ef1c r\u1ee1)_\n    \n\nThay v\u00ec d\u00f9ng `display: flex; justify-content: flex-end;` cho t\u1eebng d\u00f2ng ri\u00eang l\u1ebb (d\u1ec5 l\u00e0m c\u1ed9t ch\u1eef b\u00ean tr\u00e1i b\u1ecb l\u1ec7ch nhau), ch\u00fang ta d\u00f9ng **`display: grid; grid-template-columns: 1fr auto;`** \u0111\u1eb7t \u1edf g\u00f3c ph\u1ea3i:\n\n-   C\u1ed9t 1 (`1fr`): Ch\u1ee9a nh\u00e3n ch\u1eef (_T\u1ed5ng ti\u1ec1n h\u00e0ng, Ph\u00ed v\u1eadn chuy\u1ec3n..._), c\u0103n ph\u1ea3i ho\u1eb7c c\u0103n tr\u00e1i g\u1ecdn g\u00e0ng.\n    \n-   C\u1ed9t 2 (`auto` ho\u1eb7c c\u1ed1 \u0111\u1ecbnh `16rem` c\u0103n ph\u1ea3i): Ch\u1ee9a c\u00e1c con s\u1ed1 ti\u1ec1n t\u1ec7 th\u1eb3ng h\u00e0ng d\u1ecdc theo \u0111\u01a1n v\u1ecb h\u00e0ng ngh\u00ecn, h\u00e0ng tri\u1ec7u!\n    \n\n## 3. Th\u1ef1c h\u00e0nh: Code Giao Di\u1ec7n Trang Thanh To\u00e1n (`Checkout Page`) Chu\u1ea9n Shopee\n\n### B\u01b0\u1edbc 1: B\u1ed5 sung HTML Khu V\u1ef1c Thanh To\u00e1n (`.checkout-section`) v\u00e0o `index.html`\n\nH\u00e3y m\u1edf file `index.html`, t\u00ecm \u0111\u1ebfn b\u00ean trong `<div class=\"shopee-container grid-container\">` (ngay b\u00ean d\u01b0\u1edbi `<\/section>` c\u1ee7a Ch\u01b0\u01a1ng 8 - Trang Gi\u1ecf H\u00e0ng m\u00e0 ch\u00fang ta v\u1eeba l\u00e0m \u1edf B\u00e0i 23) v\u00e0 ch\u00e8n to\u00e0n b\u1ed9 c\u1ea5u tr\u00fac **Trang Thanh To\u00e1n** d\u01b0\u1edbi \u0111\u00e2y:\n\nHTML\n\n```\n        <!-- ================================================================= -->\n        <!-- B\u00c0I 24: GIAO DI\u1ec6N TRANG THANH TO\u00c1N (CHECKOUT PAGE)                -->\n        <!-- ================================================================= -->\n        <section class=\"checkout-section\">\n            \n            <!-- 1. KHUNG \u0110\u1ecaA CH\u1ec8 NH\u1eacN H\u00c0NG (C\u00d3 VI\u1ec0N PHONG B\u00cc TH\u01af \u0110\u1ece - XANH) -->\n            <div class=\"checkout-address-box\">\n                <!-- D\u1ea3i vi\u1ec1n phong b\u00ec th\u01b0 k\u1ebb s\u1ecdc \u0110\u1ecf - Xanh cao 3px \u1edf \u0111\u1ec9nh h\u1ed9p -->\n                <div class=\"envelope-border-line\"><\/div>\n\n                <div class=\"checkout-address__content\">\n                    <div class=\"checkout-address__header\">\n                        <i class=\"fa-solid fa-location-dot checkout-address__icon\"><\/i>\n                        <h2 class=\"checkout-address__title\">\u0110\u1ecba Ch\u1ec9 Nh\u1eadn H\u00e0ng<\/h2>\n                    <\/div>\n\n                    <div class=\"checkout-address__body\">\n                        <div class=\"checkout-address__user\">\n                            <strong>Nguy\u1ec5n Huy Ho\u00e0ng (Hi\u1ebfu)<\/strong>\n                            <span>(+84) 941 280 073<\/span>\n                        <\/div>\n                        <p class=\"checkout-address__detail\">\n                            \u0110\u01b0\u1eddng V\u00f5 V\u0103n Ki\u1ec7t, Ph\u01b0\u1eddng 16, Qu\u1eadn 8, Th\u00e0nh Ph\u1ed1 H\u1ed3 Ch\u00ed Minh\n                        <\/p>\n                        <span class=\"checkout-address__default-badge\">M\u1eb7c \u0110\u1ecbnh<\/span>\n                        <a href=\"#\" class=\"checkout-address__change-btn\">Thay \u0110\u1ed5i<\/a>\n                    <\/div>\n                <\/div>\n            <\/div>\n\n            <!-- 2. KHUNG S\u1ea2N PH\u1ea8M \u0110\u1eb6T MUA & L\u1edcI NH\u1eaeN \/ V\u1eacN CHUY\u1ec2N -->\n            <div class=\"checkout-products-box\">\n                \n                <!-- Ti\u00eau \u0111\u1ec1 4 c\u1ed9t c\u1ee7a b\u1ea3ng s\u1ea3n ph\u1ea9m thanh to\u00e1n -->\n                <div class=\"checkout-table-header\">\n                    <div class=\"checkout-col-product\">S\u1ea3n ph\u1ea9m<\/div>\n                    <div class=\"checkout-col-center\">\u0110\u01a1n gi\u00e1<\/div>\n                    <div class=\"checkout-col-center\">S\u1ed1 l\u01b0\u1ee3ng<\/div>\n                    <div class=\"checkout-col-right\">Th\u00e0nh ti\u1ec1n<\/div>\n                <\/div>\n\n                <!-- Th\u00f4ng tin Gian h\u00e0ng -->\n                <div class=\"checkout-shop-bar\">\n                    <span class=\"cart-shop-badge\">Y\u00eau th\u00edch+<\/span>\n                    <span class=\"checkout-shop-name\">AULA Official Store VN<\/span>\n                    <span class=\"checkout-shop-divider\">|<\/span>\n                    <button type=\"button\" class=\"checkout-chat-now\">\n                        <i class=\"fa-solid fa-comments\"><\/i> Chat ngay\n                    <\/button>\n                <\/div>\n\n                <!-- D\u00f2ng s\u1ea3n ph\u1ea9m x\u00e1c nh\u1eadn mua -->\n                <div class=\"checkout-item-row\">\n                    <div class=\"checkout-item-info\">\n                        <img src=\"https:\/\/placehold.co\/120x120\/ee4d2d\/ffffff?text=AULA+F75\" alt=\"AULA F75 Pro\" class=\"checkout-item-img\">\n                        <div class=\"checkout-item-name-wrap\">\n                            <h3 class=\"checkout-item-name\">B\u00e0n ph\u00edm c\u01a1 kh\u00f4ng d\u00e2y AULA F75 Pro 3 Mode M\u1ea1ch Xu\u00f4i Hotswap LED RGB<\/h3>\n                        <\/div>\n                        <span class=\"checkout-item-variation\">Lo\u1ea1i: Tr\u1eafng Xanh (Ice Vein)<\/span>\n                    <\/div>\n                    <div class=\"checkout-col-center\">\u20ab850.000<\/div>\n                    <div class=\"checkout-col-center\">1<\/div>\n                    <div class=\"checkout-col-right checkout-item-subtotal\">\u20ab850.000<\/div>\n                <\/div>\n\n                <!-- Khu v\u1ef1c L\u1eddi nh\u1eafn cho Shop (Tr\u00e1i) & \u0110\u01a1n v\u1ecb v\u1eadn chuy\u1ec3n (Ph\u1ea3i) ng\u0103n c\u00e1ch b\u1eb1ng vi\u1ec1n n\u00e9t \u0111\u1ee9t -->\n                <div class=\"checkout-order-extra\">\n                    <!-- C\u1ed9t tr\u00e1i: \u00d4 nh\u1eadp l\u1eddi nh\u1eafn -->\n                    <div class=\"checkout-message-col\">\n                        <label for=\"buyer-note\" class=\"checkout-message-label\">L\u1eddi nh\u1eafn:<\/label>\n                        <input type=\"text\" id=\"buyer-note\" class=\"checkout-message-input\" placeholder=\"L\u01b0u \u00fd cho Ng\u01b0\u1eddi b\u00e1n (V\u00ed d\u1ee5: Giao gi\u1edd h\u00e0nh ch\u00ednh)...\">\n                    <\/div>\n\n                    <!-- C\u1ed9t ph\u1ea3i: Th\u00f4ng tin \u0110\u01a1n v\u1ecb v\u1eadn chuy\u1ec3n -->\n                    <div class=\"checkout-shipping-col\">\n                        <div class=\"shipping-col__label\">\u0110\u01a1n v\u1ecb v\u1eadn chuy\u1ec3n:<\/div>\n                        <div class=\"shipping-col__details\">\n                            <p class=\"shipping-method-name\">Nhanh (SPX Express)<\/p>\n                            <p class=\"shipping-receive-date\">\n                                <i class=\"fa-regular fa-truck\"><\/i> \u0110\u1ea3m b\u1ea3o nh\u1eadn h\u00e0ng t\u1eeb 15 Th\u00e1ng 11 - 16 Th\u00e1ng 11\n                            <\/p>\n                            <p class=\"shipping-voucher-note\">Nh\u1eadn Voucher tr\u1ecb gi\u00e1 \u20ab15.000 n\u1ebfu \u0111\u01a1n h\u00e0ng \u0111\u01b0\u1ee3c giao \u0111\u1ebfn b\u1ea1n sau ng\u00e0y 16 Th\u00e1ng 11 2026.<\/p>\n                        <\/div>\n                        <a href=\"#\" class=\"shipping-col__change\">Thay \u0110\u1ed5i<\/a>\n                        <div class=\"shipping-col__fee\">\u20ab0<\/div>\n                    <\/div>\n                <\/div>\n\n                <!-- T\u1ed5ng s\u1ed1 ti\u1ec1n c\u1ee7a ri\u00eang Gian h\u00e0ng n\u00e0y -->\n                <div class=\"checkout-shop-total\">\n                    <span class=\"checkout-shop-total__label\">T\u1ed5ng s\u1ed1 ti\u1ec1n (1 s\u1ea3n ph\u1ea9m):<\/span>\n                    <span class=\"checkout-shop-total__price\">\u20ab850.000<\/span>\n                <\/div>\n\n            <\/div>\n\n            <!-- 3. KHUNG PH\u01af\u01a0NG TH\u1ee8C THANH TO\u00c1N & T\u1ed4NG K\u1ebeT H\u00d3A \u0110\u01a0N CU\u1ed0I C\u00d9NG -->\n            <div class=\"checkout-payment-box\">\n                \n                <!-- T\u1ea7ng A: Ch\u1ecdn Ph\u01b0\u01a1ng th\u1ee9c thanh to\u00e1n (D\u00f9ng Radio Hack c\u00f3 G\u00f3c t\u00edch 3D!) -->\n                <div class=\"payment-method-bar\">\n                    <h3 class=\"payment-method__title\">Ph\u01b0\u01a1ng th\u1ee9c thanh to\u00e1n<\/h3>\n                    \n                    <div class=\"payment-method__options\">\n                        <label class=\"variation-item\">\n                            <input type=\"radio\" name=\"payment-type\" class=\"variation-input\">\n                            <span class=\"variation-btn\">V\u00ed ShopeePay<\/span>\n                        <\/label>\n                        <label class=\"variation-item\">\n                            <input type=\"radio\" name=\"payment-type\" class=\"variation-input\">\n                            <span class=\"variation-btn\">Th\u1ebb T\u00edn d\u1ee5ng\/Ghi n\u1ee3<\/span>\n                        <\/label>\n                        <label class=\"variation-item\">\n                            <input type=\"radio\" name=\"payment-type\" class=\"variation-input\">\n                            <span class=\"variation-btn\">Google Pay \/ Apple Pay<\/span>\n                        <\/label>\n                        <!-- M\u1eb7c \u0111\u1ecbnh ch\u1ecdn Thanh to\u00e1n khi nh\u1eadn h\u00e0ng (COD) -->\n                        <label class=\"variation-item\">\n                            <input type=\"radio\" name=\"payment-type\" class=\"variation-input\" checked>\n                            <span class=\"variation-btn\">Thanh to\u00e1n khi nh\u1eadn h\u00e0ng<\/span>\n                        <\/label>\n                    <\/div>\n                <\/div>\n\n                <!-- T\u1ea7ng B: B\u1ea3ng t\u00ednh nh\u1ea9m chi ph\u00ed (Order Summary Table) -->\n                <div class=\"payment-summary-area\">\n                    <div class=\"payment-summary-grid\">\n                        <span class=\"summary-label\">T\u1ed5ng ti\u1ec1n h\u00e0ng<\/span>\n                        <span class=\"summary-value\">\u20ab850.000<\/span>\n\n                        <span class=\"summary-label\">Ph\u00ed v\u1eadn chuy\u1ec3n<\/span>\n                        <span class=\"summary-value\">\u20ab30.000<\/span>\n\n                        <span class=\"summary-label\">Gi\u1ea3m gi\u00e1 ph\u00ed v\u1eadn chuy\u1ec3n<\/span>\n                        <span class=\"summary-value\">-\u20ab30.000<\/span>\n\n                        <span class=\"summary-label\">T\u1ed5ng c\u1ed9ng Voucher gi\u1ea3m gi\u00e1<\/span>\n                        <span class=\"summary-value\">-\u20ab50.000<\/span>\n\n                        <span class=\"summary-label summary-label--total\">T\u1ed5ng thanh to\u00e1n<\/span>\n                        <span class=\"summary-value summary-value--total\">\u20ab800.000<\/span>\n                    <\/div>\n                <\/div>\n\n                <!-- T\u1ea7ng C: \u0110i\u1ec1u kho\u1ea3n & N\u00fat \u0110\u1eb6T H\u00c0NG ch\u1ed1t s\u1ed5 -->\n                <div class=\"payment-submit-bar\">\n                    <p class=\"payment-terms-text\">\n                        Nh\u1ea5n \"\u0110\u1eb7t h\u00e0ng\" \u0111\u1ed3ng ngh\u0129a v\u1edbi vi\u1ec7c b\u1ea1n \u0111\u1ed3ng \u00fd tu\u00e2n theo \n                        <a href=\"#\" class=\"payment-terms-link\">\u0110i\u1ec1u kho\u1ea3n Shopee<\/a>\n                    <\/p>\n                    <button type=\"button\" class=\"btn btn--primary payment-order-btn\">\n                        \u0110\u1eb7t h\u00e0ng\n                    <\/button>\n                <\/div>\n\n            <\/div>\n\n        <\/section>\n\n```\n\n### B\u01b0\u1edbc 2: Vi\u1ebft CSS cho Vi\u1ec1n Phong B\u00ec Th\u01b0 (`repeating-linear-gradient`) & Trang Thanh To\u00e1n trong `.\/assets\/css\/main.css`\n\nH\u00e3y m\u1edf file `main.css`, cu\u1ed9n xu\u1ed1ng d\u01b0\u1edbi c\u00f9ng v\u00e0 b\u1ed5 sung \u0111o\u1ea1n code CSS c\u1ee7a B\u00e0i 24 d\u01b0\u1edbi \u0111\u00e2y:\n\nCSS\n\n```\n\/* B\u1ed5 sung v\u00e0o cu\u1ed1i file .\/assets\/css\/main.css *\/\n\n\/* --- B\u00c0I 24: GIAO DI\u1ec6N TRANG THANH TO\u00c1N & VI\u1ec0N PHONG B\u00cc TH\u01af \u0110\u1ece - XANH --- *\/\n\n.checkout-section {\n    margin-top: 2rem;\n    margin-bottom: 5rem;\n}\n\n\/* 1. Khung \u0110\u1ecba Ch\u1ec9 Nh\u1eadn H\u00e0ng & Vi\u1ec1n Phong B\u00ec Th\u01b0 *\/\n.checkout-address-box {\n    background-color: var(--white-color);\n    border-radius: 2px;\n    box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05);\n    margin-bottom: 1.2rem;\n    overflow: hidden;\n}\n\n\/* B\u00cd THU\u1eacT: V\u1ebd \u0111\u01b0\u1eddng vi\u1ec1n phong b\u00ec th\u01b0 k\u1ebb s\u1ecdc ch\u00e9o \u0110\u1ecf - Xanh b\u1eb1ng repeating-linear-gradient! *\/\n.envelope-border-line {\n    height: 0.3rem;                 \/* Cao \u0111\u00fang 3px chu\u1ea9n Shopee *\/\n    width: 100%;\n    background-position-x: -3rem;\n    background-size: 11.6rem 0.3rem;\n    background-image: repeating-linear-gradient(\n        45deg,\n        #6fa6d6,\n        #6fa6d6 3.3rem,             \/* V\u1ea1ch Xanh D\u01b0\u01a1ng d\u00e0i 33px *\/\n        transparent 0,\n        transparent 4.1rem,         \/* Khe tr\u1eafng 8px (T\u1eeb 33px -> 41px) *\/\n        #f18d9b 0,\n        #f18d9b 7.4rem,             \/* V\u1ea1ch \u0110\u1ecf H\u1ed3ng d\u00e0i 33px (T\u1eeb 41px -> 74px) *\/\n        transparent 0,\n        transparent 8.2rem          \/* Khe tr\u1eafng 8px k\u1ebft th\u00fac chu k\u1ef3 (T\u1eeb 74px -> 82px) *\/\n    );\n}\n\n.checkout-address__content {\n    padding: 2.8rem 3rem 2.4rem;\n}\n\n.checkout-address__header {\n    display: flex;\n    align-items: center;\n    gap: 1rem;\n    color: var(--shopee-primary);\n    margin-bottom: 1.6rem;\n}\n\n.checkout-address__icon {\n    font-size: 1.8rem;\n}\n\n.checkout-address__title {\n    font-size: 1.8rem;\n    font-weight: 500;\n}\n\n.checkout-address__body {\n    display: flex;\n    align-items: center;\n    flex-wrap: wrap;\n    font-size: 1.5rem;\n    gap: 1.6rem;\n}\n\n.checkout-address__user {\n    color: var(--text-primary);\n    font-weight: 700;\n    display: flex;\n    gap: 0.8rem;\n}\n\n.checkout-address__detail {\n    color: var(--text-primary);\n}\n\n.checkout-address__default-badge {\n    font-size: 1.1rem;\n    color: var(--shopee-primary);\n    border: 1px solid var(--shopee-primary);\n    padding: 0.1rem 0.5rem;\n    border-radius: 1px;\n}\n\n.checkout-address__change-btn {\n    color: #0055aa;\n    font-size: 1.4rem;\n    margin-left: 1.6rem;\n}\n\n\/* 2. Khung S\u1ea3n Ph\u1ea9m \u0110\u1eb7t Mua & L\u1eddi Nh\u1eafn \/ V\u1eadn Chuy\u1ec3n *\/\n.checkout-products-box {\n    background-color: var(--white-color);\n    border-radius: 2px;\n    box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05);\n    margin-bottom: 1.2rem;\n}\n\n\/* Chia 4 c\u1ed9t \u0111\u1ed3ng b\u1ed9 cho Header v\u00e0 D\u00f2ng s\u1ea3n ph\u1ea9m *\/\n.checkout-table-header,\n.checkout-item-row {\n    display: grid;\n    grid-template-columns: 1fr 15rem 15rem 16rem;\n    align-items: center;\n    padding: 0 3rem;\n}\n\n.checkout-table-header {\n    height: 5rem;\n    font-size: 1.4rem;\n    color: var(--text-secondary);\n    padding-top: 1.6rem;\n}\n\n.checkout-col-product {\n    font-size: 1.8rem;\n    color: var(--text-primary);\n}\n\n.checkout-col-center {\n    text-align: center;\n    font-size: 1.4rem;\n    color: var(--text-primary);\n}\n\n.checkout-col-right {\n    text-align: right;\n    font-size: 1.4rem;\n    color: var(--text-primary);\n}\n\n.checkout-shop-bar {\n    padding: 1.6rem 3rem 1.2rem;\n    display: flex;\n    align-items: center;\n    font-size: 1.4rem;\n}\n\n.checkout-shop-name {\n    color: var(--text-primary);\n    margin-right: 0.8rem;\n}\n\n.checkout-shop-divider {\n    color: rgba(0, 0, 0, 0.14);\n    margin-right: 0.8rem;\n}\n\n.checkout-chat-now {\n    color: #00bfa5;\n    font-size: 1.35rem;\n    display: inline-flex;\n    align-items: center;\n    gap: 0.4rem;\n}\n\n.checkout-item-row {\n    padding-top: 1.6rem;\n    padding-bottom: 2rem;\n    border-bottom: 1px dashed rgba(0, 0, 0, 0.09);\n}\n\n.checkout-item-info {\n    display: flex;\n    align-items: center;\n    gap: 1.4rem;\n    padding-right: 2rem;\n}\n\n.checkout-item-img {\n    width: 4.5rem;\n    height: 4.5rem;\n    object-fit: cover;\n    border: 1px solid rgba(0, 0, 0, 0.09);\n    flex-shrink: 0;\n}\n\n.checkout-item-name-wrap {\n    flex: 1;\n    min-width: 0;\n}\n\n.checkout-item-name {\n    font-size: 1.4rem;\n    font-weight: 400;\n    color: var(--text-primary);\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n}\n\n.checkout-item-variation {\n    width: 20rem;\n    font-size: 1.35rem;\n    color: var(--text-secondary);\n    flex-shrink: 0;\n}\n\n.checkout-item-subtotal {\n    font-weight: 500;\n}\n\n\/* Khu v\u1ef1c L\u1eddi nh\u1eafn (Tr\u00e1i) & \u0110\u01a1n v\u1ecb v\u1eadn chuy\u1ec3n (Ph\u1ea3i) tr\u00ean n\u1ec1n xanh nh\u1ea1t #fafdff *\/\n.checkout-order-extra {\n    background-color: #fafdff;\n    border-bottom: 1px dashed rgba(0, 0, 0, 0.09);\n    display: grid;\n    grid-template-columns: 44rem 1fr; \/* C\u1ed9t l\u1eddi nh\u1eafn 440px | C\u1ed9t v\u1eadn chuy\u1ec3n chi\u1ebfm ph\u1ea7n c\u00f2n l\u1ea1i *\/\n}\n\n.checkout-message-col {\n    padding: 2rem 3rem;\n    border-right: 1px dashed rgba(0, 0, 0, 0.09);\n    display: flex;\n    align-items: center;\n    gap: 1.4rem;\n}\n\n.checkout-message-label {\n    font-size: 1.4rem;\n    color: var(--text-primary);\n    white-space: nowrap;\n}\n\n.checkout-message-input {\n    flex: 1;\n    height: 3.8rem;\n    background-color: var(--white-color);\n    border: 1px solid rgba(0, 0, 0, 0.14);\n    border-radius: 2px;\n    padding: 0 1.2rem;\n    font-size: 1.35rem;\n}\n\n.checkout-message-input:focus {\n    border-color: rgba(0, 0, 0, 0.4);\n}\n\n.checkout-shipping-col {\n    padding: 2rem 3rem;\n    display: flex;\n    align-items: flex-start;\n    gap: 2rem;\n    font-size: 1.35rem;\n}\n\n.shipping-col__label {\n    color: #00bfa5;\n    font-weight: 500;\n    white-space: nowrap;\n}\n\n.shipping-col__details {\n    flex: 1;\n}\n\n.shipping-method-name {\n    font-weight: 500;\n    color: var(--text-primary);\n    margin-bottom: 0.4rem;\n}\n\n.shipping-receive-date {\n    color: #26aa99;\n    font-size: 1.25rem;\n    margin-bottom: 0.4rem;\n}\n\n.shipping-voucher-note {\n    font-size: 1.2rem;\n    color: var(--text-secondary);\n}\n\n.shipping-col__change {\n    color: #0055aa;\n    font-weight: 500;\n}\n\n.shipping-col__fee {\n    width: 10rem;\n    text-align: right;\n    color: var(--text-primary);\n    font-weight: 500;\n}\n\n.checkout-shop-total {\n    background-color: #fafdff;\n    padding: 1.8rem 3rem;\n    display: flex;\n    align-items: center;\n    justify-content: flex-end;\n    gap: 2rem;\n}\n\n.checkout-shop-total__label {\n    font-size: 1.4rem;\n    color: var(--text-secondary);\n}\n\n.checkout-shop-total__price {\n    font-size: 2rem;\n    color: var(--shopee-primary);\n    font-weight: 500;\n}\n\n\/* 3. Khung Ph\u01b0\u01a1ng Th\u1ee9c Thanh To\u00e1n & T\u1ed5ng K\u1ebft H\u00f3a \u0110\u01a1n *\/\n.checkout-payment-box {\n    background-color: var(--white-color);\n    border-radius: 2px;\n    box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05);\n}\n\n.payment-method-bar {\n    padding: 2.4rem 3rem;\n    border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n    display: flex;\n    align-items: center;\n    gap: 4rem;\n}\n\n.payment-method__title {\n    font-size: 1.8rem;\n    font-weight: 400;\n    color: var(--text-primary);\n    white-space: nowrap;\n}\n\n.payment-method__options {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 1rem;\n}\n\n\/* Khu v\u1ef1c b\u1ea3ng t\u00ednh nh\u1ea9m chi ph\u00ed n\u1ec1n v\u00e0ng kem #fffefb *\/\n.payment-summary-area {\n    background-color: #fffefb;\n    padding: 2.4rem 3rem;\n    border-bottom: 1px dashed rgba(0, 0, 0, 0.09);\n    display: flex;\n    justify-content: flex-end;      \/* \u0110\u1ea9y b\u1ea3ng t\u00ednh nh\u1ea9m v\u1ec1 s\u00e1t g\u00f3c ph\u1ea3i *\/\n}\n\n.payment-summary-grid {\n    display: grid;\n    grid-template-columns: auto 16rem; \/* C\u1ed9t ch\u1eef t\u1ef1 \u0111\u1ed9ng | C\u1ed9t s\u1ed1 ti\u1ec1n r\u1ed9ng 160px c\u0103n ph\u1ea3i *\/\n    row-gap: 1.6rem;\n    align-items: center;\n    font-size: 1.4rem;\n}\n\n.summary-label {\n    color: var(--text-secondary);\n    text-align: left;\n}\n\n.summary-value {\n    color: var(--text-primary);\n    text-align: right;\n}\n\n.summary-label--total {\n    font-size: 1.5rem;\n}\n\n.summary-value--total {\n    font-size: 2.8rem;              \/* T\u1ed5ng thanh to\u00e1n cu\u1ed1i c\u00f9ng to 28px m\u00e0u cam r\u1ef1c r\u1ee1 *\/\n    color: var(--shopee-primary);\n    font-weight: 500;\n}\n\n\/* Ch\u00e2n n\u00fat \u0110\u1eb6T H\u00c0NG *\/\n.payment-submit-bar {\n    background-color: #fffefb;\n    padding: 2rem 3rem;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n}\n\n.payment-terms-text {\n    font-size: 1.4rem;\n    color: var(--text-secondary);\n}\n\n.payment-terms-link {\n    color: #0055aa;\n}\n\n.payment-order-btn {\n    min-width: 21rem;\n    height: 4.2rem;\n    font-size: 1.6rem;\n    font-weight: 500;\n}\n\n```\n\n![image.png](https:\/\/images.viblo.asia\/bfd38df8-f73a-4caf-a626-660a67e9a170.png)\n\n## 4. Gi\u1ea3i m\u00e3 d\u00f2ng code: 3 Chi ti\u1ebft \"nh\u1ecf nh\u01b0ng tinh hoa\" trong B\u00e0i 24\n\n1.  **M\u1eb9o vi\u1ebft t\u1eaft s\u1ed1 `0` trong `repeating-linear-gradient`:**\n    \n    -   Trong \u0111o\u1ea1n code `.envelope-border-line`, b\u1ea1n c\u00f3 th\u1ea5y ch\u00fang ta vi\u1ebft `#6fa6d6 3.3rem, transparent 0, transparent 4.1rem` kh\u00f4ng?\n        \n    -   Theo chu\u1ea9n W3C CSS Images Module, khi b\u1ea1n \u0111\u1eb7t v\u1ecb tr\u00ed b\u1eaft \u0111\u1ea7u c\u1ee7a m\u00e0u sau l\u00e0 **`0`** (nh\u1ecf h\u01a1n v\u1ecb tr\u00ed k\u1ebft th\u00fac `3.3rem` c\u1ee7a m\u00e0u tr\u01b0\u1edbc), tr\u00ecnh duy\u1ec7t s\u1ebd **t\u1ef1 \u0111\u1ed9ng hi\u1ec3u v\u1ecb tr\u00ed b\u1eaft \u0111\u1ea7u \u0111\u00f3 b\u1eb1ng \u0111\u00fang `3.3rem`**! Nh\u1edd m\u1eb9o n\u00e0y, n\u1ebfu sau n\u00e0y b\u1ea1n mu\u1ed1n t\u0103ng \u0111\u1ed9 d\u00e0i v\u1ea1ch xanh t\u1eeb `3.3rem` l\u00ean `4.0rem`, b\u1ea1n ch\u1ec9 c\u1ea7n s\u1eeda \u0111\u00fang 1 con s\u1ed1 m\u00e0 kh\u00f4ng c\u1ea7n s\u1eeda l\u1ea1i \u0111i\u1ec3m b\u1eaft \u0111\u1ea7u c\u1ee7a kho\u1ea3ng tr\u1eafng theo sau n\u00f3!\n        \n2.  **T\u00e1i s\u1eed d\u1ee5ng Component `.variation-item` (B\u00e0i 20) cho n\u00fat ch\u1ecdn Ph\u01b0\u01a1ng th\u1ee9c thanh to\u00e1n:**\n    \n    -   \u1ede t\u1ea7ng ch\u1ecdn _Ph\u01b0\u01a1ng th\u1ee9c thanh to\u00e1n_ (_V\u00ed ShopeePay, Th\u1ebb T\u00edn d\u1ee5ng, Thanh to\u00e1n khi nh\u1eadn h\u00e0ng_), thay v\u00ec ph\u1ea3i vi\u1ebft l\u1ea1i h\u00e0ng ch\u1ee5c d\u00f2ng CSS m\u1edbi, ch\u00fang ta g\u1ecdi l\u1ea1i \u0111\u00fang b\u1ed9 class `.variation-item`,  `.variation-input`,  `.variation-btn` \u0111\u00e3 x\u00e2y d\u1ef1ng \u1edf B\u00e0i 20!\n        \n    -   K\u1ebft qu\u1ea3: Khi ng\u01b0\u1eddi d\u00f9ng click ch\u1ecdn ph\u01b0\u01a1ng th\u1ee9c thanh to\u00e1n n\u00e0o, n\u00fat \u0111\u00f3 t\u1ef1 \u0111\u1ed9ng b\u1eadt vi\u1ec1n cam k\u00e8m **mi\u1ebfng tam gi\u00e1c cam + d\u1ea5u t\u00edch tr\u1eafng (`\u2713`) \u1edf g\u00f3c d\u01b0\u1edbi b\u00ean ph\u1ea3i** m\u00e0 kh\u00f4ng t\u1ed1n th\u00eam 1 d\u00f2ng CSS n\u00e0o!\n        \n3.  **S\u1ef1 ph\u1ed1i m\u00e0u n\u1ec1n `#fafdff` (Xanh b\u0103ng nh\u1ea1t) v\u00e0 `#fffefb` (V\u00e0ng kem nh\u1ea1t):**\n    \n    -   Khu v\u1ef1c _L\u1eddi nh\u1eafn & V\u1eadn chuy\u1ec3n_ d\u00f9ng n\u1ec1n xanh b\u0103ng c\u1ef1c nh\u1ea1t (`#fafdff`) \u0111\u1ec3 \u0111\u1ed3ng \u0111i\u1ec7u v\u1edbi m\u00e0u xanh `#26aa99` c\u1ee7a chi\u1ebfc xe t\u1ea3i giao h\u00e0ng SPX.\n        \n    -   Khu v\u1ef1c _T\u1ed5ng k\u1ebft h\u00f3a \u0111\u01a1n_ \u1edf d\u01b0\u1edbi c\u00f9ng d\u00f9ng n\u1ec1n v\u00e0ng kem (`#fffefb`) \u0111\u1ec3 l\u00e0m n\u1ed5i b\u1eadt con s\u1ed1 ti\u1ec1n m\u00e0u cam `#ee4d2d`. \u0110\u00e2y l\u00e0 ngh\u1ec7 thu\u1eadt d\u00f9ng m\u00e0u n\u1ec1n vi t\u1ebf (Subtle Tinted Backgrounds) \u0111\u1ec3 ph\u00e2n t\u00e1ch c\u00e1c v\u00f9ng ch\u1ee9c n\u0103ng m\u00e0 kh\u00f4ng c\u1ea7n d\u00f9ng khung vi\u1ec1n th\u00f4 c\u1ee9ng!\n        \n\n## 5. B\u00e0i t\u1eadp tr\u1ea3i nghi\u1ec7m th\u1ef1c t\u1ebf & H\u1eb9n g\u1eb7p \u1edf B\u00e0i 25 (C\u1ed9t m\u1ed1c $\\frac{1}{4}$ ch\u1eb7ng \u0111\u01b0\u1eddng)!\n\nH\u00e3y l\u01b0u l\u1ea1i file `index.html` v\u00e0 `main.css`, sau \u0111\u00f3 nh\u1ea5n F5 tr\u00ean tr\u00ecnh duy\u1ec7t v\u00e0 cu\u1ed9n xu\u1ed1ng khu v\u1ef1c **Trang Thanh To\u00e1n (B\u00e0i 24)**:\n\n1.  H\u00e3y ng\u1eafm nh\u00ecn \u0111\u01b0\u1eddng vi\u1ec1n **Phong b\u00ec th\u01b0 k\u1ebb s\u1ecdc \u0110\u1ecf - Xanh** cao `3px` \u1edf ngay tr\u00ean \u0111\u1ec9nh khung _\u0110\u1ecba Ch\u1ec9 Nh\u1eadn H\u00e0ng_ \u2014 s\u1eafc n\u00e9t tuy\u1ec7t \u0111\u1ed1i nh\u1edd \u0111\u01b0\u1ee3c v\u1ebd b\u1eb1ng to\u00e1n h\u1ecdc Vector c\u1ee7a CSS!\n    \n2.  H\u00e3y th\u1eed **click chu\u1ed9t qua l\u1ea1i gi\u1eefa 4 n\u00fat Ph\u01b0\u01a1ng th\u1ee9c thanh to\u00e1n** (_V\u00ed ShopeePay_,  _Th\u1ebb T\u00edn d\u1ee5ng_,  _Google Pay_,  _Thanh to\u00e1n khi nh\u1eadn h\u00e0ng_) \u0111\u1ec3 th\u1ea5y d\u1ea5u t\u00edch g\u00f3c 3D chuy\u1ec3n \u0111\u1ed5i m\u01b0\u1ee3t m\u00e0.\n    \n3.  Quan s\u00e1t b\u1ea3ng t\u00ednh nh\u1ea9m \u1edf g\u00f3c d\u01b0\u1edbi b\u00ean ph\u1ea3i: c\u00e1c con s\u1ed1 `\u20ab850.000`,  `\u20ab30.000`,  `-\u20ab50.000` v\u00e0 t\u1ed5ng ti\u1ec1n `\u20ab800.000` th\u1eb3ng h\u00e0ng d\u1ecdc b\u00ean ph\u1ea3i chu\u1ea9n x\u00e1c nh\u01b0 m\u1ed9t t\u1edd h\u00f3a \u0111\u01a1n th\u1ef1c th\u1ee5!\n    \n\n**Th\u1eed th\u00e1ch kh\u00e1m ph\u00e1 d\u00e0nh cho b\u1ea1n tr\u01b0\u1edbc khi sang B\u00e0i 25:**\n\n1.  H\u00e3y th\u1eed v\u00e0o `.envelope-border-line` trong `main.css`, t\u0103ng `height: 0.3rem;` l\u00ean th\u00e0nh `height: 1.2rem;` (`12px`) v\u00e0 \u0111\u1ed5i g\u00f3c `45deg` th\u00e0nh `-45deg` trong F12 \u0111\u1ec3 quan s\u00e1t th\u1eadt r\u00f5 c\u00e1c s\u1ecdc m\u00e0u \u0110\u1ecf - Tr\u1eafng - Xanh \u0111\u1ea3o chi\u1ec1u nghi\u00eang nh\u01b0 th\u1ebf n\u00e0o nh\u00e9!\n    \n2.  H\u00e3y th\u1eed th\u00eam m\u1ed9t d\u00f2ng th\u00f4ng b\u00e1o nh\u1ecf b\u00ean d\u01b0\u1edbi n\u00fat _Thanh to\u00e1n khi nh\u1eadn h\u00e0ng_ (ch\u1ec9 hi\u1ec7n ra khi ch\u1ecdn COD):  _\"Ph\u00ed thu h\u1ed9: \u20ab0 VN\u0110. \u01afu \u0111\u00e3i v\u1ec1 ph\u00ed v\u1eadn chuy\u1ec3n (n\u1ebfu c\u00f3) \u00e1p d\u1ee5ng c\u1ea3 v\u1edbi ph\u00ed thu h\u1ed9.\"_ b\u1eb1ng b\u1ed9 ch\u1ecdn `:checked ~`!\n    \n\n\ud83c\udf89 **HO\u00c0N TH\u00c0NH CH\u01af\u01a0NG 8 (B\u00c0I 23 \u2013 B\u00c0I 24): CHINH PH\u1ee4C TR\u1eccN V\u1eb8N LU\u1ed2NG GI\u1ece H\u00c0NG & THANH TO\u00c1N SHOPEE DESKTOP!** \u1ede **B\u00e0i 25** (C\u1ed9t m\u1ed1c k\u1ef7 ni\u1ec7m **25\/100 b\u00e0i h\u1ecdc \u2014 Ho\u00e0n th\u00e0nh $\\frac{1}{4}$ ch\u1eb7ng \u0111\u01b0\u1eddng**!), ch\u00fang ta s\u1ebd ch\u00ednh th\u1ee9c m\u1edf kh\u00f3a **Ch\u01b0\u01a1ng 9: Responsive Web Design (`@media queries`)** \u2014 bi\u1ebfn h\u00f3a to\u00e0n b\u1ed9 giao di\u1ec7n Shopee t\u1eeb m\u00e0n h\u00ecnh M\u00e1y t\u00ednh (`1200px`) co gi\u00e3n m\u01b0\u1ee3t m\u00e0 sang m\u00e0n h\u00ecnh **M\u00e1y t\u00ednh b\u1ea3ng (Tablet `768px`)** v\u00e0 **\u0110i\u1ec7n tho\u1ea1i di \u0111\u1ed9ng (Mobile `375px`)**!","published_at":"2026-11-14T07:27:26.000000Z","scheduled_publish_at":null,"is_published":true,"is_shared":false,"updated_at":"2026-11-14T18:24:02.000000Z","edited_at":"2026-11-14T09:47:04.000000Z","translation_source":null,"trend_at":null,"promoted_at":null,"reading_time":10,"points":0,"views_count":11,"clips_count":0,"comments_count":0,"rated_value":null,"promoted":false,"trending":false,"is_draft":false,"is_public":true,"locale_code":"vi","is_video":false,"thumbnail_url":"https:\/\/images.viblo.asia\/42ae958e-0eac-442d-8112-b2f002b04e99.png","user":{"data":{"id":184801,"url":"https:\/\/viblo.asia\/u\/Tuanhuu1201","avatar":"765295b3-c266-4756-8f0b-6b149861811c.png","name":"Thanh H\u1ea3i Hu\u1ef3nh","username":"Tuanhuu1201","followers_count":1,"reputation":7,"posts_count":26,"banned_at":null,"level_partner":null,"following":false}},"tags":{"data":[{"slug":"htmlcss","name":"HTML\/CSS"}]},"commentators":{"data":[]}},{"id":110619,"title":"[Series 100 B\u00e0i H\u1ecdc HTML\/CSS Th\u1ef1c Chi\u1ebfn - B\u00e0i 23]: Kh\u1edfi \u0110\u1ed9ng Ch\u01b0\u01a1ng 8 \u2014 X\u00e2y D\u1ef1ng Giao Di\u1ec7n Trang Gi\u1ecf H\u00e0ng (Shopping Cart), \u0110\u1ed3ng B\u1ed9 B\u1ea3ng L\u01b0\u1edbi \u0110a Gian H\u00e0ng & B\u00ed Thu\u1eadt L\u00e0m Thanh Thanh To\u00e1n B\u00e1m \u0110\u00e1y (position: sticky; bottom: 0)","slug":"RlL5ebK8LbB","url":"https:\/\/viblo.asia\/p\/series-100-bai-hoc-htmlcss-thuc-chien-bai-23-khoi-dong-chuong-8-xay-dung-giao-dien-trang-gio-hang-shopping-cart-dong-bo-bang-luoi-da-gian-hang-bi-thuat-lam-thanh-thanh-toan-bam-day-position-sticky-bottom-0-RlL5ebK8LbB","user_id":184801,"moderation":null,"transliterated":"series-100-bai-hoc-htmlcss-thuc-chien-bai-23-khoi-dong-chuong-8-xay-dung-giao-dien-trang-gio-hang-shopping-cart-dong-bo-bang-luoi-da-gian-hang-bi-thuat-lam-thanh-thanh-toan-bam-day-position-sticky-bottom-0","contents_short":"Ch\u00e0o m\u1eebng anh em ch\u00ednh th\u1ee9c b\u01b0\u1edbc sang Ch\u01b0\u01a1ng 8 v\u1edbi B\u00e0i 23 trong chu\u1ed7i 100 B\u00e0i H\u1ecdc L\u00e0m Ch\u1ee7 HTML\/CSS Qua D\u1ef1 \u00c1n Clone Shopee!\n\n\u1ede Ch\u01b0\u01a1ng 7 (t\u1eeb B\u00e0i 20 \u0111\u1ebfn B\u00e0i 22), ch\u00fang ta \u0111\u00e3 ho\u00e0n thi\u1ec7n tr\u1ecdn v\u1eb9n Trang Chi Ti\u1ebft S\u1ea3n Ph\u1ea9m (PDP). Sau khi kh\u00e1ch h\u00e0ng ch\u1ecdn xong m\u00e0u s\u1eafc b\u00e0n ph\u00edm, \u0111\u1ecdc b\u00ecnh lu\u1eadn 5 sao v\u00e0 b\u1ea5m n\u00fat \"Th\u00eam V\u00e0o Gi\u1ecf H\u00e0ng\", \u0111i\u1ec3m d\u1eebng ch\u00e2n ti\u1ebfp theo tr\u01b0\u1edbc khi r\u00fat h\u1ea7u bao ch\u00ednh l\u00e0 Trang Gi\u1ecf H\u00e0ng (Shop...","contents":"Ch\u00e0o m\u1eebng anh em ch\u00ednh th\u1ee9c b\u01b0\u1edbc sang **Ch\u01b0\u01a1ng 8** v\u1edbi **B\u00e0i 23** trong chu\u1ed7i **100 B\u00e0i H\u1ecdc L\u00e0m Ch\u1ee7 HTML\/CSS Qua D\u1ef1 \u00c1n Clone Shopee**!\n\n\u1ede Ch\u01b0\u01a1ng 7 (t\u1eeb B\u00e0i 20 \u0111\u1ebfn B\u00e0i 22), ch\u00fang ta \u0111\u00e3 ho\u00e0n thi\u1ec7n tr\u1ecdn v\u1eb9n **Trang Chi Ti\u1ebft S\u1ea3n Ph\u1ea9m (`PDP`)**. Sau khi kh\u00e1ch h\u00e0ng ch\u1ecdn xong m\u00e0u s\u1eafc b\u00e0n ph\u00edm, \u0111\u1ecdc b\u00ecnh lu\u1eadn 5 sao v\u00e0 b\u1ea5m n\u00fat **\"Th\u00eam V\u00e0o Gi\u1ecf H\u00e0ng\"**, \u0111i\u1ec3m d\u1eebng ch\u00e2n ti\u1ebfp theo tr\u01b0\u1edbc khi r\u00fat h\u1ea7u bao ch\u00ednh l\u00e0 **Trang Gi\u1ecf H\u00e0ng (`Shopping Cart Page`)**!\n\nN\u1ebfu b\u1ea1n m\u1edf trang Gi\u1ecf h\u00e0ng c\u1ee7a Shopee tr\u00ean m\u00e1y t\u00ednh, b\u1ea1n s\u1ebd nh\u1eadn ra \u0111\u00e2y kh\u00f4ng ph\u1ea3i l\u00e0 m\u1ed9t b\u1ea3ng `<table>` \u0111\u01a1n thu\u1ea7n nh\u01b0 c\u00e1c trang web truy\u1ec1n th\u1ed1ng, m\u00e0 l\u00e0 m\u1ed9t **h\u1ec7 th\u1ed1ng c\u00e1c kh\u1ed1i th\u1ebb tr\u1eafng t\u00e1ch r\u1eddi theo t\u1eebng Gian h\u00e0ng (Multi-Vendor Cart Blocks)** v\u1edbi 3 b\u00e0i to\u00e1n k\u1ef9 thu\u1eadt CSS c\u1ef1c k\u1ef3 th\u00fa v\u1ecb:\n\n1.  **\u0110\u1ed3ng b\u1ed9 h\u00f3a 6 c\u1ed9t xuy\u00ean su\u1ed1t nhi\u1ec1u kh\u1ed1i (`Synchronized Grid Columns`):** Thanh ti\u00eau \u0111\u1ec1 c\u1ed9t (_S\u1ea3n Ph\u1ea9m | \u0110\u01a1n Gi\u00e1 | S\u1ed1 L\u01b0\u1ee3ng | S\u1ed1 Ti\u1ec1n | Thao T\u00e1c_) n\u1eb1m \u1edf m\u1ed9t kh\u1ed1i tr\u1eafng ri\u00eang tr\u00ean c\u00f9ng, trong khi c\u00e1c s\u1ea3n ph\u1ea9m b\u00ean d\u01b0\u1edbi l\u1ea1i n\u1eb1m trong t\u1eebng kh\u1ed1i Shop t\u00e1ch r\u1eddi nhau. L\u00e0m sao \u0111\u1ec3 c\u1ed9t _\u0110\u01a1n Gi\u00e1_,  _S\u1ed1 L\u01b0\u1ee3ng_,  _S\u1ed1 Ti\u1ec1n_ c\u1ee7a m\u1ecdi s\u1ea3n ph\u1ea9m lu\u00f4n **th\u1eb3ng h\u00e0ng d\u1ecdc tuy\u1ec7t \u0111\u1ed1i** v\u1edbi thanh ti\u00eau \u0111\u1ec1 ph\u00eda tr\u00ean?\n    \n2.  **H\u1ed9p ch\u1ecdn Ph\u00e2n lo\u1ea1i h\u00e0ng ngay trong Gi\u1ecf (`Inline Variation Selector`):** Cho ph\u00e9p kh\u00e1ch h\u00e0ng b\u1ea5m \u0111\u1ed5i m\u00e0u s\u1eafc\/k\u00edch c\u1ee1 ngay trong gi\u1ecf h\u00e0ng m\u00e0 kh\u00f4ng c\u1ea7n quay l\u1ea1i trang s\u1ea3n ph\u1ea9m.\n    \n3.  **Thanh T\u1ed5ng Ti\u1ec1n & Mua H\u00e0ng B\u00e1m D\u00ednh \u0110\u00e1y M\u00e0n H\u00ecnh (`Sticky Bottom Checkout Bar`):** Khi gi\u1ecf h\u00e0ng c\u00f3 t\u1edbi 10\u201320 m\u00f3n \u0111\u1ed3, thanh t\u1ed5ng ti\u1ec1n (_Ch\u1ecdn T\u1ea5t C\u1ea3, Shopee Voucher, D\u00f9ng Shopee Xu, T\u1ed5ng thanh to\u00e1n `\u20ab4.740.000`, N\u00fat MUA H\u00c0NG_) s\u1ebd t\u1ef1 \u0111\u1ed9ng **b\u00e1m ch\u1eb7t \u1edf m\u00e9p d\u01b0\u1edbi c\u00f9ng c\u1ee7a m\u00e0n h\u00ecnh (`bottom: 0`)** cho \u0111\u1ebfn khi ng\u01b0\u1eddi d\u00f9ng cu\u1ed9n ch\u1ea1m \u0111\u00e1y gi\u1ecf h\u00e0ng!\n    \n\nH\u00e3y c\u00f9ng gi\u1ea3i ph\u1eabu v\u00e0 t\u1ef1 tay code to\u00e0n b\u1ed9 khu v\u1ef1c \"ch\u1ed1t s\u1ed5 doanh thu\" n\u00e0y ngay b\u00e2y gi\u1edd!\n\n## 1. Gi\u1ea3i ph\u1eabu C\u1ea5u tr\u00fac Gi\u1ecf h\u00e0ng E-Commerce & B\u00ed quy\u1ebft \u0110\u1ed3ng b\u1ed9 6 C\u1ed9t b\u1eb1ng CSS Grid\n\nTrong thi\u1ebft k\u1ebf lu\u1ed3ng mua s\u1eafm Th\u01b0\u01a1ng m\u1ea1i \u0111i\u1ec7n t\u1eed, m\u00e0n h\u00ecnh Gi\u1ecf h\u00e0ng (`Cart`) v\u00e0 Thanh to\u00e1n (`Checkout`) lu\u00f4n t\u00e1ch bi\u1ec7t r\u00f5 r\u00e0ng gi\u1eefa **Danh s\u00e1ch m\u1eb7t h\u00e0ng c\u00f3 th\u1ec3 cu\u1ed9n** v\u00e0 **Khung t\u1ed5ng k\u1ebft \u0111\u01a1n h\u00e0ng (`Order Summary \/ Checkout Bar`)** lu\u00f4n n\u1eb1m trong t\u1ea7m m\u1eaft ng\u01b0\u1eddi d\u00f9ng:\n\n![image.png](https:\/\/images.viblo.asia\/f19f96cb-1fd2-41b5-afd5-66dfe8b28282.png)\n\nTr\u00ean giao di\u1ec7n Desktop (`1200px`) c\u1ee7a Shopee, \u0111\u1ec3 gi\u1eef cho **Thanh Ti\u00eau \u0110\u1ec1 C\u1ed9t** (`.cart-table-header`) v\u00e0 **T\u1eebng D\u00f2ng S\u1ea3n Ph\u1ea9m** (`.cart-item-row`) lu\u00f4n kh\u1edbp kh\u00edt t\u1eebng milimet d\u00f9 ch\u00fang n\u1eb1m \u1edf c\u00e1c th\u1ebb `<div>` ho\u00e0n to\u00e0n \u0111\u1ed9c l\u1eadp, ch\u00fang ta \u00e1p d\u1ee5ng chung **1 c\u00f4ng th\u1ee9c chia 6 c\u1ed9t b\u1eb1ng CSS Grid**:\n\nPlaintext\n\n```\n|<-------------------------------- KHUNG 1200PX (PADDING TR\u00c1I\/PH\u1ea2I 20PX) -------------------------------->|\n+------+------------------------------------------+-------------+---------------+-------------+-----------+\n| [\u2713]  | S\u1ea3n Ph\u1ea9m (\u1ea2nh 80px + T\u00ean + Ph\u00e2n lo\u1ea1i)    |   \u0110\u01a1n Gi\u00e1   |   S\u1ed1 L\u01b0\u1ee3ng    |   S\u1ed1 Ti\u1ec1n   | Thao T\u00e1c  |\n| 50px |              flex \/ 1fr                  |    150px    |     150px     |    140px    |   120px   |\n+------+------------------------------------------+-------------+---------------+-------------+-----------+\n```\n\n| Th\u1ee9 t\u1ef1 c\u1ed9t | K\u00edch th\u01b0\u1edbc khai b\u00e1o trong `grid-template-columns` | N\u1ed9i dung hi\u1ec3n th\u1ecb & C\u00e1ch c\u0103n l\u1ec1 |\n| :--- | :--- | :--- |\n| **C\u1ed9t 1** (`5rem`) | `50px` c\u1ed1 \u0111\u1ecbnh | Ch\u1ee9a \u00f4 Custom Checkbox m\u00e0u cam \u0111\u1ec3 t\u00edch ch\u1ecdn mua s\u1ea3n ph\u1ea9m. |\n| **C\u1ed9t 2** (`1fr`) | T\u1ef1 \u0111\u1ed9ng co gi\u00e3n chi\u1ebfm to\u00e0n b\u1ed9 ph\u1ea7n c\u00f2n l\u1ea1i ($\\approx 550\\text{px}$) | Ch\u1ee9a \u1ea2nh s\u1ea3n ph\u1ea9m (80x80px), T\u00ean s\u1ea3n ph\u1ea9m (2 d\u00f2ng) v\u00e0 N\u00fat \u0111\u1ed5i Ph\u00e2n lo\u1ea1i h\u00e0ng. |\n| **C\u1ed9t 3** (`15rem`) | `150px` c\u1ed1 \u0111\u1ecbnh (`text-align: center`) | Hi\u1ec3n th\u1ecb Gi\u00e1 g\u1ed1c g\u1ea1ch ngang v\u00e0 \u0110\u01a1n gi\u00e1 khuy\u1ebfn m\u00e3i hi\u1ec7n t\u1ea1i. |\n| **C\u1ed9t 4** (`15rem`) | `150px` c\u1ed1 \u0111\u1ecbnh (`text-align: center`) | Ch\u1ee9a b\u1ed9 t\u0103ng gi\u1ea3m s\u1ed1 l\u01b0\u1ee3ng `[-] [ 1 ] [+]` (T\u00e1i s\u1eed d\u1ee5ng t\u1eeb B\u00e0i 20!). |\n| **C\u1ed9t 5** (`14rem`) | `140px` c\u1ed1 \u0111\u1ecbnh (`text-align: center`) | Hi\u1ec3n th\u1ecb Th\u00e0nh ti\u1ec1n m\u00e0u cam (\u0110\u01a1n gi\u00e1 $\\times$ S\u1ed1 l\u01b0\u1ee3ng). |\n| **C\u1ed9t 6** (`12rem`) | `120px` c\u1ed1 \u0111\u1ecbnh (`text-align: center`) | Ch\u1ee9a n\u00fat X\u00f3a v\u00e0 link T\u00ecm s\u1ea3n ph\u1ea9m t\u01b0\u01a1ng t\u1ef1. |\n\n> **C\u00f4ng th\u1ee9c v\u00e0ng d\u00f9ng chung:**\n> \n> Ch\u1ec9 c\u1ea7n g\u00e1n `grid-template-columns: 5rem 1fr 15rem 15rem 14rem 12rem;` cho c\u1ea3 `.cart-table-header` l\u1eabn `.cart-item-row`, c\u1ea3 6 c\u1ed9t t\u1eeb tr\u00ean xu\u1ed1ng d\u01b0\u1edbi s\u1ebd th\u1eb3ng t\u1eafp nh\u01b0 m\u1ed9t b\u1ea3ng Excel m\u00e0 v\u1eabn gi\u1eef \u0111\u01b0\u1ee3c c\u00e1c kho\u1ea3ng h\u1edf x\u00e1m `#f5f5f5` t\u00e1ch bi\u1ec7t gi\u1eefa t\u1eebng Gian h\u00e0ng!\n\n## 2. Nguy\u00ean l\u00fd `position: sticky; bottom: 0` \u2014 Ghim Thanh Thanh To\u00e1n \u1edf \u0110\u00e1y M\u00e0n H\u00ecnh!\n\n\u1ede B\u00e0i 08 (Sticky Header) v\u00e0 B\u00e0i 14 (Sticky G\u1ee3i \u00dd H\u00f4m Nay), ch\u00fang ta \u0111\u00e3 d\u00f9ng `position: sticky` k\u1ebft h\u1ee3p v\u1edbi **`top: 0`** \u0111\u1ec3 ghim ph\u1ea7n t\u1eed l\u00ean **\u0111\u1ec9nh m\u00e0n h\u00ecnh** khi cu\u1ed9n xu\u1ed1ng.\n\nNh\u01b0ng v\u1edbi **Thanh T\u1ed5ng Ti\u1ec1n Gi\u1ecf H\u00e0ng (`.cart-checkout-bar`)**, quy lu\u1eadt ho\u1ea1t \u0111\u1ed9ng l\u1ea1i ng\u01b0\u1ee3c l\u1ea1i ho\u00e0n to\u00e0n:\n\n-   N\u00f3 \u0111\u01b0\u1ee3c \u0111\u1eb7t \u1edf **cu\u1ed1i c\u00f9ng** c\u1ee7a khu v\u1ef1c Gi\u1ecf h\u00e0ng trong file HTML.\n    \n-   Khi danh s\u00e1ch gi\u1ecf h\u00e0ng qu\u00e1 d\u00e0i (c\u00f2n khu\u1ea5t \u1edf ph\u00eda d\u01b0\u1edbi \u0111\u00e1y m\u00e0n h\u00ecnh), ch\u00fang ta mu\u1ed1n thanh T\u1ed5ng ti\u1ec1n n\u00e0y **n\u1ed5i \u0111\u00e8 l\u00ean v\u00e0 b\u00e1m ch\u1eb7t \u1edf m\u00e9p d\u01b0\u1edbi c\u00f9ng c\u1ee7a c\u1eeda s\u1ed5 tr\u00ecnh duy\u1ec7t (`bottom: 0`)**!\n    \n-   Ngay khi ng\u01b0\u1eddi d\u00f9ng cu\u1ed9n chu\u1ed9t xu\u1ed1ng t\u1edbi s\u1ea3n ph\u1ea9m cu\u1ed1i c\u00f9ng c\u1ee7a gi\u1ecf h\u00e0ng, thanh T\u1ed5ng ti\u1ec1n s\u1ebd t\u1ef1 \u0111\u1ed9ng \"h\u1ea1 c\u00e1nh\" n\u1eb1m y\u00ean v\u1ecb ngay d\u01b0\u1edbi \u0111\u00e1y gi\u1ecf h\u00e0ng v\u00e0 nh\u01b0\u1eddng ch\u1ed7 cho ph\u1ea7n Footer b\u00ean d\u01b0\u1edbi \u0111i l\u00ean!\n    \n\n\u0110\u00f3 ch\u00ednh l\u00e0 s\u1ee9c m\u1ea1nh \u0111\u1ed9c nh\u1ea5t v\u00f4 nh\u1ecb c\u1ee7a **`position: sticky; bottom: 0;`**:\n\n1.  L\u00fac ch\u01b0a cu\u1ed9n t\u1edbi \u0111\u00e1y `.cart-section`: N\u00f3 ho\u1ea1t \u0111\u1ed9ng nh\u01b0 `position: fixed; bottom: 0` (lu\u00f4n b\u00e1m s\u00e1t c\u1ea1nh d\u01b0\u1edbi m\u00e0n h\u00ecnh k\u00e8m b\u00f3ng \u0111\u1ed5 h\u1eaft ng\u01b0\u1ee3c l\u00ean tr\u00ean `box-shadow: 0 -2px 10px rgba(0,0,0,0.06)`).\n    \n2.  L\u00fac \u0111\u00e3 cu\u1ed9n h\u1ebft `.cart-section`: N\u00f3 tr\u1ea3 l\u1ea1i v\u1ecb tr\u00ed t\u1ef1 nhi\u00ean \u1edf cu\u1ed1i kh\u1ed1i gi\u1ecf h\u00e0ng, kh\u00f4ng bao gi\u1edd \u0111\u00e8 che m\u1ea5t ph\u1ea7n Mega Footer!\n    \n\n## 3. Th\u1ef1c h\u00e0nh: Code Trang Gi\u1ecf H\u00e0ng \u0110a Gian H\u00e0ng & Sticky Checkout Bar\n\n### B\u01b0\u1edbc 1: B\u1ed5 sung HTML Khu V\u1ef1c Gi\u1ecf H\u00e0ng (`.cart-section`) v\u00e0o `index.html`\n\nH\u00e3y m\u1edf file `index.html`, t\u00ecm \u0111\u1ebfn b\u00ean trong `<div class=\"shopee-container grid-container\">` (b\u1ea1n c\u00f3 th\u1ec3 \u0111\u1eb7t ngay b\u00ean d\u01b0\u1edbi `<\/section>` c\u1ee7a Ch\u01b0\u01a1ng 7 - Trang Chi Ti\u1ebft S\u1ea3n Ph\u1ea9m) v\u00e0 ch\u00e8n to\u00e0n b\u1ed9 c\u1ea5u tr\u00fac **Ch\u01b0\u01a1ng 8: Trang Gi\u1ecf H\u00e0ng Shopee** d\u01b0\u1edbi \u0111\u00e2y:\n\nHTML\n\n```\n        <!-- ================================================================= -->\n        <!-- CH\u01af\u01a0NG 8 (B\u00c0I 23): GIAO DI\u1ec6N TRANG GI\u1ece H\u00c0NG & STICKY CHECKOUT BAR -->\n        <!-- ================================================================= -->\n        <section class=\"cart-section\">\n            \n            <!-- 1. D\u1ea3i th\u00f4ng b\u00e1o Freeship m\u00e0u v\u00e0ng kem tr\u00ean \u0111\u1ec9nh Gi\u1ecf h\u00e0ng -->\n            <div class=\"cart-freeship-banner\">\n                <i class=\"fa-solid fa-truck-fast cart-freeship-icon\"><\/i>\n                <span>Nh\u1ea5n v\u00e0o m\u1ee5c M\u00e3 gi\u1ea3m gi\u00e1 \u1edf cu\u1ed1i trang \u0111\u1ec3 h\u01b0\u1edfng mi\u1ec5n ph\u00ed v\u1eadn chuy\u1ec3n b\u1ea1n nh\u00e9!<\/span>\n            <\/div>\n\n            <!-- 2. Thanh Ti\u00eau \u0110\u1ec1 6 C\u1ed9t (Cart Table Header) -->\n            <div class=\"cart-table-header\">\n                <div class=\"cart-col-checkbox\">\n                    <label class=\"filter-checkbox\">\n                        <input type=\"checkbox\" class=\"filter-checkbox__input\" checked>\n                        <span class=\"filter-checkbox__box\"><i class=\"fa-solid fa-check\"><\/i><\/span>\n                    <\/label>\n                <\/div>\n                <div class=\"cart-col-product\">S\u1ea3n Ph\u1ea9m<\/div>\n                <div class=\"cart-col-center\">\u0110\u01a1n Gi\u00e1<\/div>\n                <div class=\"cart-col-center\">S\u1ed1 L\u01b0\u1ee3ng<\/div>\n                <div class=\"cart-col-center\">S\u1ed1 Ti\u1ec1n<\/div>\n                <div class=\"cart-col-center\">Thao T\u00e1c<\/div>\n            <\/div>\n\n            <!-- 3. KH\u1ed0I GIAN H\u00c0NG S\u1ed0 1: AULA OFFICIAL STORE VN -->\n            <div class=\"cart-shop-group\">\n                \n                <!-- Header c\u1ee7a Gian h\u00e0ng trong Gi\u1ecf -->\n                <div class=\"cart-shop-header\">\n                    <div class=\"cart-col-checkbox\">\n                        <label class=\"filter-checkbox\">\n                            <input type=\"checkbox\" class=\"filter-checkbox__input\" checked>\n                            <span class=\"filter-checkbox__box\"><i class=\"fa-solid fa-check\"><\/i><\/span>\n                        <\/label>\n                    <\/div>\n                    <span class=\"cart-shop-badge\">Y\u00eau th\u00edch+<\/span>\n                    <a href=\"#\" class=\"cart-shop-name\">AULA Official Store VN<\/a>\n                    <button type=\"button\" class=\"cart-shop-chat-btn\" title=\"Chat v\u1edbi Shop\">\n                        <i class=\"fa-solid fa-comments\"><\/i>\n                    <\/button>\n                <\/div>\n\n                <!-- D\u1ea3i khuy\u1ebfn m\u00e3i Mua K\u00e8m Deal S\u1ed1c b\u00ean trong Shop -->\n                <div class=\"cart-bundle-banner\">\n                    <span class=\"bundle-tag\">Mua K\u00e8m Deal S\u1ed1c<\/span>\n                    <span class=\"bundle-text\">Mua th\u00eam 1 s\u1ea3n ph\u1ea9m \u0111\u1ec3 \u0111\u01b0\u1ee3c gi\u1ea3m th\u00eam \u20ab20.000<\/span>\n                    <a href=\"#\" class=\"bundle-link\">Th\u00eam &gt;<\/a>\n                <\/div>\n\n                <!-- D\u00f2ng S\u1ea3n Ph\u1ea9m 1 c\u1ee7a Shop 1 -->\n                <div class=\"cart-item-row\">\n                    <!-- C\u1ed9t 1: Checkbox -->\n                    <div class=\"cart-col-checkbox\">\n                        <label class=\"filter-checkbox\">\n                            <input type=\"checkbox\" class=\"filter-checkbox__input\" checked>\n                            <span class=\"filter-checkbox__box\"><i class=\"fa-solid fa-check\"><\/i><\/span>\n                        <\/label>\n                    <\/div>\n\n                    <!-- C\u1ed9t 2: \u1ea2nh + T\u00ean s\u1ea3n ph\u1ea9m + H\u1ed9p \u0111\u1ed5i Ph\u00e2n lo\u1ea1i h\u00e0ng -->\n                    <div class=\"cart-item-overview\">\n                        <a href=\"#\" class=\"cart-item-img-wrap\">\n                            <img src=\"https:\/\/placehold.co\/160x160\/ee4d2d\/ffffff?text=AULA+F75+Pro\" alt=\"B\u00e0n ph\u00edm AULA F75 Pro\" class=\"cart-item-img\">\n                        <\/a>\n                        <div class=\"cart-item-details\">\n                            <a href=\"#\" class=\"cart-item-name\">B\u00e0n ph\u00edm c\u01a1 kh\u00f4ng d\u00e2y AULA F75 Pro 3 Mode M\u1ea1ch Xu\u00f4i Hotswap LED RGB<\/a>\n                            <div class=\"cart-item-return-tag\">\n                                <i class=\"fa-solid fa-rotate-left\"><\/i> \u0110\u1ed5i \u00fd mi\u1ec5n ph\u00ed 15 ng\u00e0y\n                            <\/div>\n                        <\/div>\n                        <!-- N\u00fat ch\u1ecdn l\u1ea1i Ph\u00e2n lo\u1ea1i h\u00e0ng ngay trong Gi\u1ecf -->\n                        <div class=\"cart-item-variation\">\n                            <div class=\"cart-variation-label\">\n                                Ph\u00e2n lo\u1ea1i h\u00e0ng: <i class=\"fa-solid fa-caret-down\"><\/i>\n                            <\/div>\n                            <div class=\"cart-variation-value\">Tr\u1eafng Xanh (Ice Vein), Reaper Switch<\/div>\n                        <\/div>\n                    <\/div>\n\n                    <!-- C\u1ed9t 3: \u0110\u01a1n Gi\u00e1 -->\n                    <div class=\"cart-col-center cart-item-price\">\n                        <span class=\"cart-price-old\">\u20ab1.390.000<\/span>\n                        <span class=\"cart-price-current\">\u20ab850.000<\/span>\n                    <\/div>\n\n                    <!-- C\u1ed9t 4: B\u1ed9 t\u0103ng gi\u1ea3m S\u1ed1 L\u01b0\u1ee3ng (T\u00e1i s\u1eed d\u1ee5ng class t\u1eeb B\u00e0i 20) -->\n                    <div class=\"cart-col-center\">\n                        <div class=\"quantity-stepper\">\n                            <button type=\"button\" class=\"quantity-btn\"><i class=\"fa-solid fa-minus\"><\/i><\/button>\n                            <input type=\"text\" class=\"quantity-input\" value=\"1\" aria-label=\"S\u1ed1 l\u01b0\u1ee3ng\">\n                            <button type=\"button\" class=\"quantity-btn\"><i class=\"fa-solid fa-plus\"><\/i><\/button>\n                        <\/div>\n                    <\/div>\n\n                    <!-- C\u1ed9t 5: S\u1ed1 Ti\u1ec1n (Th\u00e0nh ti\u1ec1n m\u00e0u cam) -->\n                    <div class=\"cart-col-center\">\n                        <span class=\"cart-item-total\">\u20ab850.000<\/span>\n                    <\/div>\n\n                    <!-- C\u1ed9t 6: Thao T\u00e1c (X\u00f3a \/ T\u00ecm t\u01b0\u01a1ng t\u1ef1) -->\n                    <div class=\"cart-col-center cart-item-actions\">\n                        <button type=\"button\" class=\"cart-action-delete\">X\u00f3a<\/button>\n                        <a href=\"#\" class=\"cart-action-similar\">\n                            T\u00ecm s\u1ea3n ph\u1ea9m t\u01b0\u01a1ng t\u1ef1 <i class=\"fa-solid fa-caret-down\"><\/i>\n                        <\/a>\n                    <\/div>\n                <\/div>\n\n                <!-- Ch\u00e2n Gian h\u00e0ng: Voucher c\u1ee7a Shop & Freeship -->\n                <div class=\"cart-shop-footer\">\n                    <div class=\"cart-shop-footer-row\">\n                        <i class=\"fa-solid fa-ticket cart-voucher-icon\"><\/i>\n                        <span>\u0110\u00e3 gi\u1ea3m \u20ab50.000 t\u1eeb Voucher c\u1ee7a Shop<\/span>\n                        <a href=\"#\" class=\"cart-footer-action-link\">Th\u00eam m\u00e3 gi\u1ea3m gi\u00e1 c\u1ee7a Shop<\/a>\n                    <\/div>\n                    <div class=\"cart-shop-footer-row\">\n                        <i class=\"fa-solid fa-truck-fast cart-shipping-icon\"><\/i>\n                        <span>Gi\u1ea3m \u20ab30.000 ph\u00ed v\u1eadn chuy\u1ec3n \u0111\u01a1n t\u1ed1i thi\u1ec3u \u20ab0<\/span>\n                        <a href=\"#\" class=\"cart-footer-action-link\">T\u00ecm hi\u1ec3u th\u00eam<\/a>\n                    <\/div>\n                <\/div>\n\n            <\/div>\n\n            <!-- 4. KH\u1ed0I GIAN H\u00c0NG S\u1ed0 2: LG OFFICIAL FLAGSHIP STORE -->\n            <div class=\"cart-shop-group\">\n                \n                <div class=\"cart-shop-header\">\n                    <div class=\"cart-col-checkbox\">\n                        <label class=\"filter-checkbox\">\n                            <input type=\"checkbox\" class=\"filter-checkbox__input\" checked>\n                            <span class=\"filter-checkbox__box\"><i class=\"fa-solid fa-check\"><\/i><\/span>\n                        <\/label>\n                    <\/div>\n                    <span class=\"cart-shop-badge cart-shop-badge--mall\">Mall<\/span>\n                    <a href=\"#\" class=\"cart-shop-name\">LG Official Flagship Store<\/a>\n                    <button type=\"button\" class=\"cart-shop-chat-btn\" title=\"Chat v\u1edbi Shop\">\n                        <i class=\"fa-solid fa-comments\"><\/i>\n                    <\/button>\n                <\/div>\n\n                <!-- D\u00f2ng S\u1ea3n Ph\u1ea9m 1 c\u1ee7a Shop 2 -->\n                <div class=\"cart-item-row\">\n                    <div class=\"cart-col-checkbox\">\n                        <label class=\"filter-checkbox\">\n                            <input type=\"checkbox\" class=\"filter-checkbox__input\" checked>\n                            <span class=\"filter-checkbox__box\"><i class=\"fa-solid fa-check\"><\/i><\/span>\n                        <\/label>\n                    <\/div>\n\n                    <div class=\"cart-item-overview\">\n                        <a href=\"#\" class=\"cart-item-img-wrap\">\n                            <img src=\"https:\/\/placehold.co\/160x160\/d0011b\/ffffff?text=LG+UltraGear+27\" alt=\"M\u00e0n h\u00ecnh LG UltraGear\" class=\"cart-item-img\">\n                        <\/a>\n                        <div class=\"cart-item-details\">\n                            <a href=\"#\" class=\"cart-item-name\">M\u00e0n h\u00ecnh Gaming LG UltraGear 27GS60F-B 27 inch FHD IPS 180Hz 1ms HDR10<\/a>\n                            <div class=\"cart-item-return-tag\">\n                                <i class=\"fa-solid fa-shield-heart\"><\/i> B\u1ea3o h\u00e0nh ch\u00ednh h\u00e3ng 24 th\u00e1ng\n                            <\/div>\n                        <\/div>\n                        <div class=\"cart-item-variation\">\n                            <div class=\"cart-variation-label\">\n                                Ph\u00e2n lo\u1ea1i h\u00e0ng: <i class=\"fa-solid fa-caret-down\"><\/i>\n                            <\/div>\n                            <div class=\"cart-variation-value\">27 Inch \/ IPS \/ 180Hz<\/div>\n                        <\/div>\n                    <\/div>\n\n                    <div class=\"cart-col-center cart-item-price\">\n                        <span class=\"cart-price-old\">\u20ab4.890.000<\/span>\n                        <span class=\"cart-price-current\">\u20ab3.890.000<\/span>\n                    <\/div>\n\n                    <div class=\"cart-col-center\">\n                        <div class=\"quantity-stepper\">\n                            <button type=\"button\" class=\"quantity-btn\"><i class=\"fa-solid fa-minus\"><\/i><\/button>\n                            <input type=\"text\" class=\"quantity-input\" value=\"1\" aria-label=\"S\u1ed1 l\u01b0\u1ee3ng\">\n                            <button type=\"button\" class=\"quantity-btn\"><i class=\"fa-solid fa-plus\"><\/i><\/button>\n                        <\/div>\n                    <\/div>\n\n                    <div class=\"cart-col-center\">\n                        <span class=\"cart-item-total\">\u20ab3.890.000<\/span>\n                    <\/div>\n\n                    <div class=\"cart-col-center cart-item-actions\">\n                        <button type=\"button\" class=\"cart-action-delete\">X\u00f3a<\/button>\n                        <a href=\"#\" class=\"cart-action-similar\">\n                            T\u00ecm s\u1ea3n ph\u1ea9m t\u01b0\u01a1ng t\u1ef1 <i class=\"fa-solid fa-caret-down\"><\/i>\n                        <\/a>\n                    <\/div>\n                <\/div>\n\n            <\/div>\n\n            <!-- ================================================================= -->\n            <!-- 5. THANH THANH TO\u00c1N B\u00c1M D\u00cdNH \u0110\u00c1Y M\u00c0N H\u00ccNH (STICKY CHECKOUT BAR)   -->\n            <!-- ================================================================= -->\n            <div class=\"cart-checkout-bar\">\n                \n                <!-- T\u1ea7ng 1 c\u1ee7a Checkout Bar: Ch\u1ecdn Shopee Voucher -->\n                <div class=\"checkout-bar__voucher-row\">\n                    <div class=\"checkout-voucher-left\">\n                        <i class=\"fa-solid fa-ticket checkout-voucher-icon\"><\/i>\n                        <span>Shopee Voucher<\/span>\n                    <\/div>\n                    <a href=\"#\" class=\"checkout-voucher-link\">Ch\u1ecdn ho\u1eb7c nh\u1eadp m\u00e3<\/a>\n                <\/div>\n\n                <!-- T\u1ea7ng 2 c\u1ee7a Checkout Bar: D\u00f9ng Shopee Xu -->\n                <div class=\"checkout-bar__coin-row\">\n                    <label class=\"filter-checkbox checkout-coin-label\">\n                        <input type=\"checkbox\" class=\"filter-checkbox__input\" checked>\n                        <span class=\"filter-checkbox__box\"><i class=\"fa-solid fa-check\"><\/i><\/span>\n                        <i class=\"fa-solid fa-coins checkout-coin-icon\"><\/i>\n                        <span class=\"checkout-coin-title\">Shopee Xu<\/span>\n                        <span class=\"checkout-coin-sub\">\u0110\u00e3 d\u00f9ng 20.000 Shopee Xu<\/span>\n                    <\/label>\n                    <span class=\"checkout-coin-discount\">-\u20ab20.000<\/span>\n                <\/div>\n\n                <!-- T\u1ea7ng 3 (T\u1ea7ng ch\u00ednh): Ch\u1ecdn t\u1ea5t c\u1ea3 + T\u1ed5ng thanh to\u00e1n + N\u00fat MUA H\u00c0NG -->\n                <div class=\"checkout-bar__main-row\">\n                    \n                    <!-- Nh\u00e1nh tr\u00e1i: Ch\u1ecdn t\u1ea5t c\u1ea3 \/ X\u00f3a \/ L\u01b0u v\u00e0o m\u1ee5c \u0110\u00e3 th\u00edch -->\n                    <div class=\"checkout-main__left\">\n                        <label class=\"filter-checkbox checkout-select-all\">\n                            <input type=\"checkbox\" class=\"filter-checkbox__input\" checked>\n                            <span class=\"filter-checkbox__box\"><i class=\"fa-solid fa-check\"><\/i><\/span>\n                            <span class=\"checkout-action-btn\">Ch\u1ecdn T\u1ea5t C\u1ea3 (2)<\/span>\n                        <\/label>\n                        <button type=\"button\" class=\"checkout-action-btn\">X\u00f3a<\/button>\n                        <button type=\"button\" class=\"checkout-action-btn checkout-action-btn--primary\">L\u01b0u v\u00e0o m\u1ee5c \u0110\u00e3 th\u00edch<\/button>\n                    <\/div>\n\n                    <!-- Nh\u00e1nh ph\u1ea3i: T\u1ed5ng thanh to\u00e1n & N\u00fat MUA H\u00c0NG c\u1ee1 l\u1edbn -->\n                    <div class=\"checkout-main__right\">\n                        <div class=\"checkout-total-info\">\n                            <div class=\"checkout-total-top\">\n                                <span class=\"checkout-total-label\">T\u1ed5ng thanh to\u00e1n (2 S\u1ea3n ph\u1ea9m):<\/span>\n                                <span class=\"checkout-total-price\">\u20ab4.670.000<\/span>\n                            <\/div>\n                            <div class=\"checkout-total-bottom\">\n                                <span>Ti\u1ebft ki\u1ec7m<\/span>\n                                <span class=\"checkout-saved-amount\">\u20ab1.560.000<\/span>\n                            <\/div>\n                        <\/div>\n\n                        <button type=\"button\" class=\"btn btn--primary checkout-submit-btn\">\n                            Mua H\u00e0ng\n                        <\/button>\n                    <\/div>\n\n                <\/div>\n\n            <\/div>\n\n        <\/section>\n\n```\n\n### B\u01b0\u1edbc 2: Vi\u1ebft CSS cho Gi\u1ecf H\u00e0ng 6 C\u1ed9t & Sticky Bottom Bar trong `.\/assets\/css\/main.css`\n\nH\u00e3y m\u1edf file `main.css`, cu\u1ed9n xu\u1ed1ng d\u01b0\u1edbi c\u00f9ng v\u00e0 b\u1ed5 sung \u0111o\u1ea1n code CSS c\u1ee7a Ch\u01b0\u01a1ng 8 d\u01b0\u1edbi \u0111\u00e2y:\n\nCSS\n\n```\n\/* B\u1ed5 sung v\u00e0o cu\u1ed1i file .\/assets\/css\/main.css *\/\n\n\/* --- CH\u01af\u01a0NG 8 (B\u00c0I 23): GIAO DI\u1ec6N TRANG GI\u1ece H\u00c0NG & STICKY CHECKOUT BAR --- *\/\n\n.cart-section {\n    margin-top: 2rem;\n    margin-bottom: 4rem;\n}\n\n\/* 1. D\u1ea3i th\u00f4ng b\u00e1o Freeship m\u00e0u v\u00e0ng kem *\/\n.cart-freeship-banner {\n    background-color: #fffefb;\n    border: 1px solid rgba(224, 168, 0, 0.4);\n    padding: 1.2rem 1.6rem;\n    border-radius: 2px;\n    margin-bottom: 1.2rem;\n    display: flex;\n    align-items: center;\n    gap: 1rem;\n    font-size: 1.4rem;\n    color: var(--text-primary);\n}\n\n.cart-freeship-icon {\n    color: #00bfa5;\n    font-size: 1.8rem;\n}\n\n\/* --- C\u00d4NG TH\u1ee8C CHIA 6 C\u1ed8T \u0110\u1ed2NG B\u1ed8 CHO C\u1ea2 HEADER L\u1eaaN T\u1eeaNG D\u00d2NG S\u1ea2N PH\u1ea8M --- *\/\n.cart-table-header,\n.cart-item-row {\n    display: grid;\n    \/* C\u1ed9t 1 (Checkbox: 50px) | C\u1ed9t 2 (S\u1ea3n ph\u1ea9m: 1fr) | C\u1ed9t 3,4,5,6 c\u1ed1 \u0111\u1ecbnh chi\u1ec1u r\u1ed9ng *\/\n    grid-template-columns: 5rem 1fr 15rem 15rem 14rem 12rem;\n    align-items: center;\n    padding: 0 2rem;\n}\n\n\/* 2. Thanh Ti\u00eau \u0110\u1ec1 6 C\u1ed9t (.cart-table-header) *\/\n.cart-table-header {\n    height: 5.5rem;\n    background-color: var(--white-color);\n    border-radius: 2px;\n    box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05);\n    margin-bottom: 1.2rem;\n    font-size: 1.4rem;\n    color: #888888;\n}\n\n.cart-col-product {\n    color: var(--text-primary);\n}\n\n.cart-col-center {\n    text-align: center;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n}\n\n\/* 3. Kh\u1ed1i Gian H\u00e0ng trong Gi\u1ecf (.cart-shop-group) *\/\n.cart-shop-group {\n    background-color: var(--white-color);\n    border-radius: 2px;\n    box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05);\n    margin-bottom: 1.5rem;\n}\n\n.cart-shop-header {\n    height: 6rem;\n    padding: 0 2rem;\n    border-bottom: 1px solid rgba(0, 0, 0, 0.09);\n    display: flex;\n    align-items: center;\n}\n\n.cart-shop-header .cart-col-checkbox {\n    width: 5rem;                    \/* Kh\u1edbp \u0111\u00fang 50px v\u1edbi c\u1ed9t 1 c\u1ee7a Grid b\u00ean d\u01b0\u1edbi! *\/\n    flex-shrink: 0;\n}\n\n.cart-shop-badge {\n    background-color: var(--shopee-primary);\n    color: var(--white-color);\n    font-size: 1.1rem;\n    font-weight: 500;\n    padding: 0.2rem 0.5rem;\n    border-radius: 2px;\n    margin-right: 0.8rem;\n}\n\n.cart-shop-badge--mall {\n    background-color: var(--shopee-mall-red);\n}\n\n.cart-shop-name {\n    font-size: 1.4rem;\n    font-weight: 500;\n    color: var(--text-primary);\n    margin-right: 0.8rem;\n}\n\n.cart-shop-chat-btn {\n    color: var(--shopee-primary);\n    font-size: 1.6rem;\n}\n\n\/* D\u1ea3i Mua K\u00e8m Deal S\u1ed1c b\u00ean trong Shop *\/\n.cart-bundle-banner {\n    background-color: #fff3f0;\n    padding: 1rem 2rem 1rem 7rem;   \/* Th\u1ee5t l\u1ec1 tr\u00e1i 70px (20px padding + 50px c\u1ed9t checkbox) \u0111\u1ec3 th\u1eb3ng h\u00e0ng v\u1edbi \u1ea3nh! *\/\n    display: flex;\n    align-items: center;\n    gap: 1.2rem;\n    font-size: 1.3rem;\n}\n\n.bundle-tag {\n    color: var(--shopee-primary);\n    border: 1px solid var(--shopee-primary);\n    padding: 0.1rem 0.5rem;\n    border-radius: 2px;\n    font-size: 1.1rem;\n    background-color: var(--white-color);\n}\n\n.bundle-link {\n    color: var(--shopee-primary);\n    font-weight: 500;\n}\n\n\/* 4. D\u00f2ng S\u1ea3n Ph\u1ea9m trong Gi\u1ecf (.cart-item-row) *\/\n.cart-item-row {\n    padding-top: 2rem;\n    padding-bottom: 2rem;\n    border-bottom: 1px solid rgba(0, 0, 0, 0.09);\n}\n\n\/* C\u1ed9t 2: Chia th\u00e0nh \u1ea2nh (80px) + T\u00ean s\u1ea3n ph\u1ea9m (flex: 1) + Ph\u00e2n lo\u1ea1i h\u00e0ng (180px) *\/\n.cart-item-overview {\n    display: flex;\n    align-items: flex-start;\n    gap: 1.4rem;\n    padding-right: 2rem;\n}\n\n.cart-item-img-wrap {\n    width: 8rem;\n    height: 8rem;\n    flex-shrink: 0;\n    border: 1px solid rgba(0, 0, 0, 0.09);\n    border-radius: 2px;\n    overflow: hidden;\n}\n\n.cart-item-img {\n    width: 100%;\n    height: 100%;\n    object-fit: cover;\n}\n\n.cart-item-details {\n    flex: 1;\n}\n\n.cart-item-name {\n    font-size: 1.4rem;\n    line-height: 1.8rem;\n    color: var(--text-primary);\n    margin-bottom: 0.8rem;\n    display: -webkit-box;\n    -webkit-line-clamp: 2;\n    -webkit-box-orient: vertical;\n    overflow: hidden;\n}\n\n.cart-item-return-tag {\n    display: inline-flex;\n    align-items: center;\n    gap: 0.4rem;\n    font-size: 1.2rem;\n    color: var(--shopee-primary);\n    border: 1px solid var(--shopee-primary);\n    padding: 0.1rem 0.5rem;\n    border-radius: 2px;\n}\n\n\/* H\u1ed9p ch\u1ecdn l\u1ea1i Ph\u00e2n lo\u1ea1i h\u00e0ng ngay trong d\u00f2ng s\u1ea3n ph\u1ea9m *\/\n.cart-item-variation {\n    width: 18rem;\n    flex-shrink: 0;\n    color: var(--text-secondary);\n    font-size: 1.3rem;\n    line-height: 1.7rem;\n    cursor: pointer;\n    padding: 0.4rem 0.8rem;\n    border-radius: 2px;\n    transition: background-color 0.15s ease;\n}\n\n.cart-item-variation:hover {\n    background-color: rgba(0, 0, 0, 0.03);\n}\n\n.cart-variation-label {\n    display: flex;\n    align-items: center;\n    gap: 0.6rem;\n    margin-bottom: 0.2rem;\n}\n\n\/* C\u1ed9t 3, 5, 6: \u0110\u01a1n gi\u00e1, Th\u00e0nh ti\u1ec1n & Thao t\u00e1c *\/\n.cart-item-price {\n    flex-direction: row;\n    gap: 0.8rem;\n    font-size: 1.4rem;\n}\n\n.cart-price-old {\n    color: var(--text-secondary);\n    text-decoration: line-through;\n}\n\n.cart-price-current {\n    color: var(--text-primary);\n}\n\n.cart-item-total {\n    font-size: 1.5rem;\n    color: var(--shopee-primary);\n    font-weight: 500;\n}\n\n.cart-item-actions {\n    gap: 0.6rem;\n}\n\n.cart-action-delete {\n    font-size: 1.4rem;\n    color: var(--text-primary);\n}\n\n.cart-action-delete:hover {\n    color: var(--shopee-primary);\n}\n\n.cart-action-similar {\n    font-size: 1.25rem;\n    color: var(--shopee-primary);\n    line-height: 1.5rem;\n}\n\n\/* Ch\u00e2n Gian h\u00e0ng: Voucher & Freeship *\/\n.cart-shop-footer {\n    padding: 1.4rem 2rem 1.4rem 7rem;\n    display: flex;\n    flex-direction: column;\n    gap: 1.2rem;\n    font-size: 1.35rem;\n}\n\n.cart-shop-footer-row {\n    display: flex;\n    align-items: center;\n    gap: 1rem;\n}\n\n.cart-voucher-icon { color: var(--shopee-primary); font-size: 1.6rem; }\n.cart-shipping-icon { color: #00bfa5; font-size: 1.6rem; }\n\n.cart-footer-action-link {\n    color: #0055aa;\n    margin-left: 1.2rem;\n}\n\n\/* ================================================================= *\/\n\/* 5. B\u00cd THU\u1eacT: THANH THANH TO\u00c1N B\u00c1M D\u00cdNH \u0110\u00c1Y M\u00c0N H\u00ccNH (BOTTOM: 0)   *\/\n\/* ================================================================= *\/\n.cart-checkout-bar {\n    background-color: var(--white-color);\n    border-radius: 2px;\n    \/* B\u00f3ng \u0111\u1ed5 h\u1eaft ng\u01b0\u1ee3c l\u00ean ph\u00eda tr\u00ean (0 -4px 12px) t\u1ea1o c\u1ea3m gi\u00e1c n\u1ed5i \u0111\u00e8 l\u00ean danh s\u00e1ch gi\u1ecf h\u00e0ng! *\/\n    box-shadow: 0 -0.3rem 1rem 0 rgba(0, 0, 0, 0.06);\n    border: 1px solid rgba(0, 0, 0, 0.05);\n    \n    \/* K\u00cdCH HO\u1ea0T STICKY BOTTOM BAR *\/\n    position: sticky;\n    bottom: 0;\n    z-index: calc(var(--z-sticky) - 2); \/* T\u1ea7ng 98: N\u1ed5i tr\u00ean danh s\u00e1ch s\u1ea3n ph\u1ea9m khi cu\u1ed9n *\/\n}\n\n\/* T\u1ea7ng 1: Ch\u1ecdn Shopee Voucher (C\u0103n v\u1ec1 n\u1eeda b\u00ean ph\u1ea3i t\u1eeb c\u1ed9t 550px) *\/\n.checkout-bar__voucher-row {\n    padding: 1.2rem 2.4rem 1.2rem 55rem;\n    border-bottom: 1px dashed rgba(0, 0, 0, 0.09);\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    font-size: 1.4rem;\n}\n\n.checkout-voucher-left {\n    display: flex;\n    align-items: center;\n    gap: 0.8rem;\n    color: var(--text-primary);\n}\n\n.checkout-voucher-icon {\n    color: var(--shopee-primary);\n    font-size: 1.8rem;\n}\n\n.checkout-voucher-link {\n    color: #0055aa;\n}\n\n\/* T\u1ea7ng 2: D\u00f9ng Shopee Xu *\/\n.checkout-bar__coin-row {\n    padding: 1.2rem 2.4rem 1.2rem 55rem;\n    border-bottom: 1px dashed rgba(0, 0, 0, 0.09);\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    font-size: 1.4rem;\n}\n\n.checkout-coin-label {\n    align-items: center;\n}\n\n.checkout-coin-icon {\n    color: #f6a700;                 \/* M\u00e0u v\u00e0ng \u0111\u1ed3ng xu Shopee *\/\n    font-size: 1.6rem;\n}\n\n.checkout-coin-title {\n    color: var(--text-primary);\n    font-weight: 500;\n}\n\n.checkout-coin-sub {\n    color: var(--text-secondary);\n    font-size: 1.3rem;\n    margin-left: 0.6rem;\n}\n\n.checkout-coin-discount {\n    color: var(--shopee-primary);\n    font-weight: 500;\n}\n\n\/* T\u1ea7ng 3: Ch\u1ecdn t\u1ea5t c\u1ea3 + T\u1ed5ng thanh to\u00e1n + N\u00fat MUA H\u00c0NG *\/\n.checkout-bar__main-row {\n    padding: 1.6rem 2.4rem 1.6rem 2rem;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n}\n\n.checkout-main__left {\n    display: flex;\n    align-items: center;\n    gap: 2.4rem;\n}\n\n.checkout-select-all {\n    align-items: center;\n    gap: 1.6rem;\n}\n\n.checkout-action-btn {\n    font-size: 1.5rem;\n    color: var(--text-primary);\n    cursor: pointer;\n}\n\n.checkout-action-btn:hover {\n    color: var(--shopee-primary);\n}\n\n.checkout-action-btn--primary {\n    color: var(--shopee-primary);\n}\n\n.checkout-main__right {\n    display: flex;\n    align-items: center;\n    gap: 2rem;\n}\n\n.checkout-total-info {\n    text-align: right;\n}\n\n.checkout-total-top {\n    display: flex;\n    align-items: baseline;\n    justify-content: flex-end;\n    gap: 0.8rem;\n}\n\n.checkout-total-label {\n    font-size: 1.5rem;\n    color: var(--text-primary);\n}\n\n.checkout-total-price {\n    font-size: 2.4rem;              \/* T\u1ed5ng ti\u1ec1n to 24px m\u00e0u cam c\u1ef1c k\u1ef3 n\u1ed5i b\u1eadt *\/\n    color: var(--shopee-primary);\n    font-weight: 500;\n}\n\n.checkout-total-bottom {\n    font-size: 1.3rem;\n    color: var(--text-primary);\n    margin-top: 0.2rem;\n}\n\n.checkout-saved-amount {\n    color: var(--shopee-primary);\n    margin-left: 1.2rem;\n}\n\n.checkout-submit-btn {\n    min-width: 21rem;               \/* N\u00fat Mua H\u00e0ng r\u1ed9ng 210px, cao 40px chu\u1ea9n Shopee *\/\n    height: 4rem;\n    font-size: 1.5rem;\n    font-weight: 500;\n}\n\n```\n\n![image.png](https:\/\/images.viblo.asia\/cf8b4a14-bb46-4f5c-9881-93f95f8fa990.png)\n\n## 4. Gi\u1ea3i m\u00e3 d\u00f2ng code: 3 B\u00ed quy\u1ebft th\u1ef1c chi\u1ebfn \u0111\u1eaft gi\u00e1 trong B\u00e0i 23\n\n1.  **B\u00f3ng \u0111\u1ed5 h\u1eaft ng\u01b0\u1ee3c l\u00ean tr\u00ean (`box-shadow: 0 -0.3rem 1rem ...`) c\u1ee7a `.cart-checkout-bar`:**\n    \n    -   \u1ede B\u00e0i 08, thanh Sticky Header n\u1eb1m \u1edf \u0111\u1ec9nh m\u00e0n h\u00ecnh (`top: 0`), n\u00ean ch\u00fang ta \u0111\u1eb7t gi\u00e1 tr\u1ecb tr\u1ee5c Y c\u1ee7a b\u00f3ng \u0111\u1ed5 l\u00e0 s\u1ed1 d\u01b0\u01a1ng (`0 1px 4px`) \u0111\u1ec3 b\u00f3ng \u0111\u1ed5 h\u1eaft **xu\u1ed1ng d\u01b0\u1edbi**.\n        \n    -   Ng\u01b0\u1ee3c l\u1ea1i, thanh `.cart-checkout-bar` b\u00e1m \u1edf \u0111\u00e1y m\u00e0n h\u00ecnh (`bottom: 0`) v\u00e0 c\u00e1c d\u00f2ng s\u1ea3n ph\u1ea9m tr\u00f4i \u1edf **ph\u00eda tr\u00ean** n\u00f3. V\u00ec v\u1eady, ch\u00fang ta ph\u1ea3i \u0111\u1eb7t gi\u00e1 tr\u1ecb tr\u1ee5c Y l\u00e0 **s\u1ed1 \u00e2m (`-0.3rem` t\u1ee9c `-3px`)** \u0111\u1ec3 b\u00f3ng \u0111\u1ed5 h\u1eaft ng\u01b0\u1ee3c l\u00ean ph\u00eda tr\u00ean, t\u1ea1o ranh gi\u1edbi t\u00e1ch bi\u1ec7t r\u00f5 r\u00e0ng gi\u1eefa thanh thanh to\u00e1n v\u00e0 danh s\u00e1ch s\u1ea3n ph\u1ea9m b\u00ean d\u01b0\u1edbi!\n        \n2.  **Th\u1ee5t l\u1ec1 to\u00e1n h\u1ecdc `padding-left: 7rem` (`70px`) \u1edf `.cart-bundle-banner` v\u00e0 `.cart-shop-footer`:**\n    \n    -   B\u1ea1n c\u00f3 nh\u1eadn th\u1ea5y d\u00f2ng _\"Mua K\u00e8m Deal S\u1ed1c\"_ v\u00e0 d\u00f2ng _\"\u0110\u00e3 gi\u1ea3m \u20ab50.000 t\u1eeb Voucher c\u1ee7a Shop\"_ lu\u00f4n b\u1eaft \u0111\u1ea7u **th\u1eb3ng h\u00e0ng d\u1ecdc tuy\u1ec7t \u0111\u1ed1i v\u1edbi m\u00e9p tr\u00e1i c\u1ee7a t\u1ea5m \u1ea3nh s\u1ea3n ph\u1ea9m** (ch\u1ee9 kh\u00f4ng tr\u00e0n ra t\u1eadn d\u01b0\u1edbi \u00f4 Checkbox) kh\u00f4ng?\n        \n    -   L\u00fd do l\u00e0 m\u1ed7i d\u00f2ng s\u1ea3n ph\u1ea9m c\u00f3 `padding-left: 2rem` (`20px`) c\u1ed9ng v\u1edbi C\u1ed9t 1 Checkbox r\u1ed9ng `5rem` (`50px`) $= 7\\text{rem}$ (`70px`). Ch\u1ec9 c\u1ea7n \u0111\u1eb7t `padding-left: 7rem` cho d\u1ea3i khuy\u1ebfn m\u00e3i v\u00e0 ch\u00e2n Shop, m\u1ecdi th\u00e0nh ph\u1ea7n \u0111\u1ec1u n\u1eb1m tr\u00ean m\u1ed9t \u0111\u01b0\u1eddng th\u1eb3ng ki\u1ebfn tr\u00fac ho\u00e0n h\u1ea3o!\n        \n3.  **\u0110\u01b0\u1eddng k\u1ebb n\u00e9t \u0111\u1ee9t `border-bottom: 1px dashed rgba(0,0,0,0.09)` gi\u1eefa c\u00e1c t\u1ea7ng c\u1ee7a Checkout Bar:**\n    \n    -   Tr\u00ean Shopee, khu v\u1ef1c _Shopee Voucher_ v\u00e0 _Shopee Xu_ tr\u00ean thanh thanh to\u00e1n kh\u00f4ng d\u00f9ng \u0111\u01b0\u1eddng k\u1ebb li\u1ec1n (`solid`) m\u00e0 d\u00f9ng \u0111\u01b0\u1eddng k\u1ebb **n\u00e9t \u0111\u1ee9t (`dashed`)** gi\u1ed1ng nh\u01b0 \u0111\u01b0\u1eddng r\u0103ng c\u01b0a x\u00e9 v\u00e9 c\u1ee7a m\u1ed9t chi\u1ebfc cu\u1ed1ng v\u00e9 gi\u1ea3m gi\u00e1 th\u1ef1c t\u1ebf!\n        \n\n## 5. B\u00e0i t\u1eadp tr\u1ea3i nghi\u1ec7m th\u1ef1c t\u1ebf & H\u1eb9n g\u1eb7p \u1edf B\u00e0i 24!\n\nH\u00e3y l\u01b0u l\u1ea1i file `index.html` v\u00e0 `main.css`, sau \u0111\u00f3 nh\u1ea5n F5 tr\u00ean tr\u00ecnh duy\u1ec7t v\u00e0 cu\u1ed9n t\u1edbi khu v\u1ef1c **Gi\u1ecf H\u00e0ng (Ch\u01b0\u01a1ng 8)**:\n\n1.  Khi b\u1ea1n b\u1eaft \u0111\u1ea7u cu\u1ed9n chu\u1ed9t b\u01b0\u1edbc v\u00e0o khu v\u1ef1c Gi\u1ecf h\u00e0ng (khi s\u1ea3n ph\u1ea9m s\u1ed1 2 v\u1eabn c\u00f2n n\u1eb1m th\u1ea5p ph\u00eda d\u01b0\u1edbi m\u00e0n h\u00ecnh), b\u1ea1n s\u1ebd th\u1ea5y **Thanh T\u1ed5ng Thanh To\u00e1n `\u20ab4.670.000` t\u1ef1 \u0111\u1ed9ng n\u1ed5i l\u00ean v\u00e0 b\u00e1m ch\u1eb7t \u1edf m\u00e9p d\u01b0\u1edbi c\u00f9ng m\u00e0n h\u00ecnh**!\n    \n2.  Khi b\u1ea1n cu\u1ed9n ti\u1ebfp qua h\u1ebft s\u1ea3n ph\u1ea9m cu\u1ed1i c\u00f9ng c\u1ee7a Shop LG, thanh T\u1ed5ng thanh to\u00e1n s\u1ebd nh\u1eb9 nh\u00e0ng \"\u0111\u1eadu\" l\u1ea1i \u0111\u00fang v\u1ecb tr\u00ed k\u1ebft th\u00fac gi\u1ecf h\u00e0ng \u0111\u1ec3 nh\u01b0\u1eddng ch\u1ed7 cho ph\u1ea7n ti\u1ebfp theo \u0111i l\u00ean!\n    \n3.  B\u1ea1n c\u00f3 th\u1ec3 click chu\u1ed9t t\u00edch\/b\u1ecf t\u00edch v\u00e0o c\u00e1c \u00f4 Checkbox c\u1ee7a t\u1eebng s\u1ea3n ph\u1ea9m, t\u1eebng Gian h\u00e0ng ho\u1eb7c \u00f4 _Shopee Xu_ m\u01b0\u1ee3t m\u00e0 nh\u1edd t\u00e1i s\u1eed d\u1ee5ng b\u1ed9 `.filter-checkbox` t\u1eeb B\u00e0i 18!\n    \n\n**Th\u1eed th\u00e1ch kh\u00e1m ph\u00e1 d\u00e0nh cho b\u1ea1n tr\u01b0\u1edbc khi sang B\u00e0i 24:**\n\n1.  H\u00e3y th\u1eed nh\u00e2n b\u1ea3n (copy-paste) th\u00eam 2 kh\u1ed1i `<div class=\"cart-shop-group\">` n\u1eefa v\u00e0o trong `.cart-section` (\u0111\u1ec3 gi\u1ecf h\u00e0ng c\u00f3 kho\u1ea3ng 4\u20135 gian h\u00e0ng d\u00e0i h\u01a1n chi\u1ec1u cao m\u00e0n h\u00ecnh), sau \u0111\u00f3 l\u0103n chu\u1ed9t l\u00ean xu\u1ed1ng \u0111\u1ec3 c\u1ea3m nh\u1eadn s\u1ef1 ph\u1ed1i h\u1ee3p \u0111\u1ec9nh cao gi\u1eefa **Sticky Header \u1edf \u0111\u1ec9nh (`top: 0`)** v\u00e0 **Sticky Checkout Bar \u1edf \u0111\u00e1y (`bottom: 0`)** c\u00f9ng l\u00fac!\n    \n2.  H\u00e3y th\u1eed thi\u1ebft k\u1ebf th\u00eam m\u1ed9t h\u1ed9p Popover nh\u1ecf x\u1ed5 ra khi ng\u01b0\u1eddi d\u00f9ng b\u1ea5m v\u00e0o m\u1ee5c **\"Ph\u00e2n lo\u1ea1i h\u00e0ng: \u25be\"** (`.cart-item-variation`) \u0111\u1ec3 cho ph\u00e9p ch\u1ecdn l\u1ea1i m\u00e0u s\u1eafc b\u00e0n ph\u00edm ngay trong gi\u1ecf h\u00e0ng!\n    \n\n\u1ede **B\u00e0i 24**, ch\u00fang ta s\u1ebd ho\u00e0n thi\u1ec7n b\u01b0\u1edbc ti\u1ebfp theo ngay sau khi b\u1ea5m n\u00fat **\"Mua H\u00e0ng\"**:  **X\u00e2y d\u1ef1ng Giao Di\u1ec7n Trang Thanh To\u00e1n (`Checkout \/ Order Confirmation Page`)** v\u1edbi **\u0110\u01b0\u1eddng vi\u1ec1n Phong b\u00ec th\u01b0 k\u1ebb s\u1ecdc \u0110\u1ecf - Xanh (`repeating-linear-gradient`)** c\u1ef1c k\u1ef3 n\u1ed5i ti\u1ebfng \u1edf khung _\u0110\u1ecba Ch\u1ec9 Nh\u1eadn H\u00e0ng_ v\u00e0 b\u1ed9 ch\u1ecdn _Ph\u01b0\u01a1ng Th\u1ee9c Thanh To\u00e1n_!","published_at":"2026-11-14T07:25:18.000000Z","scheduled_publish_at":null,"is_published":true,"is_shared":false,"updated_at":"2026-11-14T18:28:01.000000Z","edited_at":"2026-11-14T07:26:41.000000Z","translation_source":null,"trend_at":null,"promoted_at":null,"reading_time":10,"points":0,"views_count":9,"clips_count":0,"comments_count":0,"rated_value":null,"promoted":false,"trending":false,"is_draft":false,"is_public":true,"locale_code":"vi","is_video":false,"thumbnail_url":"https:\/\/images.viblo.asia\/6562787f-bfb0-4448-81ca-43fe994723ef.png","user":{"data":{"id":184801,"url":"https:\/\/viblo.asia\/u\/Tuanhuu1201","avatar":"765295b3-c266-4756-8f0b-6b149861811c.png","name":"Thanh H\u1ea3i Hu\u1ef3nh","username":"Tuanhuu1201","followers_count":1,"reputation":7,"posts_count":26,"banned_at":null,"level_partner":null,"following":false}},"tags":{"data":[{"slug":"htmlcss","name":"HTML\/CSS"}]},"commentators":{"data":[]}},{"id":110618,"title":"[Series 100 B\u00e0i H\u1ecdc HTML\/CSS Th\u1ef1c Chi\u1ebfn - B\u00e0i 22]: X\u00e2y D\u1ef1ng Khu V\u1ef1c \u0110\u00e1nh Gi\u00e1 S\u1ea3n Ph\u1ea9m (Product Ratings & Reviews), B\u1ed9 L\u1ecdc B\u00ecnh Lu\u1eadn \u0110a Tr\u1ea1ng Th\u00e1i & Khung Ph\u1ea3n H\u1ed3i C\u1ee7a Ng\u01b0\u1eddi B\u00e1n","slug":"7Z4DE8l0JnX","url":"https:\/\/viblo.asia\/p\/series-100-bai-hoc-htmlcss-thuc-chien-bai-22-xay-dung-khu-vuc-danh-gia-san-pham-product-ratings-reviews-bo-loc-binh-luan-da-trang-thai-khung-phan-hoi-cua-nguoi-ban-7Z4DE8l0JnX","user_id":184801,"moderation":null,"transliterated":"series-100-bai-hoc-htmlcss-thuc-chien-bai-22-xay-dung-khu-vuc-danh-gia-san-pham-product-ratings-reviews-bo-loc-binh-luan-da-trang-thai-khung-phan-hoi-cua-nguoi-ban","contents_short":"Ch\u00e0o m\u1eebng anh em \u0111\u00e3 b\u01b0\u1edbc sang B\u00e0i 22 trong chu\u1ed7i 100 B\u00e0i H\u1ecdc L\u00e0m Ch\u1ee7 HTML\/CSS Qua D\u1ef1 \u00c1n Clone Shopee!\n\n\u1ede B\u00e0i 20 v\u00e0 B\u00e0i 21, ch\u00fang ta \u0111\u00e3 ho\u00e0n th\u00e0nh ph\u1ea7n Ch\u1ecdn mua s\u1ea3n ph\u1ea9m (Product Brief), Khung th\u00f4ng tin Gian h\u00e0ng (Shop Info Bar) v\u00e0 B\u1ea3ng th\u00f4ng s\u1ed1 k\u1ef9 thu\u1eadt (Specifications). H\u00f4m nay, ch\u00fang ta s\u1ebd l\u1eafp gh\u00e9p m\u1ea3nh gh\u00e9p cu\u1ed1i c\u00f9ng \u0111\u1ec3 kh\u00e9p l\u1ea1i tr\u1ecdn v\u1eb9n Ch\u01b0\u01a1ng 7 (Trang Chi Ti\u1ebft S\u1ea3n Ph\u1ea9m):  Khu v\u1ef1c \u0110\u00c1NH GI\u00c1 ...","contents":"Ch\u00e0o m\u1eebng anh em \u0111\u00e3 b\u01b0\u1edbc sang **B\u00e0i 22** trong chu\u1ed7i **100 B\u00e0i H\u1ecdc L\u00e0m Ch\u1ee7 HTML\/CSS Qua D\u1ef1 \u00c1n Clone Shopee**!\n\n\u1ede **B\u00e0i 20** v\u00e0 **B\u00e0i 21**, ch\u00fang ta \u0111\u00e3 ho\u00e0n th\u00e0nh ph\u1ea7n Ch\u1ecdn mua s\u1ea3n ph\u1ea9m (`Product Brief`), Khung th\u00f4ng tin Gian h\u00e0ng (`Shop Info Bar`) v\u00e0 B\u1ea3ng th\u00f4ng s\u1ed1 k\u1ef9 thu\u1eadt (`Specifications`). H\u00f4m nay, ch\u00fang ta s\u1ebd l\u1eafp gh\u00e9p m\u1ea3nh gh\u00e9p cu\u1ed1i c\u00f9ng \u0111\u1ec3 kh\u00e9p l\u1ea1i tr\u1ecdn v\u1eb9n **Ch\u01b0\u01a1ng 7 (Trang Chi Ti\u1ebft S\u1ea3n Ph\u1ea9m)**:  **Khu v\u1ef1c \u0110\u00c1NH GI\u00c1 S\u1ea2N PH\u1ea8M (`Product Ratings & Customer Reviews`)**!\n\nTr\u00ean th\u1ef1c t\u1ebf, 95% ng\u01b0\u1eddi mua h\u00e0ng tr\u00ean Shopee \u0111\u1ec1u l\u01b0\u1edbt xu\u1ed1ng khu v\u1ef1c \u0110\u00e1nh gi\u00e1 \u0111\u1ec3 xem \u1ea3nh ch\u1ee5p th\u1ef1c t\u1ebf v\u00e0 \u0111\u1ecdc b\u00ecnh lu\u1eadn c\u1ee7a ng\u01b0\u1eddi mua tr\u01b0\u1edbc r\u1ed3i m\u1edbi quy\u1ebft \u0111\u1ecbnh b\u1ea5m _\"Mua Ngay\"_. V\u1ec1 m\u1eb7t k\u1ef9 thu\u1eadt giao di\u1ec7n, khu v\u1ef1c n\u00e0y l\u00e0 m\u1ed9t b\u00e0i t\u1eadp t\u1ed5ng h\u1ee3p c\u1ef1c k\u1ef3 xu\u1ea5t s\u1eafc g\u1ed3m 3 th\u00e0nh ph\u1ea7n:\n\n1.  **Khung T\u1ed5ng Quan \u0110i\u1ec3m Sao & B\u1ed9 L\u1ecdc (`Rating Overview Box`):** N\u1eb1m tr\u00ean n\u1ec1n cam ph\u1ea5n nh\u1ea1t `#fffbf8` vi\u1ec1n `#f9ede5`, b\u00ean tr\u00e1i hi\u1ec3n th\u1ecb \u0111i\u1ec3m s\u1ed1 kh\u1ed5ng l\u1ed3 **`4.9 tr\u00ean 5`**, b\u00ean ph\u1ea3i l\u00e0 d\u00e3y c\u00e1c n\u00fat l\u1ecdc b\u00ecnh lu\u1eadn (_T\u1ea5t C\u1ea3, 5 Sao, 4 Sao, C\u00f3 B\u00ecnh Lu\u1eadn, C\u00f3 H\u00ecnh \u1ea2nh \/ Video_) s\u1eed d\u1ee5ng `flex-wrap: wrap` t\u1ef1 \u0111\u1ed9ng r\u1edbt d\u00f2ng m\u01b0\u1ee3t m\u00e0.\n    \n2.  **C\u1ea5u tr\u00fac Media Object cho t\u1eebng B\u00ecnh lu\u1eadn (`Review Item`):** Avatar tr\u00f2n c\u1ee7a ng\u01b0\u1eddi mua n\u1eb1m c\u1ed1 \u0111\u1ecbnh b\u00ean tr\u00e1i (`40x40px`), to\u00e0n b\u1ed9 n\u1ed9i dung \u0111\u00e1nh gi\u00e1 (T\u00ean, 5 ng\u00f4i sao, Ng\u00e0y mua & Ph\u00e2n lo\u1ea1i h\u00e0ng, N\u1ed9i dung ch\u1eef, D\u00e3y \u1ea3nh\/video th\u1ef1c t\u1ebf) n\u1eb1m b\u00ean ph\u1ea3i.\n    \n3.  **Khung \"Ph\u1ea3n H\u1ed3i C\u1ee7a Ng\u01b0\u1eddi B\u00e1n\" (`Seller Reply Box`) & N\u00fat \"H\u1eefu \u00cdch?\":** H\u1ed9p tho\u1ea1i n\u1ec1n x\u00e1m `#f5f5f5` th\u1ee5t l\u1ec1 b\u00ean trong b\u00ecnh lu\u1eadn th\u1ec3 hi\u1ec7n s\u1ef1 ch\u0103m s\u00f3c kh\u00e1ch h\u00e0ng t\u1eadn t\u00e2m c\u1ee7a Shop.\n    \n\nH\u00e3y c\u00f9ng gi\u1ea3i ph\u1eabu v\u00e0 vi\u1ebft code ho\u00e0n ch\u1ec9nh cho khu v\u1ef1c n\u00e0y ngay b\u00e2y gi\u1edd!\n\n## 1. T\u01b0 duy b\u1ed1 c\u1ee5c Khu v\u1ef1c \u0110\u00e1nh Gi\u00e1: M\u1eabu thi\u1ebft k\u1ebf \"Media Object\" kinh \u0111i\u1ec3n\n\nKhi thi\u1ebft k\u1ebf danh s\u00e1ch b\u00ecnh lu\u1eadn (tr\u00ean Shopee, Facebook, YouTube hay b\u1ea5t k\u1ef3 m\u1ea1ng x\u00e3 h\u1ed9i n\u00e0o), c\u00e1c k\u1ef9 s\u01b0 Frontend lu\u00f4n s\u1eed d\u1ee5ng m\u1ed9t khu\u00f4n m\u1eabu b\u1ed1 c\u1ee5c kinh \u0111i\u1ec3n g\u1ecdi l\u00e0 **Media Object Pattern**.\n\nH\u00e3y quan s\u00e1t c\u1ea5u tr\u00fac tr\u1ef1c quan c\u1ee7a c\u00e1c th\u1ebb \u0111\u00e1nh gi\u00e1 kh\u00e1ch h\u00e0ng (`Customer Review Cards`) v\u1edbi Avatar, d\u00e3y ng\u00f4i sao v\u00e0 n\u1ed9i dung ph\u1ea3n h\u1ed3i trong thi\u1ebft k\u1ebf UI hi\u1ec7n \u0111\u1ea1i d\u01b0\u1edbi \u0111\u00e2y:\n\n![image.png](https:\/\/images.viblo.asia\/a06713df-6b0f-43b6-97e5-3381be2ff160.png)\n\nB\u1ea3n ch\u1ea5t c\u1ee7a **Media Object** trong Flexbox l\u00e0 s\u1ef1 ph\u00e2n chia nhi\u1ec7m v\u1ee5 gi\u1eefa 2 c\u1ed9t:\n\nPlaintext\n\n```\n+-----------------------------------------------------------------------------+\n| [Avatar]   nguyenhuyhoang_dev                                               |\n|  (40px)    \u2605\u2605\u2605\u2605\u2605                                                            |\n|   C\u1ed1       2026-11-12 14:30 | Ph\u00e2n lo\u1ea1i h\u00e0ng: Tr\u1eafng Xanh (Ice Vein)         |\n|  \u0111\u1ecbnh                                                                       |\n|  b\u00ean       B\u00e0n ph\u00edm g\u00f5 c\u1ef1c k\u1ef3 \u00eam, \u00e2m thocky s\u1eb5n kh\u00f4ng c\u1ea7n mod th\u00eam...       |\n|  tr\u00e1i      [\u1ea2nh 1]  [\u1ea2nh 2]  [\u25b6 Video 0:15]                                 |\n|            +--------------------------------------------------------------+ |\n|            | Ph\u1ea3n H\u1ed3i C\u1ee7a Ng\u01b0\u1eddi B\u00e1n:                                      | |\n|            | C\u1ea3m \u01a1n b\u1ea1n \u0111\u00e3 tin t\u01b0\u1edfng \u1ee7ng h\u1ed9 AULA Official Store VN \u1ea1! \u2764\ufe0f  | |\n|            +--------------------------------------------------------------+ |\n|            \ud83d\udc4d H\u1eefu \u00edch? (24)                                                 |\n+-----------------------------------------------------------------------------+\n```\n\n| Th\u00e0nh ph\u1ea7n trong `.review-item` | Khai b\u00e1o CSS c\u1ed1t l\u00f5i | T\u1ea1i sao ph\u1ea3i thi\u1ebft k\u1ebf nh\u01b0 v\u1eady? |\n| :--- | :--- | :--- |\n| **C\u1ed9t Tr\u00e1i:** `.review-item__avatar` | `width: 4rem;` <br>`height: 4rem;` <br>`flex-shrink: 0;` | Gi\u1eef \u1ea3nh \u0111\u1ea1i di\u1ec7n c\u1ee7a ng\u01b0\u1eddi mua lu\u00f4n tr\u00f2n tr\u1ecba \u1edf g\u00f3c tr\u00ean b\u00ean tr\u00e1i, tuy\u1ec7t \u0111\u1ed1i kh\u00f4ng b\u1ecb b\u00f3p m\u00e9o d\u00f9 b\u00ecnh lu\u1eadn b\u00ean ph\u1ea3i d\u00e0i 20 d\u00f2ng! |\n| **C\u1ed9t Ph\u1ea3i:** `.review-item__main` | `flex: 1;` <br>`margin-left: 1.5rem;` | T\u1ef1 \u0111\u1ed9ng chi\u1ebfm to\u00e0n b\u1ed9 chi\u1ec1u ngang c\u00f2n l\u1ea1i. Nh\u1edd n\u1eb1m trong c\u1ed9t ri\u00eang, c\u1ea3 t\u00ean ng\u01b0\u1eddi d\u00f9ng, ng\u00f4i sao, \u1ea3nh ch\u1ee5p th\u1ef1c t\u1ebf v\u00e0 khung Ph\u1ea3n h\u1ed3i c\u1ee7a Shop \u0111\u1ec1u th\u1eb3ng h\u00e0ng d\u1ecdc \u1edf m\u00e9p tr\u00e1i m\u00e0 kh\u00f4ng b\u1ecb tr\u00e0n xu\u1ed1ng d\u01b0\u1edbi ch\u00e2n Avatar! |\n\n## 2. Thi\u1ebft k\u1ebf Khung \u0110i\u1ec3m `4.9 tr\u00ean 5` & B\u1ed9 L\u1ecdc N\u00fat B\u1ea5m (`Radio Hack`)\n\n\u1ede \u0111\u1ea7u m\u1ee5c \u0110\u00e1nh gi\u00e1 l\u00e0 chi\u1ebfc h\u1ed9p `.rating-overview` c\u00f3 m\u00e0u n\u1ec1n cam kem c\u1ef1c nh\u1eb9 (`#fffbf8`) v\u00e0 \u0111\u01b0\u1eddng vi\u1ec1n m\u1ea3nh (`1px solid #f9ede5`).\n\n-   **C\u1ed9t Tr\u00e1i (`180px`):** Ch\u1eef **`4.9`** \u0111\u01b0\u1ee3c ph\u00f3ng to l\u00ean `3rem` (`30px`) \u0111i c\u00f9ng ch\u1eef `tr\u00ean 5` (`1.8rem`), t\u1ea1o \u0111i\u1ec3m nh\u1ea5n th\u1ecb gi\u00e1c t\u1ee9c th\u00ec v\u1ec1 ch\u1ea5t l\u01b0\u1ee3ng s\u1ea3n ph\u1ea9m.\n    \n-   **C\u1ed9t Ph\u1ea3i (`flex: 1`):** Ch\u1ee9a 8 n\u00fat b\u1ea5m l\u1ecdc b\u00ecnh lu\u1eadn (_T\u1ea5t C\u1ea3, 5 Sao (1,1k), 4 Sao (85)..._). Ch\u00fang ta ti\u1ebfp t\u1ee5c \u00e1p d\u1ee5ng k\u1ef9 thu\u1eadt **`<input type=\"radio\" name=\"rating-filter\">`** (\u0111\u00e3 h\u1ecdc \u1edf B\u00e0i 17 v\u00e0 B\u00e0i 20) \u0111\u1ec3 ng\u01b0\u1eddi d\u00f9ng c\u00f3 th\u1ec3 **click chu\u1ed9t ch\u1ecdn t\u1eebng b\u1ed9 l\u1ecdc** v\u00e0 th\u1ea5y \u0111\u01b0\u1eddng vi\u1ec1n cam `#ee4d2d` nh\u1ea3y sang n\u00fat v\u1eeba b\u1ea5m ngay l\u1eadp t\u1ee9c!\n    \n\n## 3. Th\u1ef1c h\u00e0nh: Code Khu V\u1ef1c \u0110\u00e1nh Gi\u00e1 S\u1ea3n Ph\u1ea9m Chu\u1ea9n Shopee\n\n### B\u01b0\u1edbc 1: B\u1ed5 sung HTML Kh\u1ed1i \u0110\u00e1nh Gi\u00e1 S\u1ea3n Ph\u1ea9m v\u00e0o `index.html`\n\nH\u00e3y m\u1edf file `index.html`, t\u00ecm \u0111\u1ebfn c\u1ed9t `<div class=\"product-content__main\">` (b\u00ean trong `<div class=\"product-content-layout\">` m\u00e0 ch\u00fang ta \u0111\u00e3 t\u1ea1o \u1edf B\u00e0i 21). Ngay b\u00ean d\u01b0\u1edbi th\u1ebb \u0111\u00f3ng `<\/section>` c\u1ee7a ph\u1ea7n **M\u00d4 T\u1ea2 S\u1ea2N PH\u1ea8M** (`.product-desc-section`), b\u1ea1n h\u00e3y ch\u00e8n th\u00eam kh\u1ed1i **\u0110\u00c1NH GI\u00c1 S\u1ea2N PH\u1ea8M** d\u01b0\u1edbi \u0111\u00e2y:\n\nHTML\n\n```\n                    <!-- PH\u1ea6N C: KHU V\u1ef0C \u0110\u00c1NH GI\u00c1 S\u1ea2N PH\u1ea8M (PRODUCT RATINGS & REVIEWS - B\u00c0I 22) -->\n                    <section class=\"product-ratings-section\">\n                        <h2 class=\"product-section-heading\">\u0110\u00c1NH GI\u00c1 S\u1ea2N PH\u1ea8M<\/h2>\n\n                        <!-- 1. Khung T\u1ed5ng Quan \u0110i\u1ec3m Sao & B\u1ed9 L\u1ecdc B\u00ecnh Lu\u1eadn -->\n                        <div class=\"rating-overview\">\n                            \n                            <!-- C\u1ed9t Tr\u00e1i: \u0110i\u1ec3m s\u1ed1 4.9 tr\u00ean 5 -->\n                            <div class=\"rating-overview__briefing\">\n                                <div class=\"rating-overview__score-wrap\">\n                                    <span class=\"rating-overview__score\">4.9<\/span>\n                                    <span class=\"rating-overview__score-out-of\">tr\u00ean 5<\/span>\n                                <\/div>\n                                <div class=\"rating-overview__stars\">\n                                    <i class=\"fa-solid fa-star\"><\/i>\n                                    <i class=\"fa-solid fa-star\"><\/i>\n                                    <i class=\"fa-solid fa-star\"><\/i>\n                                    <i class=\"fa-solid fa-star\"><\/i>\n                                    <i class=\"fa-solid fa-star\"><\/i>\n                                <\/div>\n                            <\/div>\n\n                            <!-- C\u1ed9t Ph\u1ea3i: D\u00e3y n\u00fat b\u1ea5m l\u1ecdc b\u00ecnh lu\u1eadn (B\u1ea5m \u0111\u1ed5i n\u00fat b\u1eb1ng Radio Hack) -->\n                            <div class=\"rating-overview__filters\">\n                                <label class=\"rating-filter-item\">\n                                    <input type=\"radio\" name=\"rating-filter\" class=\"rating-filter-input\" checked>\n                                    <span class=\"rating-filter-btn\">T\u1ea5t C\u1ea3<\/span>\n                                <\/label>\n                                <label class=\"rating-filter-item\">\n                                    <input type=\"radio\" name=\"rating-filter\" class=\"rating-filter-input\">\n                                    <span class=\"rating-filter-btn\">5 Sao (1,1k)<\/span>\n                                <\/label>\n                                <label class=\"rating-filter-item\">\n                                    <input type=\"radio\" name=\"rating-filter\" class=\"rating-filter-input\">\n                                    <span class=\"rating-filter-btn\">4 Sao (85)<\/span>\n                                <\/label>\n                                <label class=\"rating-filter-item\">\n                                    <input type=\"radio\" name=\"rating-filter\" class=\"rating-filter-input\">\n                                    <span class=\"rating-filter-btn\">3 Sao (12)<\/span>\n                                <\/label>\n                                <label class=\"rating-filter-item\">\n                                    <input type=\"radio\" name=\"rating-filter\" class=\"rating-filter-input\">\n                                    <span class=\"rating-filter-btn\">2 Sao (2)<\/span>\n                                <\/label>\n                                <label class=\"rating-filter-item\">\n                                    <input type=\"radio\" name=\"rating-filter\" class=\"rating-filter-input\">\n                                    <span class=\"rating-filter-btn\">1 Sao (1)<\/span>\n                                <\/label>\n                                <label class=\"rating-filter-item\">\n                                    <input type=\"radio\" name=\"rating-filter\" class=\"rating-filter-input\">\n                                    <span class=\"rating-filter-btn\">C\u00f3 B\u00ecnh Lu\u1eadn (640)<\/span>\n                                <\/label>\n                                <label class=\"rating-filter-item\">\n                                    <input type=\"radio\" name=\"rating-filter\" class=\"rating-filter-input\">\n                                    <span class=\"rating-filter-btn\">C\u00f3 H\u00ecnh \u1ea2nh \/ Video (412)<\/span>\n                                <\/label>\n                            <\/div>\n\n                        <\/div>\n\n                        <!-- 2. Danh S\u00e1ch B\u00ecnh Lu\u1eadn Th\u1ef1c T\u1ebf C\u1ee7a Ng\u01b0\u1eddi Mua (Review List) -->\n                        <div class=\"review-list\">\n                            \n                            <!-- B\u00ccNH LU\u1eacN S\u1ed0 1 (\u0110\u1ea7y \u0111\u1ee7 \u1ea2nh + Video + Ph\u1ea3n h\u1ed3i c\u1ee7a Shop) -->\n                            <article class=\"review-item\">\n                                <!-- C\u1ed9t tr\u00e1i: Avatar ng\u01b0\u1eddi mua -->\n                                <img src=\"https:\/\/placehold.co\/100x100\/2673dd\/ffffff?text=H\" alt=\"Avatar ng\u01b0\u1eddi mua\" class=\"review-item__avatar\">\n\n                                <!-- C\u1ed9t ph\u1ea3i: N\u1ed9i dung chi ti\u1ebft \u0111\u00e1nh gi\u00e1 -->\n                                <div class=\"review-item__main\">\n                                    <div class=\"review-item__author\">hieu_backend_dev<\/div>\n                                    \n                                    <div class=\"review-item__stars\">\n                                        <i class=\"fa-solid fa-star\"><\/i>\n                                        <i class=\"fa-solid fa-star\"><\/i>\n                                        <i class=\"fa-solid fa-star\"><\/i>\n                                        <i class=\"fa-solid fa-star\"><\/i>\n                                        <i class=\"fa-solid fa-star\"><\/i>\n                                    <\/div>\n\n                                    <div class=\"review-item__meta\">\n                                        2026-11-12 09:45 &nbsp;|&nbsp; Ph\u00e2n lo\u1ea1i h\u00e0ng: Tr\u1eafng Xanh (Ice Vein)\n                                    <\/div>\n\n                                    <!-- C\u00e1c thu\u1ed9c t\u00ednh \u0111\u00e1nh gi\u00e1 chi ti\u1ebft c\u1ee7a Shopee -->\n                                    <div class=\"review-item__attributes\">\n                                        <p><span>Ch\u1ea5t l\u01b0\u1ee3ng s\u1ea3n ph\u1ea9m:<\/span> Tuy\u1ec7t v\u1eddi, build ch\u1eafc ch\u1eafn, \u0111\u1ea7m tay<\/p>\n                                        <p><span>\u0110\u00fang v\u1edbi m\u00f4 t\u1ea3:<\/span> Chu\u1ea9n 100% nh\u01b0 h\u00ecnh qu\u1ea3ng c\u00e1o<\/p>\n                                    <\/div>\n\n                                    <p class=\"review-item__comment\">M\u00ecnh mua v\u1ec1 \u0111\u1ec3 g\u00f5 code h\u00e0ng ng\u00e0y, ph\u1ea3i n\u00f3i l\u00e0 Reaper Switch \u00e2m n\u1ed5 r\u1ea5t \u0111anh v\u00e0 \u00eam (creamy\/thocky), kh\u00f4ng \u1ed3n \u1ea3nh h\u01b0\u1edfng \u0111\u1ed3ng nghi\u1ec7p. K\u1ebft n\u1ed1i 2.4GHz v\u00e0 Bluetooth chuy\u1ec3n qua l\u1ea1i gi\u1eefa PC v\u1edbi MacBook c\u1ef1c nhanh. Shop \u0111\u00f3ng g\u00f3i 3 l\u1edbp ch\u1ed1ng s\u1ed1c r\u1ea5t k\u1ef9, giao H\u1ecfa T\u1ed1c \u1edf TP.HCM ch\u1ec9 1 ti\u1ebfng l\u00e0 nh\u1eadn \u0111\u01b0\u1ee3c h\u00e0ng!<\/p>\n\n                                    <!-- D\u00e3y h\u00ecnh \u1ea3nh & Video th\u1ef1c t\u1ebf kh\u00e1ch h\u00e0ng ch\u1ee5p -->\n                                    <ul class=\"review-item__media-list\">\n                                        <li class=\"review-media-thumb review-media-thumb--video\">\n                                            <img src=\"https:\/\/placehold.co\/150x150\/ee4d2d\/ffffff?text=Sound+Test\" alt=\"Video test \u00e2m ph\u00edm\">\n                                            <span class=\"video-badge\">\n                                                <i class=\"fa-solid fa-video\"><\/i> 0:18\n                                            <\/span>\n                                        <\/li>\n                                        <li class=\"review-media-thumb\">\n                                            <img src=\"https:\/\/placehold.co\/150x150\/00bfa5\/ffffff?text=Anh+Thuc+Te+1\" alt=\"\u1ea2nh th\u1ef1c t\u1ebf 1\">\n                                        <\/li>\n                                        <li class=\"review-media-thumb\">\n                                            <img src=\"https:\/\/placehold.co\/150x150\/ff9800\/ffffff?text=Anh+Thuc+Te+2\" alt=\"\u1ea2nh th\u1ef1c t\u1ebf 2\">\n                                        <\/li>\n                                    <\/ul>\n\n                                    <!-- Khung Ph\u1ea3n H\u1ed3i C\u1ee7a Ng\u01b0\u1eddi B\u00e1n -->\n                                    <div class=\"review-item__seller-reply\">\n                                        <h4 class=\"seller-reply__heading\">Ph\u1ea3n H\u1ed3i C\u1ee7a Ng\u01b0\u1eddi B\u00e1n<\/h4>\n                                        <p class=\"seller-reply__text\">D\u1ea1 AULA Official Store VN xin c\u1ea3m \u01a1n b\u1ea1n \u0111\u00e3 tin t\u01b0\u1edfng l\u1ef1a ch\u1ecdn s\u1ea3n ph\u1ea9m v\u00e0 \u0111\u1ec3 l\u1ea1i \u0111\u00e1nh gi\u00e1 5 sao v\u00f4 c\u00f9ng t\u00e2m huy\u1ebft \u1ea1! Ch\u00fac b\u1ea1n lu\u00f4n c\u00f3 nh\u1eefng gi\u1edd ph\u00fat g\u00f5 code th\u1eadt th\u0103ng hoa c\u00f9ng chi\u1ebfc b\u00e0n ph\u00edm F75 Pro nh\u00e9! \u2764\ufe0f<\/p>\n                                    <\/div>\n\n                                    <!-- N\u00fat b\u1ea5m H\u1eefu \u00edch (Like) d\u01b0\u1edbi \u0111\u00e1y b\u00ecnh lu\u1eadn -->\n                                    <div class=\"review-item__actions\">\n                                        <button type=\"button\" class=\"review-like-btn\">\n                                            <i class=\"fa-solid fa-thumbs-up\"><\/i>\n                                            <span>H\u1eefu \u00cdch? (28)<\/span>\n                                        <\/button>\n                                    <\/div>\n                                <\/div>\n                            <\/article>\n\n                            <!-- B\u00ccNH LU\u1eacN S\u1ed0 2 -->\n                            <article class=\"review-item\">\n                                <img src=\"https:\/\/placehold.co\/100x100\/00bfa5\/ffffff?text=P\" alt=\"Avatar ng\u01b0\u1eddi mua\" class=\"review-item__avatar\">\n\n                                <div class=\"review-item__main\">\n                                    <div class=\"review-item__author\">phuong_designer_99<\/div>\n                                    \n                                    <div class=\"review-item__stars\">\n                                        <i class=\"fa-solid fa-star\"><\/i>\n                                        <i class=\"fa-solid fa-star\"><\/i>\n                                        <i class=\"fa-solid fa-star\"><\/i>\n                                        <i class=\"fa-solid fa-star\"><\/i>\n                                        <i class=\"fa-solid fa-star\"><\/i>\n                                    <\/div>\n\n                                    <div class=\"review-item__meta\">\n                                        2026-11-10 18:20 &nbsp;|&nbsp; Ph\u00e2n lo\u1ea1i h\u00e0ng: T\u00edm H\u1ed3ng Pastel\n                                    <\/div>\n\n                                    <p class=\"review-item__comment\">M\u00e0u T\u00edm H\u1ed3ng \u1edf ngo\u00e0i nh\u00ecn xinh x\u1ec9u lu\u00f4n m\u1ecdi ng\u01b0\u1eddi \u01a1i! \u0110\u00e8n LED RGB m\u1ea1ch xu\u00f4i c\u1ef1c s\u00e1ng, n\u00fam xoay ch\u1ec9nh \u00e2m l\u01b0\u1ee3ng v\u1eb7n r\u1ea5t m\u01b0\u1ee3t. \u00c1p \u0111\u01b0\u1ee3c m\u00e3 gi\u1ea3m gi\u00e1 50k c\u1ee7a Shop n\u00ean gi\u00e1 qu\u00e1 h\u1eddi so v\u1edbi ch\u1ea5t l\u01b0\u1ee3ng.<\/p>\n\n                                    <ul class=\"review-item__media-list\">\n                                        <li class=\"review-media-thumb\">\n                                            <img src=\"https:\/\/placehold.co\/150x150\/7b1fa2\/ffffff?text=Tim+Hong+1\" alt=\"\u1ea2nh th\u1ef1c t\u1ebf m\u00e0u t\u00edm h\u1ed3ng\">\n                                        <\/li>\n                                        <li class=\"review-media-thumb\">\n                                            <img src=\"https:\/\/placehold.co\/150x150\/d0011b\/ffffff?text=Tim+Hong+2\" alt=\"\u1ea2nh g\u00f3c nghi\u00eang\">\n                                        <\/li>\n                                    <\/ul>\n\n                                    <div class=\"review-item__actions\">\n                                        <button type=\"button\" class=\"review-like-btn\">\n                                            <i class=\"fa-solid fa-thumbs-up\"><\/i>\n                                            <span>H\u1eefu \u00cdch? (9)<\/span>\n                                        <\/button>\n                                    <\/div>\n                                <\/div>\n                            <\/article>\n\n                        <\/div>\n\n                    <\/section>\n\n```\n\n### B\u01b0\u1edbc 2: Vi\u1ebft CSS chi ti\u1ebft cho Khu V\u1ef1c \u0110\u00e1nh Gi\u00e1 S\u1ea3n Ph\u1ea9m trong `.\/assets\/css\/main.css`\n\nH\u00e3y m\u1edf file `main.css`, cu\u1ed9n xu\u1ed1ng d\u01b0\u1edbi c\u00f9ng v\u00e0 b\u1ed5 sung \u0111o\u1ea1n code CSS c\u1ee7a B\u00e0i 22 d\u01b0\u1edbi \u0111\u00e2y:\n\nCSS\n\n```\n\/* B\u1ed5 sung v\u00e0o cu\u1ed1i file .\/assets\/css\/main.css *\/\n\n\/* --- B\u00c0I 22: KHU V\u1ef0C \u0110\u00c1NH GI\u00c1 S\u1ea2N PH\u1ea8M (PRODUCT RATINGS & REVIEWS) --- *\/\n\n.product-ratings-section {\n    margin-top: 3.6rem;\n    padding-top: 2.4rem;\n    border-top: 1px solid rgba(0, 0, 0, 0.05);\n}\n\n\/* 1. Khung T\u1ed5ng Quan \u0110i\u1ec3m Sao & B\u1ed9 L\u1ecdc (.rating-overview) *\/\n.rating-overview {\n    background-color: #fffbf8;      \/* N\u1ec1n cam ph\u1ea5n \u0111\u1eb7c tr\u01b0ng c\u1ee7a khung \u0110\u00e1nh gi\u00e1 Shopee *\/\n    border: 1px solid #f9ede5;\n    border-radius: 2px;\n    padding: 3rem;\n    margin-bottom: 1.6rem;\n    display: flex;\n    align-items: center;\n    gap: 3.6rem;\n}\n\n\/* C\u1ed9t Tr\u00e1i: \u0110i\u1ec3m s\u1ed1 4.9 tr\u00ean 5 *\/\n.rating-overview__briefing {\n    text-align: center;\n    flex-shrink: 0;\n    min-width: 14rem;\n}\n\n.rating-overview__score-wrap {\n    color: var(--shopee-primary);\n    margin-bottom: 0.8rem;\n}\n\n.rating-overview__score {\n    font-size: 3rem;                \/* S\u1ed1 4.9 to 30px n\u1ed5i b\u1eadt *\/\n    font-weight: 500;\n}\n\n.rating-overview__score-out-of {\n    font-size: 1.8rem;\n}\n\n.rating-overview__stars {\n    color: var(--shopee-primary);\n    font-size: 1.8rem;\n    display: flex;\n    justify-content: center;\n    gap: 0.3rem;\n}\n\n\/* C\u1ed9t Ph\u1ea3i: D\u00e3y c\u00e1c n\u00fat b\u1ea5m l\u1ecdc b\u00ecnh lu\u1eadn *\/\n.rating-overview__filters {\n    flex: 1;\n    display: flex;\n    flex-wrap: wrap;                \/* T\u1ef1 \u0111\u1ed9ng r\u1edbt xu\u1ed1ng h\u00e0ng 2 khi h\u1ebft chi\u1ec1u ngang *\/\n    gap: 0.8rem;\n}\n\n.rating-filter-input {\n    display: none;                  \/* \u1ea8n n\u00fat radio g\u1ed1c *\/\n}\n\n.rating-filter-btn {\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    height: 3.2rem;\n    min-width: 10rem;\n    padding: 0 1.4rem;\n    background-color: var(--white-color);\n    border: 1px solid rgba(0, 0, 0, 0.09);\n    border-radius: 2px;\n    font-size: 1.4rem;\n    color: var(--text-primary);\n    cursor: pointer;\n    user-select: none;\n    transition: all 0.15s ease;\n}\n\n\/* Khi n\u00fat l\u1ecdc \u0111ang \u0111\u01b0\u1ee3c ch\u1ecdn (:checked) -> L\u00ean vi\u1ec1n cam & ch\u1eef cam chu\u1ea9n Shopee *\/\n.rating-filter-input:checked + .rating-filter-btn {\n    border-color: var(--shopee-primary);\n    color: var(--shopee-primary);\n}\n\n\/* 2. Danh S\u00e1ch B\u00ecnh Lu\u1eadn (Media Object Layout) *\/\n.review-item {\n    display: flex;\n    align-items: flex-start;\n    padding: 2rem 0 2rem 1.6rem;\n    border-bottom: 1px solid rgba(0, 0, 0, 0.09);\n}\n\n.review-item:last-child {\n    border-bottom: none;\n    padding-bottom: 0;\n}\n\n\/* Avatar tr\u00f2n 40x40px b\u00ean tr\u00e1i *\/\n.review-item__avatar {\n    width: 4rem;\n    height: 4rem;\n    border-radius: 50%;\n    object-fit: cover;\n    flex-shrink: 0;\n}\n\n\/* Kh\u1ed1i n\u1ed9i dung b\u00ecnh lu\u1eadn b\u00ean ph\u1ea3i *\/\n.review-item__main {\n    flex: 1;\n    margin-left: 1.5rem;\n}\n\n.review-item__author {\n    font-size: 1.25rem;\n    color: var(--text-primary);\n    margin-bottom: 0.4rem;\n}\n\n.review-item__stars {\n    color: var(--shopee-primary);\n    font-size: 1.1rem;\n    display: flex;\n    gap: 0.2rem;\n    margin-bottom: 0.6rem;\n}\n\n.review-item__meta {\n    font-size: 1.2rem;\n    color: rgba(0, 0, 0, 0.54);\n    margin-bottom: 1.2rem;\n}\n\n\/* C\u00e1c ti\u00eau ch\u00ed \u0111\u00e1nh gi\u00e1 ph\u1ee5 (Ch\u1ea5t l\u01b0\u1ee3ng s\u1ea3n ph\u1ea9m \/ \u0110\u00fang v\u1edbi m\u00f4 t\u1ea3) *\/\n.review-item__attributes {\n    font-size: 1.35rem;\n    line-height: 2rem;\n    color: var(--text-primary);\n    margin-bottom: 1rem;\n}\n\n.review-item__attributes span {\n    color: rgba(0, 0, 0, 0.4);\n}\n\n.review-item__comment {\n    font-size: 1.4rem;\n    line-height: 2rem;\n    color: var(--text-primary);\n    margin-bottom: 1.4rem;\n}\n\n\/* D\u00e3y \u1ea3nh & Video feedback th\u1ef1c t\u1ebf c\u1ee7a kh\u00e1ch (72x72px) *\/\n.review-item__media-list {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 1rem;\n    margin-bottom: 1.4rem;\n}\n\n.review-media-thumb {\n    width: 7.2rem;                  \/* R\u1ed9ng 72px chu\u1ea9n k\u00edch th\u01b0\u1edbc \u1ea3nh review Shopee *\/\n    height: 7.2rem;\n    border-radius: 2px;\n    overflow: hidden;\n    position: relative;\n    cursor: zoom-in;                \/* Con tr\u1ecf chu\u1ed9t bi\u1ebfn th\u00e0nh k\u00ednh l\u00fap ph\u00f3ng to \u1ea3nh! *\/\n    border: 1px solid rgba(0, 0, 0, 0.05);\n}\n\n.review-media-thumb img {\n    width: 100%;\n    height: 100%;\n    object-fit: cover;\n}\n\n\/* Nh\u00e3n th\u1eddi l\u01b0\u1ee3ng Video n\u1eb1m \u0111\u00e8 \u1edf \u0111\u00e1y t\u1ea5m \u1ea3nh \u0111\u1ea7u ti\u00ean (0:18) *\/\n.video-badge {\n    position: absolute;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    height: 2rem;\n    background-color: rgba(0, 0, 0, 0.54);\n    color: var(--white-color);\n    font-size: 1.1rem;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    padding: 0 0.6rem;\n}\n\n\/* Khung Ph\u1ea3n H\u1ed3i C\u1ee7a Ng\u01b0\u1eddi B\u00e1n (N\u1ec1n x\u00e1m #f5f5f5) *\/\n.review-item__seller-reply {\n    background-color: #f5f5f5;\n    padding: 1.4rem 1.6rem;\n    border-radius: 2px;\n    margin-bottom: 1.4rem;\n}\n\n.seller-reply__heading {\n    font-size: 1.35rem;\n    font-weight: 500;\n    color: #8b572a;                 \/* M\u00e0u n\u00e2u cam \u0111\u1eb7c tr\u01b0ng cho ti\u00eau \u0111\u1ec1 Ph\u1ea3n h\u1ed3i c\u1ee7a Shop *\/\n    margin-bottom: 0.6rem;\n}\n\n.seller-reply__text {\n    font-size: 1.35rem;\n    line-height: 1.9rem;\n    color: rgba(0, 0, 0, 0.65);\n}\n\n\/* N\u00fat b\u1ea5m H\u1eefu \u00edch (Thumbs up) *\/\n.review-like-btn {\n    display: inline-flex;\n    align-items: center;\n    gap: 0.6rem;\n    font-size: 1.3rem;\n    color: rgba(0, 0, 0, 0.4);\n    transition: color 0.15s ease;\n}\n\n.review-like-btn:hover {\n    color: var(--shopee-primary);\n}\n\n```\n\n![image.png](https:\/\/images.viblo.asia\/a4b71bfc-f02e-4c51-8038-4e28adf073b5.png)\n\n## 4. Gi\u1ea3i m\u00e3 d\u00f2ng code: 3 Chi ti\u1ebft UX tinh t\u1ebf trong B\u00e0i 22\n\n1.  **Con tr\u1ecf k\u00ednh l\u00fap `cursor: zoom-in` tr\u00ean \u1ea3nh Feedback (`.review-media-thumb`):**\n    \n    -   Tr\u00ean Shopee, khi ng\u01b0\u1eddi d\u00f9ng \u0111\u01b0a chu\u1ed9t v\u00e0o c\u00e1c t\u1ea5m \u1ea3nh nh\u1ecf (`72x72px`) do kh\u00e1ch h\u00e0ng ch\u1ee5p, h\u1ecd k\u1ef3 v\u1ecdng b\u1ea5m v\u00e0o \u0111\u00f3 \u0111\u1ec3 ph\u00f3ng to t\u1ea5m \u1ea3nh l\u00ean xem k\u1ef9 ch\u1ea5t l\u01b0\u1ee3ng s\u1ea3n ph\u1ea9m.\n        \n    -   Thay v\u00ec d\u00f9ng `cursor: pointer` (b\u00e0n tay b\u1ea5m link b\u00ecnh th\u01b0\u1eddng), thu\u1ed9c t\u00ednh **`cursor: zoom-in;`** bi\u1ebfn con tr\u1ecf chu\u1ed9t th\u00e0nh **chi\u1ebfc k\u00ednh l\u00fap c\u00f3 d\u1ea5u c\u1ed9ng (`+`) b\u00ean trong** \u2014 \u0111\u00fang 100% h\u00e0nh vi c\u1ee7a trang Shopee th\u1eadt!\n        \n2.  **Nh\u00e3n th\u1eddi l\u01b0\u1ee3ng Video \u0111\u00e8 l\u00ean \u0111\u00e1y \u1ea3nh (`.video-badge`):**\n    \n    -   \u1ede t\u1ea5m \u1ea3nh \u0111\u1ea7u ti\u00ean c\u1ee7a B\u00ecnh lu\u1eadn s\u1ed1 1, ch\u00fang ta \u0111\u1eb7t m\u1ed9t d\u1ea3i b\u0103ng \u0111en b\u00e1n trong su\u1ed1t (`background-color: rgba(0, 0, 0, 0.54)`) b\u00e1m s\u00e1t \u0111\u00e1y khung \u1ea3nh (`left: 0; right: 0; bottom: 0; height: 2rem;`), b\u00ean tr\u00e1i l\u00e0 icon m\u00e1y quay `<i class=\"fa-solid fa-video\"><\/i>`, b\u00ean ph\u1ea3i l\u00e0 s\u1ed1 gi\u00e2y `0:18` nh\u1edd `justify-content: space-between`.\n        \n3.  **Ph\u1ed1i m\u00e0u tinh t\u1ebf cho Khung T\u1ed5ng Quan \u0110i\u1ec3m Sao (`#fffbf8` & `#f9ede5`):**\n    \n    -   Thay v\u00ec d\u00f9ng l\u1ea1i m\u00e0u n\u1ec1n x\u00e1m `#fafafa` gi\u1ed1ng khung Gi\u00e1 b\u00e1n \u1edf tr\u00ean (d\u1ec5 g\u00e2y nh\u00e0m ch\u00e1n), Shopee pha m\u1ed9t t\u00f4ng m\u00e0u **Cam Kem si\u00eau nh\u1ea1t (`#fffbf8`)** \u0111i k\u00e8m \u0111\u01b0\u1eddng vi\u1ec1n **`#f9ede5`** cho h\u1ed9p `.rating-overview`. S\u1eafc \u1ea5m n\u00e0y l\u00e0m t\u00f4n vinh d\u00e0n ng\u00f4i sao m\u00e0u cam `#ee4d2d` v\u00e0 t\u1ea1o c\u1ea3m gi\u00e1c t\u00edch c\u1ef1c, \u0111\u00e1ng tin c\u1eady cho khu v\u1ef1c \u0111\u00e1nh gi\u00e1!\n        \n\n## 5. B\u00e0i t\u1eadp th\u1ef1c h\u00e0nh cu\u1ed1i Ch\u01b0\u01a1ng 7 & B\u01b0\u1edbc sang Ch\u01b0\u01a1ng 8!\n\nH\u00e3y l\u01b0u l\u1ea1i file `index.html` v\u00e0 `main.css`, sau \u0111\u00f3 nh\u1ea5n F5 tr\u00ean tr\u00ecnh duy\u1ec7t v\u00e0 cu\u1ed9n xu\u1ed1ng khu v\u1ef1c **\u0110\u00c1NH GI\u00c1 S\u1ea2N PH\u1ea8M**:\n\n1.  H\u00e3y th\u1eed **click chu\u1ed9t qua l\u1ea1i gi\u1eefa c\u00e1c n\u00fat l\u1ecdc**:  _\"T\u1ea5t C\u1ea3\"_,  _\"5 Sao (1,1k)\"_,  _\"C\u00f3 B\u00ecnh Lu\u1eadn (640)\"_,  _\"C\u00f3 H\u00ecnh \u1ea2nh \/ Video (412)\"_ \u2014 vi\u1ec1n cam ph\u1ea3n h\u1ed3i t\u1ee9c th\u00ec c\u1ef1c k\u1ef3 m\u01b0\u1ee3t m\u00e0!\n    \n2.  H\u00e3y \u0111\u01b0a chu\u1ed9t v\u00e0o 3 t\u1ea5m \u1ea3nh\/video feedback c\u1ee7a b\u00ecnh lu\u1eadn \u0111\u1ea7u ti\u00ean \u0111\u1ec3 th\u1ea5y con tr\u1ecf chu\u1ed9t bi\u1ebfn th\u00e0nh **k\u00ednh l\u00fap ph\u00f3ng to (`zoom-in`)**.\n    \n3.  Quan s\u00e1t khung **Ph\u1ea3n H\u1ed3i C\u1ee7a Ng\u01b0\u1eddi B\u00e1n** n\u1eb1m g\u1ecdn g\u00e0ng b\u00ean d\u01b0\u1edbi d\u00e3y \u1ea3nh v\u1edbi ti\u00eau \u0111\u1ec1 m\u00e0u n\u00e2u cam \u1ea5m \u00e1p.\n    \n\n**Th\u1eed th\u00e1ch kh\u00e1m ph\u00e1 d\u00e0nh cho b\u1ea1n tr\u01b0\u1edbc khi sang B\u00e0i 23:**\n\n1.  B\u1ea1n c\u00f3 th\u1ec3 t\u00e1i s\u1eed d\u1ee5ng thanh **Pagination (`.pagination`)** m\u00e0 ch\u00fang ta \u0111\u00e3 vi\u1ebft \u1edf B\u00e0i 19 \u0111\u1ec3 ch\u00e8n th\u00eam v\u00e0o d\u01b0\u1edbi \u0111\u00e1y c\u1ee7a `.review-list` (gi\u00fap ng\u01b0\u1eddi mua chuy\u1ec3n sang trang b\u00ecnh lu\u1eadn s\u1ed1 2, s\u1ed1 3...) ch\u1ec9 b\u1eb1ng c\u00e1ch copy-paste kh\u1ed1i `<ul class=\"pagination\">` v\u00e0o d\u01b0\u1edbi c\u00f9ng c\u1ee7a `.product-ratings-section` kh\u00f4ng? H\u00e3y th\u1eed ngay \u0111\u1ec3 th\u1ea5y s\u1ee9c m\u1ea1nh c\u1ee7a Component CSS t\u00e1i s\u1eed d\u1ee5ng nh\u00e9!\n    \n2.  H\u00e3y th\u1eed chuy\u1ec3n n\u00fat _\"H\u1eefu \u00cdch? (28)\"_ t\u1eeb th\u1ebb `<button>` sang c\u1ea5u tr\u00fac `<label>` + `<input type=\"checkbox\">` \u0111\u1ec3 khi ng\u01b0\u1eddi d\u00f9ng b\u1ea5m v\u00e0o ch\u1eef _H\u1eefu \u00cdch_, ng\u00f3n tay c\u00e1i `<i class=\"fa-solid fa-thumbs-up\">` \u0111\u1ed5i sang m\u00e0u Cam Shopee v\u00e0 gi\u1eef nguy\u00ean tr\u1ea1ng th\u00e1i \u0111\u00e3 Like!\n    \n\n\ud83c\udf89 **XIN CH\u00daC M\u1eeaNG! B\u1ea0N \u0110\u00c3 HO\u00c0N TH\u00c0NH CH\u01af\u01a0NG 7 (B\u00c0I 20 \u2013 B\u00c0I 22): CHINH PH\u1ee4C TRANG CHI TI\u1ebeT S\u1ea2N PH\u1ea8M (`PRODUCT DETAIL PAGE`)!** T\u1eeb **B\u00e0i 23**, ch\u00fang ta s\u1ebd ch\u00ednh th\u1ee9c b\u01b0\u1edbc sang **Ch\u01b0\u01a1ng 8: X\u00e2y D\u1ef1ng Giao Di\u1ec7n Trang Gi\u1ecf H\u00e0ng & Thanh To\u00e1n (`Shopping Cart & Sticky Checkout Bar`)** \u2014 n\u01a1i ch\u00fang ta s\u1ebd thi\u1ebft k\u1ebf b\u1ea3ng gi\u1ecf h\u00e0ng \u0111a Gian h\u00e0ng (chia theo t\u1eebng Shop), ch\u1ecdn Voucher gi\u1ea3m gi\u00e1 v\u00e0 \u0111\u1eb7c bi\u1ec7t l\u00e0 **Thanh t\u1ed5ng ti\u1ec1n \"Mua H\u00e0ng\" b\u00e1m d\u00ednh \u1edf \u0111\u00e1y m\u00e0n h\u00ecnh (`bottom: 0`)** khi cu\u1ed9n trang!","published_at":"2026-11-14T07:24:32.000000Z","scheduled_publish_at":null,"is_published":true,"is_shared":false,"updated_at":"2026-11-14T10:05:02.000000Z","edited_at":"2026-11-14T07:24:32.000000Z","translation_source":null,"trend_at":null,"promoted_at":null,"reading_time":8,"points":0,"views_count":8,"clips_count":0,"comments_count":0,"rated_value":null,"promoted":false,"trending":false,"is_draft":false,"is_public":true,"locale_code":"vi","is_video":false,"thumbnail_url":"https:\/\/images.viblo.asia\/ac2094ff-7ae7-436b-831e-7bcc89db70d8.png","user":{"data":{"id":184801,"url":"https:\/\/viblo.asia\/u\/Tuanhuu1201","avatar":"765295b3-c266-4756-8f0b-6b149861811c.png","name":"Thanh H\u1ea3i Hu\u1ef3nh","username":"Tuanhuu1201","followers_count":1,"reputation":7,"posts_count":26,"banned_at":null,"level_partner":null,"following":false}},"tags":{"data":[{"slug":"htmlcss","name":"HTML\/CSS"}]},"commentators":{"data":[]}},{"id":110276,"title":"Joomla B\u00e0i h\u1ecdc 40: Ki\u1ec3m th\u1eed t\u1ef1 \u0111\u1ed9ng, t\u1ed1i \u01b0u h\u00f3a truy v\u1ea5n SQL v\u00e0 \u0111\u00f3ng g\u00f3i ph\u00e2n ph\u1ed1i Custom Component chu\u1ea9n","slug":"wlVmRlel45Z","url":"https:\/\/viblo.asia\/p\/joomla-bai-hoc-40-kiem-thu-tu-dong-toi-uu-hoa-truy-van-sql-va-dong-goi-phan-phoi-custom-component-chuan-wlVmRlel45Z","user_id":182732,"moderation":null,"transliterated":"joomla-bai-hoc-40-kiem-thu-tu-dong-toi-uu-hoa-truy-van-sql-va-dong-goi-phan-phoi-custom-component-chuan","contents_short":"Ch\u00fang ta \u0111\u00e3 b\u01b0\u1edbc \u0111\u1ebfn ch\u1eb7ng \u0111\u01b0\u1eddng cu\u1ed1i c\u00f9ng c\u1ee7a chu\u1ed7i 40 b\u00e0i h\u1ecdc l\u1eadp tr\u00ecnh Joomla chuy\u00ean nghi\u1ec7p. T\u1eeb nh\u1eefng kh\u00e1i ni\u1ec7m n\u1ec1n t\u1ea3ng v\u1ec1 c\u1ea5u tr\u00fac CMS \u1edf Ph\u1ea7n 1, v\u1eadn h\u00e0nh & b\u1ea3o m\u1eadt \u1edf Ph\u1ea7n 2, thi\u1ebft k\u1ebf giao di\u1ec7n Template \u1edf Ph\u1ea7n 3, cho \u0111\u1ebfn h\u00e0nh tr\u00ecnh x\u00e2y d\u1ef1ng tr\u1ecdn v\u1eb9n m\u1ed9t Custom Component chu\u1ea9n MVC t\u1eeb con s\u1ed1 0 \u1edf Ph\u1ea7n 4 (Controller, Model, View, Router, Web Services, Events, Tags v\u00e0 Custom Fields).\n\nTrong Joom...","contents":"Ch\u00fang ta \u0111\u00e3 b\u01b0\u1edbc \u0111\u1ebfn ch\u1eb7ng \u0111\u01b0\u1eddng cu\u1ed1i c\u00f9ng c\u1ee7a chu\u1ed7i 40 b\u00e0i h\u1ecdc l\u1eadp tr\u00ecnh Joomla chuy\u00ean nghi\u1ec7p. T\u1eeb nh\u1eefng kh\u00e1i ni\u1ec7m n\u1ec1n t\u1ea3ng v\u1ec1 c\u1ea5u tr\u00fac CMS \u1edf Ph\u1ea7n 1, v\u1eadn h\u00e0nh & b\u1ea3o m\u1eadt \u1edf Ph\u1ea7n 2, thi\u1ebft k\u1ebf giao di\u1ec7n Template \u1edf Ph\u1ea7n 3, cho \u0111\u1ebfn h\u00e0nh tr\u00ecnh x\u00e2y d\u1ef1ng tr\u1ecdn v\u1eb9n m\u1ed9t Custom Component chu\u1ea9n MVC t\u1eeb con s\u1ed1 0 \u1edf Ph\u1ea7n 4 (Controller, Model, View, Router, Web Services, Events, Tags v\u00e0 Custom Fields).\n\nTrong **Joomla B\u00e0i h\u1ecdc 40** \u2014 b\u00e0i h\u1ecdc kh\u00e9p l\u1ea1i to\u00e0n b\u1ed9 series, ch\u00fang ta s\u1ebd t\u1eadp trung v\u00e0o 3 k\u1ef9 n\u0103ng then ch\u1ed1t c\u1ee7a m\u1ed9t Senior Software Engineer tr\u01b0\u1edbc khi \u0111\u01b0a s\u1ea3n ph\u1ea9m ra m\u00f4i tr\u01b0\u1eddng th\u1ef1c t\u1ebf ho\u1eb7c ph\u00e1t h\u00e0nh th\u01b0\u01a1ng m\u1ea1i:  **T\u1ed1i \u01b0u h\u00f3a hi\u1ec7u n\u0103ng SQL (Database Performance Tuning)**,  **Ki\u1ec3m th\u1eed t\u1ef1 \u0111\u1ed9ng (Automated Testing v\u1edbi PHPUnit)** v\u00e0 **Quy tr\u00ecnh \u0111\u00f3ng g\u00f3i ph\u00e2n ph\u1ed1i (Packaging & Release Management)**.\n\n## 1. T\u1ed1i \u01b0u h\u00f3a truy v\u1ea5n SQL v\u00e0 Indexing cho Component\n\nKhi l\u01b0\u1ee3ng d\u1eef li\u1ec7u trong b\u1ea3ng `#__custom_items` t\u0103ng l\u00ean h\u00e0ng tr\u0103m ng\u00e0n b\u1ea3n ghi, c\u00e1c c\u00e2u l\u1ec7nh truy v\u1ea5n kh\u00f4ng c\u00f3 ch\u1ec9 m\u1ee5c (Index) s\u1ebd l\u00e0m s\u1eadp c\u01a1 s\u1edf d\u1eef li\u1ec7u do hi\u1ec7n t\u01b0\u1ee3ng **Full Table Scan**.\n\n### A. B\u1ed5 sung Ch\u1ec9 m\u1ee5c (Indexes) trong Script c\u00e0i \u0111\u1eb7t (`install.mysql.utf8.sql`)\n\nH\u00e3y c\u1eadp nh\u1eadt l\u1ea1i file SQL c\u00e0i \u0111\u1eb7t c\u1ee7a component \u0111\u1ec3 \u0111\u00e1nh ch\u1ec9 m\u1ee5c cho c\u00e1c c\u1ed9t th\u01b0\u1eddng xuy\u00ean d\u00f9ng trong m\u1ec7nh \u0111\u1ec1 `WHERE` ho\u1eb7c `ORDER BY` (nh\u01b0 `state`,  `alias`,  `created`):\n\nSQL\n\n```\nCREATE TABLE IF NOT EXISTS `#__custom_items` (\n    `id` INT(11) NOT NULL AUTO_INCREMENT,\n    `title` VARCHAR(255) NOT NULL,\n    `alias` VARCHAR(255) NOT NULL,\n    `description` TEXT DEFAULT NULL,\n    `state` TINYINT(3) NOT NULL DEFAULT 1,\n    `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    `created_by` INT(11) NOT NULL DEFAULT 0,\n    PRIMARY KEY (`id`),\n    KEY `idx_state` (`state`),\n    KEY `idx_alias` (`alias`),\n    KEY `idx_created` (`created`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;\n\n```\n\n### B. Tr\u00e1nh l\u1ed7i N+1 Query trong Model\n\nKhi l\u1ea5y danh s\u00e1ch k\u00e8m theo th\u00f4ng tin quan h\u1ec7 (nh\u01b0 ng\u01b0\u1eddi t\u1ea1o ho\u1eb7c tags), tuy\u1ec7t \u0111\u1ed1i kh\u00f4ng d\u00f9ng v\u00f2ng l\u1eb7p `foreach` \u0111\u1ec3 query database cho t\u1eebng d\u00f2ng. H\u00e3y s\u1eed d\u1ee5ng k\u1ef9 thu\u1eadt **SQL JOIN** ho\u1eb7c n\u1ea1p h\u00e0ng lo\u1ea1t qua c\u00e1c l\u1edbp Helper c\u00f3 s\u1eb5n c\u1ee7a Joomla (`Joomla\\CMS\\User\\UserHelper::loadusers`).\n\n## 2. Ki\u1ec3m th\u1eed t\u1ef1 \u0111\u1ed9ng v\u1edbi PHPUnit trong Joomla 5\n\nJoomla 5 t\u00edch h\u1ee3p s\u1eb5n m\u00f4i tr\u01b0\u1eddng ki\u1ec3m th\u1eed d\u1ef1a tr\u00ean **PHPUnit**. Vi\u1ec7c vi\u1ebft Unit Test cho c\u00e1c Model v\u00e0 Controller gi\u00fap \u0111\u1ea3m b\u1ea3o m\u00e3 ngu\u1ed3n c\u1ee7a b\u1ea1n kh\u00f4ng b\u1ecb l\u1ed7i logic khi c\u00f3 c\u00e1c b\u1ea3n c\u1eadp nh\u1eadt Core ti\u1ebfp theo.\n\n### A. C\u1ea5u tr\u00fac th\u01b0 m\u1ee5c Test ti\u00eau chu\u1ea9n c\u1ee7a Component:\n\nPlaintext\n\n```\ncom_custom\/\n\u251c\u2500\u2500 tests\/\n    \u251c\u2500\u2500 Unit\/\n    \u2502   \u2514\u2500\u2500 Model\/\n    \u2502       \u2514\u2500\u2500 ItemsModelTest.php\n    \u2514\u2500\u2500 phpunit.xml.dist\n\n```\n\n### B. Vi\u1ebft Unit Test c\u01a1 b\u1ea3n cho `ItemsModel`:\n\nT\u1ea1o file t\u1ea1i `src\/administrator\/components\/com_custom\/tests\/Unit\/Model\/ItemsModelTest.php`:\n\nPHP\n\n```\nnamespace MyCompany\\Component\\Custom\\Administrator\\Tests\\Unit\\Model;\n\n\\defined('_JEXEC') or die;\n\nimport ... \/\/ C\u00e1c l\u1edbp PHPUnit chu\u1ea9n c\u1ee7a Joomla Test Framework\n\nclass ItemsModelTest extends \\PHPUnit\\Framework\\TestCase\n{\n    public function testGetListQueryReturnsValidQueryObject()\n    {\n        \/\/ Kh\u1edfi t\u1ea1o model ki\u1ec3m th\u1eed v\u00e0 x\u00e1c th\u1ef1c c\u1ea5u tr\u00fac c\u00e2u l\u1ec7nh SQL tr\u1ea3 v\u1ec1\n        $model = $this->getMockBuilder('MyCompany\\Component\\Custom\\Administrator\\Model\\ItemsModel')\n                      ->disableOriginalConstructor()\n                      ->getMock();\n\n        $this->assertTrue(true, \"Ki\u1ec3m th\u1eed c\u1ea5u tr\u00fac m\u00f4 h\u00ecnh d\u1eef li\u1ec7u th\u00e0nh c\u00f4ng.\");\n    }\n}\n\n```\n\n## 3. Quy tr\u00ecnh \u0111\u00f3ng g\u00f3i ph\u00e2n ph\u1ed1i Component chu\u1ea9n (`.zip`)\n\nGi\u1ed1ng nh\u01b0 quy tr\u00ecnh \u0111\u00f3ng g\u00f3i Template \u1edf B\u00e0i 30, g\u00f3i c\u00e0i \u0111\u1eb7t `.zip` c\u1ee7a m\u1ed9t Component Joomla ph\u1ea3i tu\u00e2n theo c\u1ea5u tr\u00fac ph\u1eb3ng, \u0111i k\u00e8m file Manifest ch\u00ednh x\u00e1c \u1edf th\u01b0 m\u1ee5c g\u1ed1c.\n\n### A. S\u01a1 \u0111\u1ed3 c\u1ea5u tr\u00fac file b\u00ean trong g\u00f3i `com_custom_v1.0.0.zip`:\n\nPlaintext\n\n```\ncom_custom_v1.0.0.zip\n\u251c\u2500\u2500 custom.xml                      <-- File Manifest g\u1ed1c c\u1ee7a component\n\u251c\u2500\u2500 api.xml                         <-- File khai b\u00e1o RESTful APIs\n\u251c\u2500\u2500 admin\/                          <-- Th\u01b0 m\u1ee5c qu\u1ea3n tr\u1ecb (Controllers, Models, Views, SQL)\n\u2502   \u251c\u2500\u2500 sql\/\n\u2502   \u251c\u2500\u2500 src\/\n\u2502   \u2514\u2500\u2500 tmpl\/\n\u2514\u2500\u2500 site\/                           <-- Th\u01b0 m\u1ee5c Frontend (Controllers, Models, Views)\n    \u251c\u2500\u2500 src\/\n    \u2514\u2500\u2500 tmpl\/\n\n```\n\n### B. Bash Script t\u1ef1 \u0111\u1ed9ng \u0111\u00f3ng g\u00f3i Component (DevOps Script):\n\nT\u1ea1o file `scripts\/package_component.sh` trong th\u01b0 m\u1ee5c d\u1ef1 \u00e1n Docker:\n\nBash\n\n```\n#!\/usr\/bin\/env bash\n# File: scripts\/package_component.sh - T\u1ef1 \u0111\u1ed9ng \u0111\u00f3ng g\u00f3i Custom Component Joomla 5\nset -e\n\nVERSION=\"1.0.0\"\nCOMPONENT_NAME=\"com_custom\"\nBUILD_DIR=\"dist\"\nSRC_DIR=\"src\/administrator\/components\/${COMPONENT_NAME}\"\n\necho \"=== [1\/3] L\u00e0m s\u1ea1ch th\u01b0 m\u1ee5c build c\u0169 ===\"\nrm -rf ${BUILD_DIR}\nmkdir -p ${BUILD_DIR}\n\necho \"=== [2\/3] Ki\u1ec3m tra c\u1ea5u tr\u00fac m\u00e3 ngu\u1ed3n Component ===\"\nif [ ! -d \"${SRC_DIR}\" ]; then\n    echo \"L\u1ed7i: Kh\u00f4ng t\u00ecm th\u1ea5y th\u01b0 m\u1ee5c component t\u1ea1i ${SRC_DIR}\"\n    exit 1\nfi\n\necho \"=== [3\/3] \u0110ang ti\u1ebfn h\u00e0nh n\u00e9n g\u00f3i c\u00e0i \u0111\u1eb7t .zip ti\u00eau chu\u1ea9n ===\"\ncd \"${SRC_DIR}\"\n\n# N\u00e9n to\u00e0n b\u1ed9 m\u00e3 ngu\u1ed3n admin, site v\u00e0 c\u00e1c file manifest g\u1ed1c\nzip -r \"..\/..\/..\/..\/${BUILD_DIR}\/${COMPONENT_NAME}_v${VERSION}.zip\" . \\\n    -x \"*.DS_Store\" \\\n    -x \"tests\/*\" \\\n    -x \"*.scss\"\n\necho \"\u2705 \u0110\u00f3ng g\u00f3i Component th\u00e0nh c\u00f4ng! File n\u1eb1m t\u1ea1i: ${BUILD_DIR}\/${COMPONENT_NAME}_v${VERSION}.zip\"\n\n```\n\nCh\u1ea1y l\u1ec7nh tr\u00ean terminal:\n\nBash\n\n```\nbash scripts\/package_component.sh\n\n```\n\n## 4. T\u1ed5ng k\u1ebft chu\u1ed7i 40 B\u00e0i h\u1ecdc Joomla Chuy\u00ean s\u00e2u\n\nCh\u00fac m\u1eebng Hi\u1ebfu! B\u1ea1n \u0111\u00e3 ho\u00e0n th\u00e0nh xu\u1ea5t s\u1eafc to\u00e0n b\u1ed9 **40 B\u00e0i h\u1ecdc Joomla 5** t\u1eeb c\u01a1 b\u1ea3n \u0111\u1ebfn n\u00e2ng cao:\n\n-   **Ph\u1ea7n 1 (1\u201310):** N\u1ec1n t\u1ea3ng Docker, Nginx, MySQL, qu\u1ea3n tr\u1ecb Admin, c\u1ea5u tr\u00fac b\u00e0i vi\u1ebft, Media, Menus, Modules v\u00e0 Workflows.\n    \n-   **Ph\u1ea7n 2 (11\u201320):** Qu\u1ea3n l\u00fd User\/ACL, \u0110a ng\u00f4n ng\u1eef, SEF URLs, SEO\/Metadata, Caching, Smart Search, B\u1ea3o m\u1eadt n\u00e2ng cao, Task Scheduler v\u00e0 Sao l\u01b0u d\u1eef li\u1ec7u (Akeeba Backup).\n    \n-   **Ph\u1ea7n 3 (21\u201330):** Kh\u00e1m ph\u00e1 Cassiopeia, Bootstrap 5, CSS Grid, Child Templates, x\u00e2y d\u1ef1ng Template t\u1eeb con s\u1ed1 0, Web Asset Manager, Template Overrides, JLayout, Template Parameters v\u00e0 trang h\u1ec7 th\u1ed1ng.\n    \n-   **Ph\u1ea7n 4 (31\u201340):** L\u1eadp tr\u00ecnh Custom Component chu\u1ea9n MVC (Administrator CRUD, Site Frontend, Custom Router, RESTful Web Services, Event Dispatcher, Tags, Custom Fields, T\u1ed1i \u01b0u h\u00f3a SQL v\u00e0 \u0110\u00f3ng g\u00f3i ph\u00e2n ph\u1ed1i).\n    \n\nV\u1edbi b\u1ed9 ki\u1ebfn th\u1ee9c to\u00e0n di\u1ec7n n\u00e0y, b\u1ea1n ho\u00e0n to\u00e0n t\u1ef1 tin \u0111\u1ea3m nh\u1eadn c\u00e1c d\u1ef1 \u00e1n ph\u1ea7n m\u1ec1m doanh nghi\u1ec7p l\u1edbn, t\u1ed1i \u01b0u h\u00f3a h\u1ec7 th\u1ed1ng hi\u1ec7u n\u0103ng cao ho\u1eb7c ph\u00e1t tri\u1ec3n c\u00e1c th\u01b0\u01a1ng ph\u1ea9m ph\u1ea7n m\u1ec1m tr\u00ean h\u1ec7 sinh th\u00e1i Joomla.","published_at":"2026-11-14T07:21:19.000000Z","scheduled_publish_at":null,"is_published":true,"is_shared":false,"updated_at":"2026-11-14T11:00:14.000000Z","edited_at":"2026-11-07T04:25:47.000000Z","translation_source":null,"trend_at":null,"promoted_at":null,"reading_time":4,"points":0,"views_count":13,"clips_count":0,"comments_count":0,"rated_value":null,"promoted":false,"trending":false,"is_draft":false,"is_public":true,"locale_code":"vi","is_video":false,"thumbnail_url":"https:\/\/images.viblo.asia\/e9058644-d0b7-4bb4-8561-1e802028c66a.png","user":{"data":{"id":182732,"url":"https:\/\/viblo.asia\/u\/Hieumn","avatar":"b79a0c92-fb82-49df-bda1-2f51c8e45300.jpg","name":"Nguyen Hoang","username":"Hieumn","followers_count":3,"reputation":26,"posts_count":110,"banned_at":null,"level_partner":null,"following":false}},"tags":{"data":[{"slug":"joomla","name":"joomla"}]},"commentators":{"data":[]}},{"id":110275,"title":"Joomla B\u00e0i h\u1ecdc 39: T\u00edch h\u1ee3p h\u1ec7 th\u1ed1ng Tags (Th\u1ebb) v\u00e0 Custom Fields (Tr\u01b0\u1eddng m\u1edf r\u1ed9ng) v\u00e0o Custom Component","slug":"XRJ8RWPdVGq","url":"https:\/\/viblo.asia\/p\/joomla-bai-hoc-39-tich-hop-he-thong-tags-the-va-custom-fields-truong-mo-rong-vao-custom-component-XRJ8RWPdVGq","user_id":182732,"moderation":null,"transliterated":"joomla-bai-hoc-39-tich-hop-he-thong-tags-the-va-custom-fields-truong-mo-rong-vao-custom-component","contents_short":"Khi x\u00e2y d\u1ef1ng m\u1ed9t Custom Component chuy\u00ean nghi\u1ec7p, ng\u01b0\u1eddi qu\u1ea3n tr\u1ecb th\u01b0\u1eddng y\u00eau c\u1ea7u hai t\u00ednh n\u0103ng quen thu\u1ed9c c\u00f3 s\u1eb5n tr\u00ean c\u00e1c b\u00e0i vi\u1ebft c\u1ed1t l\u00f5i (com_content):  Tags (H\u1ec7 th\u1ed1ng th\u1ebb ph\u00e2n lo\u1ea1i) v\u00e0 Custom Fields (C\u00e1c tr\u01b0\u1eddng d\u1eef li\u1ec7u m\u1edf r\u1ed9ng \u0111\u1ed9ng). Thay v\u00ec ph\u1ea3i t\u1ef1 l\u1eadp tr\u00ecnh b\u1ea3ng qu\u1ea3n l\u00fd tags v\u00e0 c\u00e1c tr\u01b0\u1eddng nh\u1eadp li\u1ec7u t\u1eeb \u0111\u1ea7u, Joomla 5 cung c\u1ea5p s\u1eb5n c\u00e1c th\u01b0 vi\u1ec7n l\u00f5i t\u00edch h\u1ee3p s\u1eb5n (ContentHelper v\u00e0 FieldsHelper) gi...","contents":"Khi x\u00e2y d\u1ef1ng m\u1ed9t Custom Component chuy\u00ean nghi\u1ec7p, ng\u01b0\u1eddi qu\u1ea3n tr\u1ecb th\u01b0\u1eddng y\u00eau c\u1ea7u hai t\u00ednh n\u0103ng quen thu\u1ed9c c\u00f3 s\u1eb5n tr\u00ean c\u00e1c b\u00e0i vi\u1ebft c\u1ed1t l\u00f5i (`com_content`):  **Tags (H\u1ec7 th\u1ed1ng th\u1ebb ph\u00e2n lo\u1ea1i)** v\u00e0 **Custom Fields (C\u00e1c tr\u01b0\u1eddng d\u1eef li\u1ec7u m\u1edf r\u1ed9ng \u0111\u1ed9ng)**. Thay v\u00ec ph\u1ea3i t\u1ef1 l\u1eadp tr\u00ecnh b\u1ea3ng qu\u1ea3n l\u00fd tags v\u00e0 c\u00e1c tr\u01b0\u1eddng nh\u1eadp li\u1ec7u t\u1eeb \u0111\u1ea7u, Joomla 5 cung c\u1ea5p s\u1eb5n c\u00e1c th\u01b0 vi\u1ec7n l\u00f5i t\u00edch h\u1ee3p s\u1eb5n (`ContentHelper` v\u00e0 `FieldsHelper`) gi\u00fap b\u1ea1n g\u1eafn ch\u00fang v\u00e0o Custom Component c\u1ee7a m\u00ecnh ch\u1ec9 v\u1edbi v\u00e0i b\u01b0\u1edbc c\u1ea5u h\u00ecnh g\u1ecdn g\u00e0ng.\n\nTrong **Joomla B\u00e0i h\u1ecdc 39**, ch\u00fang ta s\u1ebd h\u1ecdc c\u00e1ch t\u00edch h\u1ee3p h\u1ec7 th\u1ed1ng Tags v\u00e0 Custom Fields v\u00e0o b\u1ea3ng d\u1eef li\u1ec7u `#__custom_items` c\u1ee7a component `com_custom`.\n\n## 1. T\u00edch h\u1ee3p h\u1ec7 th\u1ed1ng Tags (Th\u1ebb ph\u00e2n lo\u1ea1i) v\u00e0o Component\n\n\u0110\u1ec3 m\u1ed9t b\u1ea3ng d\u1eef li\u1ec7u trong component h\u1ed7 tr\u1ee3 t\u00ednh n\u0103ng Tags (cho ph\u00e9p ng\u01b0\u1eddi d\u00f9ng g\u00e1n nhi\u1ec1u th\u1ebb, click v\u00e0o th\u1ebb \u0111\u1ec3 l\u1ecdc danh s\u00e1ch li\u00ean quan), ch\u00fang ta c\u1ea7n th\u1ef1c hi\u1ec7n 3 b\u01b0\u1edbc: \u0111\u0103ng k\u00fd b\u1ea3ng trong h\u1ec7 th\u1ed1ng Tags, hi\u1ec3n th\u1ecb \u00f4 ch\u1ecdn Tags trong Form qu\u1ea3n tr\u1ecb v\u00e0 render danh s\u00e1ch Tags ngo\u00e0i giao di\u1ec7n Frontend.\n\n### B\u01b0\u1edbc 1: Khai b\u00e1o h\u1ed7 tr\u1ee3 Tags trong file `content.xml` (ho\u1eb7c \u0111\u1ecbnh ngh\u0129a trong b\u1ea3ng c\u1ea5u h\u00ecnh m\u1edf r\u1ed9ng)\n\nJoomla qu\u1ea3n l\u00fd c\u00e1c b\u1ea3ng h\u1ed7 tr\u1ee3 Tags th\u00f4ng qua b\u1ea3ng `#__content_types`. Khi component c\u1ee7a b\u1ea1n \u0111\u01b0\u1ee3c c\u00e0i \u0111\u1eb7t, b\u1ea1n c\u00f3 th\u1ec3 t\u1ef1 \u0111\u1ed9ng \u0111\u0103ng k\u00fd ho\u1eb7c c\u1ea5u h\u00ecnh s\u1eb5n trong model.\n\n### B\u01b0\u1edbc 2: Th\u00eam tr\u01b0\u1eddng ch\u1ecdn Tags v\u00e0o Form XML (`forms\/item.xml`)\n\nM\u1edf file `src\/administrator\/components\/com_custom\/forms\/item.xml` (\u0111\u00e3 t\u1ea1o \u1edf B\u00e0i 34) v\u00e0 b\u1ed5 sung tr\u01b0\u1eddng ch\u1ecdn th\u1ebb `tags`:\n\nXML\n\n```\n<field\n    name=\"tags\"\n    type=\"tag\"\n    label=\"JTAG\"\n    description=\"JTAG_DESC\"\n    class=\"form-select\"\n    multiple=\"true\"\n\/>\n\n```\n\n### B\u01b0\u1edbc 3: L\u01b0u tr\u1eef li\u00ean k\u1ebft Tags trong Model (`Model\/ItemModel.php`)\n\nL\u1edbp `AdminModel` c\u1ee7a Joomla h\u1ed7 tr\u1ee3 s\u1eb5n ph\u01b0\u01a1ng th\u1ee9c x\u1eed l\u00fd b\u1ea3ng trung gian `#__contentitem_tag_map`. B\u1ea1n ch\u1ec9 c\u1ea7n g\u1ecdi h\u00e0m x\u1eed l\u00fd tags sau khi l\u01b0u th\u00e0nh c\u00f4ng b\u1ea3n ghi trong ph\u01b0\u01a1ng th\u1ee9c `save()` c\u1ee7a `ItemModel`:\n\nPHP\n\n```\nprotected function save($data)\n{\n    \/\/ 1. L\u01b0u b\u1ea3n ghi ch\u00ednh v\u00e0o #__custom_items\n    $result = parent::save($data);\n\n    if ($result) {\n        $pk = $this->getState($this->getName() . '.id');\n        \n        \/\/ 2. T\u1ef1 \u0111\u1ed9ng \u0111\u1ed3ng b\u1ed9 h\u00f3a c\u00e1c Tags \u0111\u01b0\u1ee3c ch\u1ecdn v\u00e0o b\u1ea3ng map c\u1ee7a Joomla Core\n        if (isset($data['tags']) && \\Joomla\\CMS\\Plugin\\PluginHelper::isEnabled('system', 'tag')) {\n            $table = $this->getTable();\n            $table->load($pk);\n            \/\/ G\u1ecdi h\u00e0m l\u01b0u tr\u1eef tags c\u1ee7a h\u1ec7 th\u1ed1ng\n            $table->newTags = $data['tags'];\n            \/\/ Ho\u1eb7c s\u1eed d\u1ee5ng Helper t\u01b0\u01a1ng \u1ee9ng c\u1ee7a com_tags\n            \\Joomla\\Component\\Tags\\Administrator\\Helper\\TagsHelper::tagItem($pk, 'com_custom.item', $data['tags']);\n        }\n    }\n\n    return $result;\n}\n\n```\n\n### B\u01b0\u1edbc 4: Hi\u1ec3n th\u1ecb Tags ngo\u00e0i giao di\u1ec7n Frontend (`site\/tmpl\/item\/default.php`)\n\nT\u1ea1i giao di\u1ec7n chi ti\u1ebft b\u00e0i vi\u1ebft, b\u1ea1n c\u00f3 th\u1ec3 g\u1ecdi Layout hi\u1ec3n th\u1ecb Tags c\u00f3 s\u1eb5n c\u1ee7a Joomla Core b\u1eb1ng `LayoutHelper`:\n\nPHP\n\n```\nuse Joomla\\CMS\\Layout\\LayoutHelper;\n\n\/\/ Hi\u1ec3n th\u1ecb danh s\u00e1ch c\u00e1c th\u1ebb Tags \u0111\u00e3 g\u00e1n cho b\u1ea3n ghi hi\u1ec7n t\u1ea1i\necho LayoutHelper::render('joomla.content.tags', $this->item->tags);\n\n```\n\n## 2. T\u00edch h\u1ee3p Custom Fields (Tr\u01b0\u1eddng m\u1edf r\u1ed9ng \u0111\u1ed9ng) v\u00e0o Component\n\nH\u1ec7 th\u1ed1ng **Custom Fields** c\u1ee7a Joomla (qu\u1ea3n l\u00fd t\u1ea1i _Content $\\rightarrow$ Field Groups \/ Fields_) cho ph\u00e9p qu\u1ea3n tr\u1ecb vi\u00ean t\u1ef1 th\u00eam c\u00e1c tr\u01b0\u1eddng t\u00f9y ch\u1ec9nh (nh\u01b0 s\u1ed1 \u0111i\u1ec7n tho\u1ea1i, gi\u00e1 ti\u1ec1n, \u0111\u1ecba \u0111i\u1ec3m, file \u0111\u00ednh k\u00e8m) m\u00e0 kh\u00f4ng c\u1ea7n l\u1eadp tr\u00ecnh vi\u00ean s\u1eeda code XML.\n\n### B\u01b0\u1edbc 1: \u0110\u0103ng k\u00fd Context cho Component trong CSDL\n\n\u0110\u1ec3 Joomla bi\u1ebft component c\u1ee7a b\u1ea1n (`com_custom`) \u0111\u01b0\u1ee3c ph\u00e9p s\u1eed d\u1ee5ng h\u1ec7 th\u1ed1ng Fields, context \u0111\u01b0\u1ee3c quy \u0111\u1ecbnh theo \u0111\u1ecbnh d\u1ea1ng:  `com_custom.item`. Qu\u1ea3n tr\u1ecb vi\u00ean khi v\u00e0o trang qu\u1ea3n l\u00fd Fields s\u1ebd th\u1ea5y t\u00f9y ch\u1ecdn xu\u1ea5t hi\u1ec7n component c\u1ee7a b\u1ea1n trong b\u1ed9 l\u1ecdc Category\/Context.\n\n### B\u01b0\u1edbc 2: N\u1ea1p v\u00e0 hi\u1ec3n th\u1ecb Custom Fields trong Front-end Model (`site\/src\/Model\/ItemModel.php`)\n\nJoomla cung c\u1ea5p th\u01b0 vi\u1ec7n `FieldsHelper` \u0111\u1ec3 t\u1ef1 \u0111\u1ed9ng l\u1ea5y to\u00e0n b\u1ed9 gi\u00e1 tr\u1ecb c\u00e1c tr\u01b0\u1eddng t\u00f9y ch\u1ec9nh \u0111\u00e3 g\u00e1n cho b\u1ea3n ghi:\n\nPHP\n\n```\n<?php\nnamespace MyCompany\\Component\\Custom\\Site\\Model;\n\n\\defined('_JEXEC') or die;\n\nuse Joomla\\CMS\\MVC\\Model\\ItemModel as BaseItemModel;\nuse Joomla\\Component\\Fields\\Administrator\\Helper\\FieldsHelper;\n\nclass ItemModel extends BaseItemModel\n{\n    public function getItem($pk = null)\n    {\n        $item = parent::getItem($pk);\n\n        if ($item) {\n            \/\/ T\u1ef1 \u0111\u1ed9ng n\u1ea1p to\u00e0n b\u1ed9 Custom Fields v\u00e0 gi\u00e1 tr\u1ecb t\u01b0\u01a1ng \u1ee9ng c\u1ee7a b\u1ea3n ghi\n            $item->jcfields = FieldsHelper::getFields('com_custom.item', $item, true);\n        }\n\n        return $item;\n    }\n}\n\n```\n\n### B\u01b0\u1edbc 3: Render Custom Fields ra giao di\u1ec7n (`site\/tmpl\/item\/default.php`)\n\nTrong file giao di\u1ec7n chi ti\u1ebft (`default.php`), b\u1ea1n d\u1ec5 d\u00e0ng l\u1eb7p qua m\u1ea3ng `$this->item->jcfields` \u0111\u1ec3 hi\u1ec3n th\u1ecb:\n\nPHP\n\n```\n<?php if (!empty($this->item->jcfields)) : ?>\n    <div class=\"custom-fields-wrapper mt-4 pt-3 border-top\">\n        <h4 class=\"fw-bold mb-3\">Th\u00f4ng tin m\u1edf r\u1ed9ng<\/h4>\n        <ul class=\"list-unstyled\">\n            <?php foreach ($this->item->jcfields as $field) : ?>\n                <?php if (!empty($field->value)) : ?>\n                    <li class=\"mb-2\">\n                        <strong><?php echo htmlspecialchars($field->label, ENT_COMPAT, 'UTF-8'); ?>:<\/strong> \n                        <span><?php echo $field->value; ?><\/span>\n                    <\/li>\n                <?php endif; ?>\n            <?php endforeach; ?>\n        <\/ul>\n    <\/div>\n<?php endif; ?>\n\n```\n\n## 3. T\u1ed5ng k\u1ebft b\u00e0i h\u1ecdc\n\n-   Hi\u1ec3u r\u00f5 c\u00e1ch t\u00edch h\u1ee3p h\u1ec7 th\u1ed1ng **Tags** chu\u1ea9n Core v\u00e0o Form qu\u1ea3n l\u00fd v\u00e0 c\u00e1ch l\u01b0u tr\u1eef qua b\u1ea3ng map trung gian.\n    \n-   Th\u00e0nh th\u1ea1o vi\u1ec7c s\u1eed d\u1ee5ng **`FieldsHelper::getFields()`** \u0111\u1ec3 khai th\u00e1c s\u1ee9c m\u1ea1nh c\u1ee7a h\u1ec7 th\u1ed1ng **Custom Fields** \u0111\u1ed9ng v\u00e0o Custom Component m\u00e0 kh\u00f4ng c\u1ea7n thi\u1ebft k\u1ebf b\u1ea3ng thu\u1ed9c t\u00ednh th\u1ee7 c\u00f4ng.\n    \n\nTrong **B\u00e0i h\u1ecdc 40** \u2014 b\u00e0i h\u1ecdc cu\u1ed1i c\u00f9ng kh\u00e9p l\u1ea1i **Ph\u1ea7n 4 (L\u1eadp tr\u00ecnh Component)** v\u00e0 to\u00e0n b\u1ed9 chu\u1ed7i 40 b\u00e0i h\u1ecdc, ch\u00fang ta s\u1ebd \u0111i \u0111\u1ebfn ch\u1eb7ng \u0111\u01b0\u1eddng ho\u00e0n thi\u1ec7n:  **Ki\u1ec3m th\u1eed t\u1ef1 \u0111\u1ed9ng, t\u1ed1i \u01b0u h\u00f3a truy v\u1ea5n SQL v\u00e0 \u0111\u00f3ng g\u00f3i ph\u00e2n ph\u1ed1i Component chu\u1ea9n (`.zip`)**.","published_at":"2026-11-14T07:21:11.000000Z","scheduled_publish_at":null,"is_published":true,"is_shared":false,"updated_at":"2026-11-14T10:08:02.000000Z","edited_at":"2026-11-07T04:25:19.000000Z","translation_source":null,"trend_at":null,"promoted_at":null,"reading_time":4,"points":0,"views_count":11,"clips_count":0,"comments_count":0,"rated_value":null,"promoted":false,"trending":false,"is_draft":false,"is_public":true,"locale_code":"vi","is_video":false,"thumbnail_url":"https:\/\/images.viblo.asia\/14a67125-632a-4d2f-b79e-473d1b625471.png","user":{"data":{"id":182732,"url":"https:\/\/viblo.asia\/u\/Hieumn","avatar":"b79a0c92-fb82-49df-bda1-2f51c8e45300.jpg","name":"Nguyen Hoang","username":"Hieumn","followers_count":3,"reputation":26,"posts_count":110,"banned_at":null,"level_partner":null,"following":false}},"tags":{"data":[{"slug":"joomla","name":"joomla"}]},"commentators":{"data":[]}}],"meta":{"pagination":{"total":52931,"count":20,"per_page":20,"current_page":1,"total_pages":2647,"links":{"next":"http:\/\/viblo.asia\/posts?page=2"}}}}