From 4128be112eda0c9c24b337f4a5f80ce0fbaa4c79 Mon Sep 17 00:00:00 2001
From: Claude
Date: Thu, 13 Nov 2025 03:19:50 +0000
Subject: [PATCH 1/8] feat: enhance blog code blocks and add admonition support
- Add syntax-highlighted code blocks with copy button functionality
- Improve code block color contrast with better Shiki themes (github-light/github-dark-dimmed)
- Add support for Docusaurus-style admonitions (:::tip:::, :::warning:::, etc.)
- Replace emoji icons with professional SVG icons from simple-icons for social share buttons
- Add hover effects and visual feedback for copy buttons
- Improve accessibility with proper aria-labels
Technical changes:
- Install remark-directive and remark-admonitions for MDX admonition support
- Add custom remark plugin to handle admonition rendering
- Add CSS styles for 5 admonition types (tip, warning, danger, note, info)
- Implement JavaScript code block copy functionality
- Update BlogPostLayout to use SocialIcon component for Twitter/LinkedIn
---
astro.config.mjs | 35 ++-
package-lock.json | 356 ++++++++++++++++++++++++++++++-
package.json | 6 +-
src/layouts/BlogPostLayout.astro | 65 +++++-
src/styles/global.css | 77 ++++++-
5 files changed, 523 insertions(+), 16 deletions(-)
diff --git a/astro.config.mjs b/astro.config.mjs
index 4a409f4..37ae6e9 100644
--- a/astro.config.mjs
+++ b/astro.config.mjs
@@ -2,6 +2,28 @@ import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';
import tailwind from '@astrojs/tailwind';
+import remarkDirective from 'remark-directive';
+import { visit } from 'unist-util-visit';
+import { h } from 'hastscript';
+
+// Custom remark plugin to handle admonitions (:::tip:::, :::warning:::, etc.)
+function remarkAdmonitions() {
+ return (tree) => {
+ visit(tree, (node) => {
+ if (
+ node.type === 'textDirective' ||
+ node.type === 'leafDirective' ||
+ node.type === 'containerDirective'
+ ) {
+ const data = node.data || (node.data = {});
+ const tagName = node.type === 'textDirective' ? 'span' : 'div';
+
+ data.hName = tagName;
+ data.hProperties = h(tagName, { class: `admonition admonition-${node.name}` }).properties;
+ }
+ });
+ };
+}
// https://astro.build/config
export default defineConfig({
@@ -21,13 +43,22 @@ export default defineConfig({
}),
],
markdown: {
+ remarkPlugins: [remarkDirective, remarkAdmonitions],
shikiConfig: {
- theme: 'github-dark',
themes: {
light: 'github-light',
- dark: 'github-dark',
+ dark: 'github-dark-dimmed',
},
wrap: true,
+ transformers: [
+ {
+ name: 'add-copy-button',
+ pre(node) {
+ // Add a data attribute to enable copy button
+ this.addClassToHast(node, 'code-block-wrapper');
+ },
+ },
+ ],
},
},
});
diff --git a/package-lock.json b/package-lock.json
index 4af4ea4..ac0323c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,9 +14,13 @@
"@astrojs/sitemap": "^3.1.6",
"@astrojs/tailwind": "^5.1.1",
"astro": "^4.15.11",
+ "hastscript": "^9.0.1",
+ "remark-admonitions": "^1.2.1",
+ "remark-directive": "^4.0.0",
"simple-icons": "^15.20.0",
"tailwindcss": "^3.4.1",
- "typescript": "^5.6.2"
+ "typescript": "^5.6.2",
+ "unist-util-visit": "^5.0.0"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.15",
@@ -3979,6 +3983,29 @@
"node": ">=8"
}
},
+ "node_modules/is-buffer": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
+ "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/is-core-module": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
@@ -4434,6 +4461,27 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/mdast-util-directive": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz",
+ "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "ccount": "^2.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "parse-entities": "^4.0.0",
+ "stringify-entities": "^4.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/mdast-util-find-and-replace": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
@@ -4799,6 +4847,25 @@
"micromark-util-types": "^2.0.0"
}
},
+ "node_modules/micromark-extension-directive": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz",
+ "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-factory-whitespace": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "parse-entities": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/micromark-extension-gfm": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
@@ -6456,6 +6523,284 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/remark-admonitions": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/remark-admonitions/-/remark-admonitions-1.2.1.tgz",
+ "integrity": "sha512-Ji6p68VDvD+H1oS95Fdx9Ar5WA2wcDA4kwrrhVU7fGctC6+d3uiMICu7w7/2Xld+lnU7/gi+432+rRbup5S8ow==",
+ "license": "MIT",
+ "dependencies": {
+ "rehype-parse": "^6.0.2",
+ "unified": "^8.4.2",
+ "unist-util-visit": "^2.0.1"
+ }
+ },
+ "node_modules/remark-admonitions/node_modules/@types/unist": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
+ "license": "MIT"
+ },
+ "node_modules/remark-admonitions/node_modules/bail": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz",
+ "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/remark-admonitions/node_modules/ccount": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz",
+ "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/remark-admonitions/node_modules/comma-separated-tokens": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz",
+ "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/remark-admonitions/node_modules/hast-util-from-parse5": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-5.0.3.tgz",
+ "integrity": "sha512-gOc8UB99F6eWVWFtM9jUikjN7QkWxB3nY0df5Z0Zq1/Nkwl5V4hAAsl0tmwlgWl/1shlTF8DnNYLO8X6wRV9pA==",
+ "license": "MIT",
+ "dependencies": {
+ "ccount": "^1.0.3",
+ "hastscript": "^5.0.0",
+ "property-information": "^5.0.0",
+ "web-namespaces": "^1.1.2",
+ "xtend": "^4.0.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-admonitions/node_modules/hast-util-parse-selector": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
+ "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-admonitions/node_modules/hastscript": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-5.1.2.tgz",
+ "integrity": "sha512-WlztFuK+Lrvi3EggsqOkQ52rKbxkXL3RwB6t5lwoa8QLMemoWfBuL43eDrwOamJyR7uKQKdmKYaBH1NZBiIRrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "comma-separated-tokens": "^1.0.0",
+ "hast-util-parse-selector": "^2.0.0",
+ "property-information": "^5.0.0",
+ "space-separated-tokens": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-admonitions/node_modules/is-plain-obj": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/remark-admonitions/node_modules/parse5": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
+ "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
+ "license": "MIT"
+ },
+ "node_modules/remark-admonitions/node_modules/property-information": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz",
+ "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==",
+ "license": "MIT",
+ "dependencies": {
+ "xtend": "^4.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/remark-admonitions/node_modules/rehype-parse": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-6.0.2.tgz",
+ "integrity": "sha512-0S3CpvpTAgGmnz8kiCyFLGuW5yA4OQhyNTm/nwPopZ7+PI11WnGl1TTWTGv/2hPEe/g2jRLlhVVSsoDH8waRug==",
+ "license": "MIT",
+ "dependencies": {
+ "hast-util-from-parse5": "^5.0.0",
+ "parse5": "^5.0.0",
+ "xtend": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-admonitions/node_modules/space-separated-tokens": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz",
+ "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/remark-admonitions/node_modules/trough": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz",
+ "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/remark-admonitions/node_modules/unified": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/unified/-/unified-8.4.2.tgz",
+ "integrity": "sha512-JCrmN13jI4+h9UAyKEoGcDZV+i1E7BLFuG7OsaDvTXI5P0qhHX+vZO/kOhz9jn8HGENDKbwSeB0nVOg4gVStGA==",
+ "license": "MIT",
+ "dependencies": {
+ "bail": "^1.0.0",
+ "extend": "^3.0.0",
+ "is-plain-obj": "^2.0.0",
+ "trough": "^1.0.0",
+ "vfile": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-admonitions/node_modules/unist-util-is": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz",
+ "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-admonitions/node_modules/unist-util-stringify-position": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz",
+ "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2.0.2"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-admonitions/node_modules/unist-util-visit": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz",
+ "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "unist-util-is": "^4.0.0",
+ "unist-util-visit-parents": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-admonitions/node_modules/unist-util-visit-parents": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz",
+ "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "unist-util-is": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-admonitions/node_modules/vfile": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz",
+ "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "is-buffer": "^2.0.0",
+ "unist-util-stringify-position": "^2.0.0",
+ "vfile-message": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-admonitions/node_modules/vfile-message": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz",
+ "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "unist-util-stringify-position": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-admonitions/node_modules/web-namespaces": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-1.1.4.tgz",
+ "integrity": "sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/remark-directive": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-4.0.0.tgz",
+ "integrity": "sha512-7sxn4RfF1o3izevPV1DheyGDD6X4c9hrGpfdUpm7uC++dqrnJxIZVkk7CoKqcLm0VUMAuOol7Mno3m6g8cfMuA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-directive": "^3.0.0",
+ "micromark-extension-directive": "^4.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/remark-gfm": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
@@ -8157,6 +8502,15 @@
"node": ">=8"
}
},
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
"node_modules/xxhash-wasm": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz",
diff --git a/package.json b/package.json
index d47c984..01c2db1 100644
--- a/package.json
+++ b/package.json
@@ -16,9 +16,13 @@
"@astrojs/sitemap": "^3.1.6",
"@astrojs/tailwind": "^5.1.1",
"astro": "^4.15.11",
+ "hastscript": "^9.0.1",
+ "remark-admonitions": "^1.2.1",
+ "remark-directive": "^4.0.0",
"simple-icons": "^15.20.0",
"tailwindcss": "^3.4.1",
- "typescript": "^5.6.2"
+ "typescript": "^5.6.2",
+ "unist-util-visit": "^5.0.0"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.15",
diff --git a/src/layouts/BlogPostLayout.astro b/src/layouts/BlogPostLayout.astro
index 3288022..9989bbf 100644
--- a/src/layouts/BlogPostLayout.astro
+++ b/src/layouts/BlogPostLayout.astro
@@ -3,6 +3,7 @@ import BaseLayout from './BaseLayout.astro';
import TableOfContents from '@components/TableOfContents.astro';
import Giscus from '@components/Giscus.astro';
import LanguageBadge from '@components/LanguageBadge.astro';
+import SocialIcon from '@components/SocialIcon.astro';
import { formatDate, getCategoryColor } from '@utils/helpers';
import type { CollectionEntry } from 'astro:content';
@@ -102,23 +103,31 @@ const { title, description, date, updated, author, category, tags, language } =
href={`https://twitter.com/intent/tweet?text=${encodeURIComponent(title)}&url=${encodeURIComponent(Astro.url.href)}`}
target="_blank"
rel="noopener noreferrer"
- class="btn-secondary text-sm"
+ class="btn-secondary text-sm flex items-center gap-2"
+ aria-label="Share on Twitter"
>
- 🐦 Twitter
+
+ Twitter
- 💼 LinkedIn
+
+ LinkedIn
- 🔗 Copy Link
+
+
+
+ Copy Link
@@ -155,15 +164,51 @@ const { title, description, date, updated, author, category, tags, language } =
document.getElementById('copy-link')?.addEventListener('click', async () => {
await navigator.clipboard.writeText(window.location.href);
const button = document.getElementById('copy-link');
- const originalText = button?.textContent || '';
- if (button) {
- button.textContent = '✓ Copied!';
+ const span = button?.querySelector('span');
+ if (span) {
+ const originalText = span.textContent || '';
+ span.textContent = '✓ Copied!';
setTimeout(() => {
- button.textContent = originalText;
+ span.textContent = originalText;
}, 2000);
}
});
+ // Add copy buttons to code blocks
+ document.querySelectorAll('.prose pre').forEach((pre) => {
+ // Create copy button
+ const button = document.createElement('button');
+ button.className = 'copy-button';
+ button.textContent = 'Copy';
+ button.setAttribute('aria-label', 'Copy code to clipboard');
+
+ // Add click handler
+ button.addEventListener('click', async () => {
+ const code = pre.querySelector('code');
+ if (code) {
+ const text = code.textContent || '';
+ try {
+ await navigator.clipboard.writeText(text);
+ button.textContent = 'Copied!';
+ button.classList.add('copied');
+ setTimeout(() => {
+ button.textContent = 'Copy';
+ button.classList.remove('copied');
+ }, 2000);
+ } catch (err) {
+ console.error('Failed to copy code:', err);
+ button.textContent = 'Failed';
+ setTimeout(() => {
+ button.textContent = 'Copy';
+ }, 2000);
+ }
+ }
+ });
+
+ // Append button to pre element
+ pre.appendChild(button);
+ });
+
// Smooth scroll for anchor links
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
anchor.addEventListener('click', function (this: HTMLAnchorElement, e: Event) {
diff --git a/src/styles/global.css b/src/styles/global.css
index bee62b1..f83d1c5 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -133,11 +133,34 @@
}
.prose pre {
- @apply bg-slate-900 dark:bg-slate-950 rounded-lg p-4 overflow-x-auto my-6;
+ @apply bg-slate-100 dark:bg-slate-900 rounded-lg p-4 overflow-x-auto my-6 relative;
+ position: relative;
}
.prose pre code {
- @apply bg-transparent text-slate-100 p-0;
+ @apply bg-transparent p-0;
+}
+
+/* Code block wrapper with copy button */
+.prose .code-block-wrapper {
+ @apply relative;
+}
+
+.prose pre:hover .copy-button {
+ @apply opacity-100;
+}
+
+.prose .copy-button {
+ @apply absolute top-2 right-2 px-3 py-1.5 bg-slate-700 hover:bg-slate-600 dark:bg-slate-600 dark:hover:bg-slate-500 text-white text-xs rounded opacity-0 transition-all duration-200 cursor-pointer font-sans;
+ z-index: 10;
+}
+
+.prose .copy-button:hover {
+ @apply shadow-lg;
+}
+
+.prose .copy-button.copied {
+ @apply bg-green-600 hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-600;
}
.prose blockquote {
@@ -187,6 +210,56 @@
@apply bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300;
}
+/* Admonitions (:::tip:::, :::warning:::, etc.) */
+.prose .admonition {
+ @apply my-6 p-4 rounded-lg border-l-4;
+}
+
+.prose .admonition-tip {
+ @apply bg-blue-50 dark:bg-blue-950 border-blue-500 text-blue-900 dark:text-blue-100;
+}
+
+.prose .admonition-tip::before {
+ content: '💡 Tip';
+ @apply block font-bold mb-2 text-blue-700 dark:text-blue-300;
+}
+
+.prose .admonition-warning {
+ @apply bg-yellow-50 dark:bg-yellow-950 border-yellow-500 text-yellow-900 dark:text-yellow-100;
+}
+
+.prose .admonition-warning::before {
+ content: '⚠️ Warning';
+ @apply block font-bold mb-2 text-yellow-700 dark:text-yellow-300;
+}
+
+.prose .admonition-danger {
+ @apply bg-red-50 dark:bg-red-950 border-red-500 text-red-900 dark:text-red-100;
+}
+
+.prose .admonition-danger::before {
+ content: '🚨 Danger';
+ @apply block font-bold mb-2 text-red-700 dark:text-red-300;
+}
+
+.prose .admonition-note {
+ @apply bg-gray-50 dark:bg-gray-950 border-gray-500 text-gray-900 dark:text-gray-100;
+}
+
+.prose .admonition-note::before {
+ content: '📝 Note';
+ @apply block font-bold mb-2 text-gray-700 dark:text-gray-300;
+}
+
+.prose .admonition-info {
+ @apply bg-cyan-50 dark:bg-cyan-950 border-cyan-500 text-cyan-900 dark:text-cyan-100;
+}
+
+.prose .admonition-info::before {
+ content: 'ℹ️ Info';
+ @apply block font-bold mb-2 text-cyan-700 dark:text-cyan-300;
+}
+
/* Category badges */
.category-badge {
@apply inline-block px-3 py-1 text-sm font-medium rounded-full transition-colors;
From 0fdf46ab28e70d344b0a3378a06d5c97e920d5dc Mon Sep 17 00:00:00 2001
From: Claude
Date: Thu, 13 Nov 2025 03:42:33 +0000
Subject: [PATCH 2/8] feat: replace Unicode emoji flags with SVG flag icons
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Create new FlagIcon component with SVG-based US and Vietnam flags
- Update LanguageBadge to use SVG flags instead of Unicode emoji (🇺🇸, 🇻🇳)
- Update LanguageSwitcher to display flag icons instead of text (EN/VI)
- SVG flags render consistently across all browsers and operating systems
- Improves accessibility and visual consistency
This fixes the issue where Unicode emoji flags don't render properly
on some browsers and operating systems.
---
src/components/FlagIcon.astro | 35 +++++++++++++++++++++++++++
src/components/LanguageBadge.astro | 12 +++++----
src/components/LanguageSwitcher.astro | 8 +++---
3 files changed, 46 insertions(+), 9 deletions(-)
create mode 100644 src/components/FlagIcon.astro
diff --git a/src/components/FlagIcon.astro b/src/components/FlagIcon.astro
new file mode 100644
index 0000000..6777b08
--- /dev/null
+++ b/src/components/FlagIcon.astro
@@ -0,0 +1,35 @@
+---
+interface Props {
+ country: 'us' | 'vn';
+ class?: string;
+}
+
+const { country, class: className = 'w-4 h-4' } = Astro.props;
+---
+
+{country === 'us' && (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+)}
+
+{country === 'vn' && (
+
+
+
+
+)}
diff --git a/src/components/LanguageBadge.astro b/src/components/LanguageBadge.astro
index 468ff1d..36c732e 100644
--- a/src/components/LanguageBadge.astro
+++ b/src/components/LanguageBadge.astro
@@ -1,15 +1,17 @@
---
-import { getLanguageInfo } from '@utils/helpers';
+import FlagIcon from './FlagIcon.astro';
interface Props {
language: 'en' | 'vi';
}
const { language } = Astro.props;
-const info = getLanguageInfo(language);
+const country = language === 'en' ? 'us' : 'vn';
+const label = language === 'en' ? 'English' : 'Tiếng Việt';
+const badgeClass = language === 'en' ? 'lang-badge-en' : 'lang-badge-vi';
---
-
- {info.flag}
- {info.label === 'English' ? 'EN' : 'VI'}
+
+
+ {label}
diff --git a/src/components/LanguageSwitcher.astro b/src/components/LanguageSwitcher.astro
index 4e19f41..ebf0658 100644
--- a/src/components/LanguageSwitcher.astro
+++ b/src/components/LanguageSwitcher.astro
@@ -1,5 +1,6 @@
---
import { languages, defaultLang, getLangFromUrl, getLocalizedPath } from '@i18n/utils';
+import FlagIcon from './FlagIcon.astro';
const currentLang = getLangFromUrl(Astro.url);
const currentPath = Astro.url.pathname.replace(new RegExp(`^/${currentLang}`), '') || '/';
@@ -7,6 +8,7 @@ const currentPath = Astro.url.pathname.replace(new RegExp(`^/${currentLang}`), '
// Generate alternate language URL
const alternateLang = currentLang === 'en' ? 'vi' : 'en';
const alternateUrl = getLocalizedPath(currentPath, alternateLang);
+const alternateCountry = alternateLang === 'en' ? 'us' : 'vn';
---
@@ -16,11 +18,9 @@ const alternateUrl = getLocalizedPath(currentPath, alternateLang);
aria-label={`Switch to ${languages[alternateLang]}`}
title={`Switch to ${languages[alternateLang]}`}
>
-
-
-
+
- {currentLang === 'en' ? 'VI' : 'EN'}
+ {languages[alternateLang]}
From 27f9dea4440ba944b38170d6260fbdcaabd0e511 Mon Sep 17 00:00:00 2001
From: Claude
Date: Thu, 13 Nov 2025 03:45:04 +0000
Subject: [PATCH 3/8] feat: simplify language switcher to show only flag icon
- Remove language text label from switcher button
- Display only the flag icon for cleaner UI
- Increase flag size from w-5 to w-6 for better visibility
- Keep aria-label and title for accessibility
---
src/components/LanguageSwitcher.astro | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/src/components/LanguageSwitcher.astro b/src/components/LanguageSwitcher.astro
index ebf0658..a62028a 100644
--- a/src/components/LanguageSwitcher.astro
+++ b/src/components/LanguageSwitcher.astro
@@ -14,14 +14,11 @@ const alternateCountry = alternateLang === 'en' ? 'us' : 'vn';
From 9a06cd4148133377143d8f5ce960ac0330987839 Mon Sep 17 00:00:00 2001
From: Claude
Date: Thu, 13 Nov 2025 03:51:12 +0000
Subject: [PATCH 4/8] feat: replace all Unicode emoji flags with SVG icons
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Add Japan flag (jp) support to FlagIcon component
- Update blog index pages to use FlagIcon component instead of emoji
- Remove all Unicode emoji flags (🇺🇸, 🇻🇳, 🇯🇵) from codebase
- Update i18n translation files (en.ts, vi.ts) to remove emoji flags
- Update constants.ts to remove emoji flags from tagline and country
Benefits:
- Consistent rendering across all browsers and operating systems
- No more missing/broken emoji flags on older systems
- Better accessibility with SVG icons
- Cleaner, more professional appearance
Files modified:
- src/components/FlagIcon.astro (added Japan flag)
- src/pages/blog/index.astro (use FlagIcon component)
- src/pages/vi/blog/index.astro (use FlagIcon component)
- src/i18n/en.ts (removed emoji from bio, location, language labels)
- src/i18n/vi.ts (removed emoji from bio, location, language labels)
- src/utils/constants.ts (removed emoji from tagline and education country)
---
src/components/FlagIcon.astro | 9 ++++++++-
src/i18n/en.ts | 8 ++++----
src/i18n/vi.ts | 8 ++++----
src/pages/blog/index.astro | 4 +++-
src/pages/vi/blog/index.astro | 4 +++-
src/utils/constants.ts | 4 ++--
6 files changed, 24 insertions(+), 13 deletions(-)
diff --git a/src/components/FlagIcon.astro b/src/components/FlagIcon.astro
index 6777b08..ca7b046 100644
--- a/src/components/FlagIcon.astro
+++ b/src/components/FlagIcon.astro
@@ -1,6 +1,6 @@
---
interface Props {
- country: 'us' | 'vn';
+ country: 'us' | 'vn' | 'jp';
class?: string;
}
@@ -33,3 +33,10 @@ const { country, class: className = 'w-4 h-4' } = Astro.props;
)}
+
+{country === 'jp' && (
+
+
+
+
+)}
diff --git a/src/i18n/en.ts b/src/i18n/en.ts
index 3778015..8a5306b 100644
--- a/src/i18n/en.ts
+++ b/src/i18n/en.ts
@@ -27,7 +27,7 @@ export default {
title: 'About Me',
subtitle: 'AI Engineer with Master\'s degree from JAIST, specializing in production ML systems',
greeting: 'Hello there!',
- bio1: 'I\'m Hieu Nguyen, an AI Engineer from Vietnam 🇻🇳 with a passion for transforming research into production-ready systems.',
+ bio1: 'I\'m Hieu Nguyen, an AI Engineer from Vietnam with a passion for transforming research into production-ready systems.',
bio2: 'My expertise lies in Natural Language Processing, RAG systems, and Large Language Models. I specialize in building scalable AI solutions that solve real-world problems.',
bio3: 'I hold a {degree} from {school} in Japan, where I deepened my knowledge in information retrieval and machine learning.',
bio4: 'When I\'m not coding, you\'ll find me exploring self-hosted solutions, contributing to open source, or writing about AI/ML on my blog.',
@@ -87,7 +87,7 @@ export default {
quickInfo: {
title: 'Quick Info',
location: 'Location',
- locationValue: 'Hanoi, Vietnam 🇻🇳',
+ locationValue: 'Hanoi, Vietnam',
role: 'Role',
roleValue: 'AI Engineer',
education: 'Education',
@@ -124,8 +124,8 @@ export default {
readArticle: 'Read Article',
emptyState: 'No posts found',
clearFilters: 'Clear filters',
- english: '🇺🇸 English',
- vietnamese: '🇻🇳 Vietnamese',
+ english: 'English',
+ vietnamese: 'Vietnamese',
filter: {
all: 'All',
aiml: 'AI/ML',
diff --git a/src/i18n/vi.ts b/src/i18n/vi.ts
index 2b26145..f64192e 100644
--- a/src/i18n/vi.ts
+++ b/src/i18n/vi.ts
@@ -27,7 +27,7 @@ export default {
title: 'Về Tôi',
subtitle: 'Kỹ sư AI với bằng Thạc sĩ từ JAIST, chuyên về hệ thống ML production',
greeting: 'Xin chào!',
- bio1: 'Tôi là Hiếu Nguyễn, một Kỹ sư AI từ Việt Nam 🇻🇳 với đam mê chuyển đổi nghiên cứu thành các hệ thống production.',
+ bio1: 'Tôi là Hiếu Nguyễn, một Kỹ sư AI từ Việt Nam với đam mê chuyển đổi nghiên cứu thành các hệ thống production.',
bio2: 'Chuyên môn của tôi nằm ở Xử lý Ngôn ngữ Tự nhiên, hệ thống RAG và Mô hình Ngôn ngữ Lớn. Tôi chuyên xây dựng các giải pháp AI có khả năng mở rộng để giải quyết các vấn đề thực tế.',
bio3: 'Tôi có bằng {degree} từ {school} ở Nhật Bản, nơi tôi đã nâng cao kiến thức về truy xuất thông tin và học máy.',
bio4: 'Khi không code, bạn sẽ thấy tôi khám phá các giải pháp self-hosted, đóng góp cho open source, hoặc viết về AI/ML trên blog.',
@@ -87,7 +87,7 @@ export default {
quickInfo: {
title: 'Thông Tin Nhanh',
location: 'Vị Trí',
- locationValue: 'Hà Nội, Việt Nam 🇻🇳',
+ locationValue: 'Hà Nội, Việt Nam',
role: 'Vai Trò',
roleValue: 'Kỹ Sư AI',
education: 'Học Vấn',
@@ -124,8 +124,8 @@ export default {
readArticle: 'Đọc Bài Viết',
emptyState: 'Không tìm thấy bài viết',
clearFilters: 'Xóa bộ lọc',
- english: '🇺🇸 English',
- vietnamese: '🇻🇳 Tiếng Việt',
+ english: 'English',
+ vietnamese: 'Tiếng Việt',
filter: {
all: 'Tất Cả',
aiml: 'AI/ML',
diff --git a/src/pages/blog/index.astro b/src/pages/blog/index.astro
index 2df4fb3..05f0c19 100644
--- a/src/pages/blog/index.astro
+++ b/src/pages/blog/index.astro
@@ -2,6 +2,7 @@
import { getCollection } from 'astro:content';
import BaseLayout from '@layouts/BaseLayout.astro';
import BlogCard from '@components/BlogCard.astro';
+import FlagIcon from '@components/FlagIcon.astro';
import { readingTime } from '@utils/helpers';
import { getLangFromUrl, useTranslations } from '@i18n/utils';
@@ -87,7 +88,8 @@ const featuredPost = sortedPosts[0];
- {featuredPost.data.language === 'vi' ? '🇻🇳 VI' : '🇺🇸 EN'}
+
+ {featuredPost.data.language === 'vi' ? 'VI' : 'EN'}
{featuredPost.data.category}
diff --git a/src/pages/vi/blog/index.astro b/src/pages/vi/blog/index.astro
index 2df4fb3..05f0c19 100644
--- a/src/pages/vi/blog/index.astro
+++ b/src/pages/vi/blog/index.astro
@@ -2,6 +2,7 @@
import { getCollection } from 'astro:content';
import BaseLayout from '@layouts/BaseLayout.astro';
import BlogCard from '@components/BlogCard.astro';
+import FlagIcon from '@components/FlagIcon.astro';
import { readingTime } from '@utils/helpers';
import { getLangFromUrl, useTranslations } from '@i18n/utils';
@@ -87,7 +88,8 @@ const featuredPost = sortedPosts[0];
- {featuredPost.data.language === 'vi' ? '🇻🇳 VI' : '🇺🇸 EN'}
+
+ {featuredPost.data.language === 'vi' ? 'VI' : 'EN'}
{featuredPost.data.category}
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 4596b8c..aa76deb 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -4,7 +4,7 @@ export const SITE = {
url: 'https://behitek.com',
author: 'Hieu Nguyen',
email: 'hello@behitek.com',
- tagline: 'AI Engineer • Vietnam 🇻🇳',
+ tagline: 'AI Engineer • Vietnam',
bio: 'I love bringing AI into production to solve real problems, with a focus on natural language processing, retrieval augmented generation (RAG), LLMs, and information retrieval.',
};
@@ -40,7 +40,7 @@ export const NAV_LINKS = [
export const EDUCATION = {
degree: "Master's in Information Science",
school: 'Japan Advanced Institute of Science and Technology (JAIST)',
- country: '🇯🇵 Japan',
+ country: 'Japan',
};
export const TECH_STACK = {
From 6ad7d551ec1cb5c3e93a0fab91578d18e75ada04 Mon Sep 17 00:00:00 2001
From: Claude
Date: Thu, 13 Nov 2025 04:29:14 +0000
Subject: [PATCH 5/8] feat: make language flag icons rectangular (3:2 aspect
ratio)
- Change flag icons from square (24x24) to rectangular (30x20) viewBox
- Use 3:2 aspect ratio which matches standard country flag proportions
- Update default class from w-4 h-4 to w-6 h-4 for better visibility
- Adjust star positions and counts for US flag to fit new dimensions
- Make flags look more natural and recognizable
---
src/components/FlagIcon.astro | 54 ++++++++++++++++++++---------------
1 file changed, 31 insertions(+), 23 deletions(-)
diff --git a/src/components/FlagIcon.astro b/src/components/FlagIcon.astro
index ca7b046..58afdb4 100644
--- a/src/components/FlagIcon.astro
+++ b/src/components/FlagIcon.astro
@@ -4,39 +4,47 @@ interface Props {
class?: string;
}
-const { country, class: className = 'w-4 h-4' } = Astro.props;
+const { country, class: className = 'w-6 h-4' } = Astro.props;
---
{country === 'us' && (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
)}
{country === 'vn' && (
-
-
-
+
+
+
)}
{country === 'jp' && (
-
-
-
+
+
+
)}
From ac74eb2095dc972c98b08bc00dde94c3bc2429dc Mon Sep 17 00:00:00 2001
From: Claude
Date: Thu, 13 Nov 2025 04:39:06 +0000
Subject: [PATCH 6/8] fix: use rectangular size for language switcher flag icon
- Change from w-6 h-6 (square) to w-9 h-6 (rectangular)
- Maintains 3:2 aspect ratio to match flag icon viewBox
- Prevents flag from being cropped in language switcher
---
src/components/LanguageSwitcher.astro | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/LanguageSwitcher.astro b/src/components/LanguageSwitcher.astro
index a62028a..4c20353 100644
--- a/src/components/LanguageSwitcher.astro
+++ b/src/components/LanguageSwitcher.astro
@@ -18,7 +18,7 @@ const alternateCountry = alternateLang === 'en' ? 'us' : 'vn';
aria-label={`Switch to ${languages[alternateLang]}`}
title={`Switch to ${languages[alternateLang]}`}
>
-
+
From 6498d93f66724b6ec88c719679d1c2ce24b5eaa6 Mon Sep 17 00:00:00 2001
From: Claude
Date: Thu, 13 Nov 2025 05:06:42 +0000
Subject: [PATCH 7/8] revert: change flag icons back to square format
- Revert from 30x20 rectangular to 24x24 square viewBox
- Change LanguageSwitcher from w-9 h-6 back to w-6 h-6
- Change default flag size from w-6 h-4 back to w-5 h-5
- Square format works better for consistent display
---
src/components/FlagIcon.astro | 54 ++++++++++++---------------
src/components/LanguageSwitcher.astro | 2 +-
2 files changed, 24 insertions(+), 32 deletions(-)
diff --git a/src/components/FlagIcon.astro b/src/components/FlagIcon.astro
index 58afdb4..9e22c62 100644
--- a/src/components/FlagIcon.astro
+++ b/src/components/FlagIcon.astro
@@ -4,47 +4,39 @@ interface Props {
class?: string;
}
-const { country, class: className = 'w-6 h-4' } = Astro.props;
+const { country, class: className = 'w-5 h-5' } = Astro.props;
---
{country === 'us' && (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
)}
{country === 'vn' && (
-
-
-
+
+
+
)}
{country === 'jp' && (
-
-
-
+
+
+
)}
diff --git a/src/components/LanguageSwitcher.astro b/src/components/LanguageSwitcher.astro
index 4c20353..a62028a 100644
--- a/src/components/LanguageSwitcher.astro
+++ b/src/components/LanguageSwitcher.astro
@@ -18,7 +18,7 @@ const alternateCountry = alternateLang === 'en' ? 'us' : 'vn';
aria-label={`Switch to ${languages[alternateLang]}`}
title={`Switch to ${languages[alternateLang]}`}
>
-
+
From 5592481feda71ca61c9eb0307f614f2908d6fd12 Mon Sep 17 00:00:00 2001
From: Claude
Date: Thu, 13 Nov 2025 05:26:16 +0000
Subject: [PATCH 8/8] feat: replace flag icon with text in language switcher
- Remove flag icon from language switcher
- Show simple 'EN' or 'VI' text instead
- Simpler, cleaner, and more reliable than flag icons
- Adjust padding from p-2 to px-3 py-2 for better text spacing
---
src/components/LanguageSwitcher.astro | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/components/LanguageSwitcher.astro b/src/components/LanguageSwitcher.astro
index a62028a..758b7ae 100644
--- a/src/components/LanguageSwitcher.astro
+++ b/src/components/LanguageSwitcher.astro
@@ -1,6 +1,5 @@
---
import { languages, defaultLang, getLangFromUrl, getLocalizedPath } from '@i18n/utils';
-import FlagIcon from './FlagIcon.astro';
const currentLang = getLangFromUrl(Astro.url);
const currentPath = Astro.url.pathname.replace(new RegExp(`^/${currentLang}`), '') || '/';
@@ -8,17 +7,18 @@ const currentPath = Astro.url.pathname.replace(new RegExp(`^/${currentLang}`), '
// Generate alternate language URL
const alternateLang = currentLang === 'en' ? 'vi' : 'en';
const alternateUrl = getLocalizedPath(currentPath, alternateLang);
-const alternateCountry = alternateLang === 'en' ? 'us' : 'vn';
---