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 @@ +