diff --git a/README.md b/README.md
index 63bf6a53..3404f1c0 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,7 @@ It is primarily Astro, Tailwind and Typescript, with a very small amount of Soli
- β
Markdown support
- β
MDX Support (components in your markdown)
- β
Searchable content (posts and projects)
+- β
Code Blocks - copy to clipboard
## π― Lighthouse score

@@ -61,7 +62,6 @@ Replace npm with your package manager of choice. `npm`, `pnpm`, `yarn`, `bun`, e
## πΊοΈ Roadmap
A few features I plan to implement
-- β¬ Code Blocks - copy to clipboard
- β¬ Article Pages - Table of Contents
- β¬ Article Pages - Share on social media
diff --git a/public/copy.svg b/public/copy.svg
new file mode 100644
index 00000000..1adb0944
--- /dev/null
+++ b/public/copy.svg
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/public/js/copy.js b/public/js/copy.js
new file mode 100644
index 00000000..f9cff70f
--- /dev/null
+++ b/public/js/copy.js
@@ -0,0 +1,45 @@
+const codeBlocks = document.querySelectorAll('pre:has(code)');
+
+//add copy btn to every code block on the dom
+codeBlocks.forEach((code) => {
+ //button icon
+ const use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
+ use.setAttribute('href', '/copy.svg#empty');
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ svg.classList.add('copy-svg');
+ svg.appendChild(use);
+
+ //create button
+ const btn = document.createElement('button');
+ btn.appendChild(svg);
+ btn.classList.add('copy-btn');
+ btn.addEventListener('click', (e) => copyCode(e));
+
+ //container to fix copy button
+ const container = document.createElement('div');
+ container.classList.add('copy-cnt');
+ container.appendChild(btn);
+
+ //add to code block
+ code.classList.add('relative');
+ code.appendChild(container);
+});
+
+/**
+* @param {MouseEvent} event
+*/
+function copyCode(event) {
+ let codeBlock = getChildByTagName(event.currentTarget.parentElement.parentElement, 'CODE')
+ navigator.clipboard.writeText(codeBlock.innerText)
+ const use = getChildByTagName(getChildByTagName(event.currentTarget, 'svg'), 'use');
+ use.setAttribute('href', '/copy.svg#filled')
+ setTimeout(() => {
+ if (use) {
+ use.setAttribute('href', '/copy.svg#empty')
+ }
+ }, 100);
+}
+
+function getChildByTagName(element, tagName) {
+ return Array.from(element.children).find((child) => child.tagName === tagName);
+}
diff --git a/src/components/BaseHead.astro b/src/components/BaseHead.astro
index eca0dc1e..b1826827 100644
--- a/src/components/BaseHead.astro
+++ b/src/components/BaseHead.astro
@@ -53,6 +53,7 @@ const { title, description, image = "/open-graph.jpg" } = Astro.props
+
diff --git a/src/styles/global.css b/src/styles/global.css
index 9ef386f2..7e0ef18f 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -2,6 +2,10 @@
@tailwind components;
@tailwind utilities;
+:root{
+ --copy-btn-margin:10px;
+}
+
@layer base {
@font-face {
font-family: "Atkinson";
@@ -147,4 +151,18 @@ article img {
#meteors .shower.ul {
@apply rotate-315;
-}
\ No newline at end of file
+}
+
+.copy-cnt{
+ @apply absolute w-full;
+ top:var(--copy-btn-margin);
+}
+.copy-btn {
+ @apply w-[30px] fixed;
+ left:calc(100% - var(--copy-btn-margin));
+ transform: translateX(-100%);
+}
+
+.copy-svg {
+ @apply w-full aspect-square text-white opacity-70 hover:opacity-90;
+}