diff --git a/css/components/code-block.css b/css/components/code-block.css
new file mode 100644
index 0000000..4461c1c
--- /dev/null
+++ b/css/components/code-block.css
@@ -0,0 +1,61 @@
+/* Code block with copy button */
+.code-block-wrapper {
+ position: relative;
+}
+
+.code-block-wrapper pre {
+ margin: 0;
+}
+
+/* Preserve default margins for wrapped pre elements not in homepage */
+.two-column-layout .code-block-wrapper,
+.blog-entry .code-block-wrapper,
+.one-column-layout .code-block-wrapper:not(.homepage-content .code-block-wrapper) {
+ margin: var(--space-lg) 0;
+}
+
+.copy-code-button {
+ position: absolute;
+ top: 0;
+ right: 0.75rem;
+ width: 36px;
+ height: 36px;
+ opacity: 0;
+ transition: opacity 200ms ease-in-out, background-color 200ms ease-in-out, transform 200ms ease-in-out;
+ cursor: pointer;
+ background-color: rgba(255, 255, 255, 0.95);
+ border: 1px solid #d1d5db;
+ border-radius: var(--radius-md);
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ z-index: 100;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0;
+}
+
+.copy-code-button svg {
+ width: 20px;
+ height: 20px;
+ stroke: #475569;
+ stroke-width: 2;
+}
+
+.code-block-wrapper:hover .copy-code-button {
+ opacity: 1;
+}
+
+.copy-code-button:hover {
+ background-color: #f3f4f6;
+ transform: scale(1.05);
+}
+
+.copy-code-button.copied {
+ background-color: var(--color-phel-primary);
+ border-color: var(--color-phel-primary);
+}
+
+.copy-code-button.copied svg {
+ stroke: white;
+}
+
diff --git a/css/components/dark-mode.css b/css/components/dark-mode.css
index e6449c1..6e03cbe 100644
--- a/css/components/dark-mode.css
+++ b/css/components/dark-mode.css
@@ -598,11 +598,6 @@
color: var(--color-dark-text-accent);
}
-.dark .badge-success {
- background: var(--color-dark-current-badge-bg);
- color: var(--color-dark-current-badge-text);
-}
-
.dark .badge-warning {
background: rgba(245, 158, 11, 0.2);
color: #fbbf24;
@@ -781,6 +776,29 @@
background: rgba(191, 164, 255, 0.3);
}
+/* Dark mode code block copy button */
+.dark .copy-code-button {
+ background-color: rgba(42, 49, 60, 0.95);
+ border-color: #475569;
+}
+
+.dark .copy-code-button svg {
+ stroke: #cbd5e1;
+}
+
+.dark .copy-code-button:hover {
+ background-color: #2f3741;
+}
+
+.dark .copy-code-button.copied {
+ background-color: var(--color-dark-text-accent);
+ border-color: var(--color-dark-text-accent);
+}
+
+.dark .copy-code-button.copied svg {
+ stroke: white;
+}
+
/* Homepage Title Accent - Dark Mode */
.dark .homepage-title-accent {
color: var(--color-dark-text-accent);
diff --git a/css/components/documentation.css b/css/components/documentation.css
index f228690..9189ce0 100644
--- a/css/components/documentation.css
+++ b/css/components/documentation.css
@@ -536,65 +536,6 @@ h6:hover .zola-anchor {
opacity: 1;
}
-/* Info boxes / Callouts */
-@utility callout {
- padding: var(--space-lg) var(--space-xl);
- margin: var(--space-xl) 0;
- border-left: 4px solid;
- border-radius: var(--radius-lg);
- box-shadow: var(--shadow-sm);
- position: relative;
-}
-
-.callout::before {
- content: '';
- position: absolute;
- top: var(--space-lg);
- left: var(--space-lg);
- font-size: var(--text-2xl);
- line-height: 1;
-}
-
-.callout-info {
- background: rgba(59, 130, 246, 0.05);
- border-left-color: #3b82f6;
-}
-
-.callout-info::before {
- content: 'ℹ️';
-}
-
-.callout-warning {
- background: rgba(245, 158, 11, 0.05);
- border-left-color: #f59e0b;
-}
-
-.callout-warning::before {
- content: '⚠️';
-}
-
-.callout-success {
- background: rgba(16, 185, 129, 0.05);
- border-left-color: #10b981;
-}
-
-.callout-success::before {
- content: '✅';
-}
-
-.callout-error {
- background: rgba(239, 68, 68, 0.05);
- border-left-color: #ef4444;
-}
-
-.callout-error::before {
- content: '❌';
-}
-
-.callout p:first-of-type {
- margin-left: 2em;
-}
-
/* API documentation specific */
.api-doc {
margin: var(--space-2xl) 0;
@@ -635,12 +576,7 @@ h6:hover .zola-anchor {
line-height: var(--leading-relaxed);
}
-/* Code examples with copy button */
-.code-block-wrapper {
- position: relative;
- margin: var(--space-xl) 0;
-}
-
+/* Code examples with header */
.code-block-header {
display: flex;
justify-content: space-between;
@@ -655,28 +591,10 @@ h6:hover .zola-anchor {
color: var(--color-light-text-secondary);
}
-.code-block-wrapper pre {
- margin: 0;
+.code-block-header + pre {
border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}
-.copy-code-button {
- padding: var(--space-xs) var(--space-md);
- font-size: var(--text-xs);
- font-weight: 600;
- color: var(--color-light-link);
- background: transparent;
- border: 1px solid var(--color-light-link);
- border-radius: var(--radius-sm);
- cursor: pointer;
- transition: all var(--duration-fast) var(--ease-out);
-}
-
-.copy-code-button:hover {
- background: var(--color-light-surface-hover);
- transform: translateY(-1px);
-}
-
.copy-code-button:active {
transform: translateY(0);
}
diff --git a/css/components/features.css b/css/components/features.css
index 568055e..ab8b157 100644
--- a/css/components/features.css
+++ b/css/components/features.css
@@ -76,6 +76,12 @@
margin-right: -50vw;
}
+.homepage-code-section .code-block-wrapper {
+ margin-top: var(--space-2xl);
+ margin-bottom: var(--space-2xl);
+}
+
+.homepage-code-section .code-block-wrapper pre,
.homepage-code-section pre {
margin-left: 0;
margin-right: 0;
@@ -157,7 +163,7 @@
padding: var(--space-xl) 0;
}
- .homepage-code-section > * {
+ .homepage-code-section > *:not(.code-block-wrapper) {
max-width: var(--container-content);
margin-left: auto;
margin-right: auto;
@@ -165,10 +171,17 @@
padding-right: var(--space-xl);
}
- .homepage-code-section pre {
+ .homepage-code-section .code-block-wrapper {
max-width: var(--container-content);
margin-left: auto;
margin-right: auto;
+ padding-left: 0;
+ padding-right: 0;
+ }
+
+ .homepage-code-section .code-block-wrapper pre {
+ margin-left: auto;
+ margin-right: auto;
}
}
diff --git a/scripts/concat-tailwind.js b/scripts/concat-tailwind.js
index 53b2bca..19209de 100644
--- a/scripts/concat-tailwind.js
+++ b/scripts/concat-tailwind.js
@@ -16,6 +16,7 @@ const parts = [
'css/components/documentation.css',
'css/components/blog.css',
'css/components/features.css',
+ 'css/components/code-block.css',
'css/components/dark-mode.css',
];
diff --git a/static/copy-code.js b/static/copy-code.js
new file mode 100644
index 0000000..10499bf
--- /dev/null
+++ b/static/copy-code.js
@@ -0,0 +1,61 @@
+document.addEventListener('DOMContentLoaded', function() {
+ // Add copy functionality to all pre blocks
+ const preBlocks = document.querySelectorAll('pre');
+
+ preBlocks.forEach(pre => {
+ // Wrap pre in a container if not already wrapped
+ if (!pre.parentElement.classList.contains('code-block-wrapper')) {
+ const wrapper = document.createElement('div');
+ wrapper.className = 'code-block-wrapper';
+ pre.parentNode.insertBefore(wrapper, pre);
+ wrapper.appendChild(pre);
+ }
+
+ // Create copy button element
+ const copyButton = document.createElement('button');
+ copyButton.className = 'copy-code-button';
+ copyButton.setAttribute('aria-label', 'Copy code to clipboard');
+ copyButton.innerHTML = `
+
+ `;
+
+ // Add button to wrapper
+ pre.parentElement.appendChild(copyButton);
+
+ // Handle copy
+ copyButton.addEventListener('click', async () => {
+ const code = pre.querySelector('code');
+ const text = code ? code.textContent : pre.textContent;
+
+ try {
+ await navigator.clipboard.writeText(text);
+
+ // Add copied state
+ copyButton.classList.add('copied');
+
+ // Change icon to checkmark
+ copyButton.innerHTML = `
+
+ `;
+
+ // Reset after 2 seconds
+ setTimeout(() => {
+ copyButton.classList.remove('copied');
+ copyButton.innerHTML = `
+
+ `;
+ }, 2000);
+ } catch (err) {
+ console.error('Failed to copy code:', err);
+ }
+ });
+ });
+});
diff --git a/templates/base.html b/templates/base.html
index 5c6abdd..573e161 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -45,6 +45,7 @@
+