diff --git a/internal/build/assets/js/main.js b/internal/build/assets/js/main.js
deleted file mode 100644
index 3695c68..0000000
--- a/internal/build/assets/js/main.js
+++ /dev/null
@@ -1,127 +0,0 @@
-(function() {
- 'use strict';
-
- // Live reload when studio saves settings
- const channel = new BroadcastChannel('writekit-studio');
- channel.onmessage = function(event) {
- if (event.data.type === 'settings-changed') {
- location.reload();
- }
- };
-
- document.addEventListener('DOMContentLoaded', initSearch);
-
- function initSearch() {
- const trigger = document.getElementById('search-trigger');
- const modal = document.getElementById('search-modal');
- const backdrop = modal?.querySelector('.search-modal-backdrop');
- const input = document.getElementById('search-input');
- const results = document.getElementById('search-results');
-
- if (!trigger || !modal || !input || !results) return;
-
- let debounceTimer;
-
- function open() {
- modal.classList.add('active');
- document.body.style.overflow = 'hidden';
- input.value = '';
- results.innerHTML = '';
- setTimeout(() => input.focus(), 10);
- }
-
- function close() {
- modal.classList.remove('active');
- document.body.style.overflow = '';
- }
-
- trigger.addEventListener('click', open);
- backdrop.addEventListener('click', close);
-
- document.addEventListener('keydown', function(e) {
- if (e.key === '/' && !modal.classList.contains('active') &&
- !['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName)) {
- e.preventDefault();
- open();
- }
- if (e.key === 'Escape' && modal.classList.contains('active')) {
- close();
- }
- });
-
- input.addEventListener('input', function() {
- const query = this.value.trim();
- clearTimeout(debounceTimer);
-
- if (query.length < 2) {
- results.innerHTML = '';
- return;
- }
-
- debounceTimer = setTimeout(() => search(query), 150);
- });
-
- input.addEventListener('keydown', function(e) {
- const items = results.querySelectorAll('.search-result');
- const focused = results.querySelector('.search-result.focused');
-
- if (e.key === 'ArrowDown') {
- e.preventDefault();
- if (!focused && items.length) {
- items[0].classList.add('focused');
- } else if (focused?.nextElementSibling) {
- focused.classList.remove('focused');
- focused.nextElementSibling.classList.add('focused');
- }
- } else if (e.key === 'ArrowUp') {
- e.preventDefault();
- if (focused?.previousElementSibling) {
- focused.classList.remove('focused');
- focused.previousElementSibling.classList.add('focused');
- }
- } else if (e.key === 'Enter' && focused) {
- e.preventDefault();
- const link = focused.querySelector('a');
- if (link) window.location.href = link.href;
- }
- });
-
- async function search(query) {
- try {
- const res = await fetch('/api/reader/search?q=' + encodeURIComponent(query));
- const data = await res.json();
-
- if (!data || data.length === 0) {
- results.innerHTML = '
No results found
';
- return;
- }
-
- results.innerHTML = data.map(r => `
-
- `).join('');
- } catch (e) {
- results.innerHTML = 'Search failed
';
- }
- }
-
- function highlight(text, query) {
- if (!text) return '';
- const escaped = escapeHtml(text);
- const tokens = query.split(/\s+/).filter(t => t.length > 0);
- if (!tokens.length) return escaped;
- const pattern = tokens.map(t => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
- return escaped.replace(new RegExp(`(${pattern})`, 'gi'), '$1');
- }
-
- function escapeHtml(text) {
- const div = document.createElement('div');
- div.textContent = text;
- return div.innerHTML;
- }
- }
-})();
diff --git a/internal/build/templates/base.html b/internal/build/templates/base.html
deleted file mode 100644
index 2effa31..0000000
--- a/internal/build/templates/base.html
+++ /dev/null
@@ -1,78 +0,0 @@
-
-
-
-
-
- {{.Title}}
-
-
-
- {{if .NoIndex}}{{end}}
-
-
-
-
-
-
- {{if .OGImage}}{{end}}
-
-
-
-
-
-
- {{if .OGImage}}{{end}}
-
-
-
-
- {{if .FontURL}}{{end}}
-
-
-
-
-
- {{if .StructuredData}}
-
- {{end}}
-
-
-
-
-
- {{template "content" .}}
-
-
-
-
-
-
-
-
-
-
Press ESC to close
-
-
-
-
-
-
- {{block "scripts" .}}{{end}}
-
-
diff --git a/internal/build/assets/assets.go b/internal/tenant/assets/assets.go
similarity index 81%
rename from internal/build/assets/assets.go
rename to internal/tenant/assets/assets.go
index a7eed10..a8c055c 100644
--- a/internal/build/assets/assets.go
+++ b/internal/tenant/assets/assets.go
@@ -6,6 +6,8 @@ import (
"net/http"
)
+// Embedded static assets for tenant blogs
+//
//go:embed css js
var staticFS embed.FS
diff --git a/internal/build/assets/css/style.css b/internal/tenant/assets/css/style.css
similarity index 85%
rename from internal/build/assets/css/style.css
rename to internal/tenant/assets/css/style.css
index a52612e..5382977 100644
--- a/internal/build/assets/css/style.css
+++ b/internal/tenant/assets/css/style.css
@@ -392,7 +392,7 @@ main {
margin: 0;
}
-.prose pre {
+.prose pre:not(.chroma) {
margin: var(--content-spacing) 0;
padding: 1.125rem 1.25rem;
background: var(--bg-secondary);
@@ -404,6 +404,13 @@ main {
line-height: 1.6;
}
+.prose pre.chroma {
+ font-family: var(--font-mono);
+ font-size: 0.875rem;
+ line-height: 1.6;
+ overflow-x: auto;
+}
+
.prose code {
font-family: var(--font-mono);
font-size: 0.875em;
@@ -419,6 +426,64 @@ main {
font-size: inherit;
}
+/* Enhanced Code Block */
+.code-block {
+ margin: var(--content-spacing) 0;
+ border: 1px solid var(--border);
+ border-radius: 0.5rem;
+ overflow: hidden;
+ background: var(--bg-secondary);
+}
+
+.code-block pre {
+ margin: 0;
+ border: none;
+ border-radius: 0;
+ padding: 1rem 1.25rem;
+}
+
+.code-header {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.625rem 1rem;
+ background: var(--bg);
+ border-bottom: 1px solid var(--border);
+ font-size: 0.8125rem;
+ color: var(--text-muted);
+}
+
+.code-icon {
+ width: 1rem;
+ height: 1rem;
+ flex-shrink: 0;
+}
+
+.code-title {
+ flex: 1;
+ font-family: var(--font-mono);
+ font-size: 0.8125rem;
+}
+
+.code-copy {
+ padding: 0.25rem 0.625rem;
+ font-size: 0.75rem;
+ font-weight: 500;
+ color: var(--text-muted);
+ background: var(--bg-secondary);
+ border: 1px solid var(--border);
+ border-radius: 0.25rem;
+ cursor: pointer;
+ transition: all 0.15s;
+ font-family: var(--font-body);
+}
+
+.code-copy:hover {
+ color: var(--text);
+ background: var(--bg);
+ border-color: var(--text-muted);
+}
+
.prose img {
max-width: 100%;
height: auto;
@@ -512,147 +577,6 @@ main {
font-weight: 500;
}
-/* Search */
-.search-trigger {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- padding: 0.375rem 0.75rem;
- border: 1px solid var(--border);
- border-radius: 0.375rem;
- background: var(--bg);
- color: var(--text-muted);
- cursor: pointer;
- font-size: 0.875rem;
- transition: border-color 0.15s;
-}
-
-.search-trigger:hover {
- border-color: color-mix(in srgb, var(--accent) 50%, var(--border));
-}
-
-.search-trigger kbd {
- padding: 0.125rem 0.375rem;
- background: var(--bg-secondary);
- border-radius: 0.25rem;
- font-family: var(--font-mono);
- font-size: 0.6875rem;
-}
-
-.search-modal {
- display: none;
- position: fixed;
- inset: 0;
- z-index: 100;
-}
-
-.search-modal.active {
- display: block;
-}
-
-.search-modal-backdrop {
- position: absolute;
- inset: 0;
- background: rgba(0, 0, 0, 0.4);
- backdrop-filter: blur(2px);
-}
-
-.search-modal-content {
- position: absolute;
- top: 15%;
- left: 50%;
- transform: translateX(-50%);
- width: 90%;
- max-width: 520px;
- background: var(--bg);
- border-radius: 0.5rem;
- box-shadow: 0 20px 40px -8px rgba(0, 0, 0, 0.2);
- overflow: hidden;
-}
-
-#search-input {
- width: 100%;
- padding: 1rem 1.25rem;
- border: none;
- font-size: 1rem;
- outline: none;
- background: var(--bg);
- color: var(--text);
-}
-
-#search-input::placeholder {
- color: var(--text-muted);
-}
-
-.search-results {
- max-height: 320px;
- overflow-y: auto;
- border-top: 1px solid var(--border);
-}
-
-.search-result {
- border-bottom: 1px solid var(--border);
-}
-
-.search-result:last-child {
- border-bottom: none;
-}
-
-.search-result a {
- display: block;
- padding: 0.875rem 1.25rem;
- color: inherit;
-}
-
-.search-result a:hover {
- text-decoration: none;
-}
-
-.search-result:hover, .search-result.focused {
- background: var(--bg-secondary);
-}
-
-.search-result-title {
- font-weight: 500;
- font-size: 0.9375rem;
-}
-
-.search-result-snippet {
- font-size: 0.8125rem;
- color: var(--text-muted);
- margin-top: 0.25rem;
- line-height: 1.5;
-}
-
-.search-result mark {
- background: color-mix(in srgb, var(--accent) 25%, transparent);
- color: inherit;
- border-radius: 0.125rem;
- padding: 0 0.125rem;
-}
-
-.search-hint {
- padding: 0.625rem 1.25rem;
- font-size: 0.75rem;
- color: var(--text-muted);
- text-align: center;
- border-top: 1px solid var(--border);
-}
-
-.search-hint kbd {
- padding: 0.125rem 0.375rem;
- background: var(--bg-secondary);
- border-radius: 0.25rem;
- font-family: var(--font-mono);
-}
-
-.search-no-results {
- padding: 1.5rem 1.25rem;
- text-align: center;
- color: var(--text-muted);
- font-size: 0.9375rem;
-}
-
/* Comment Form */
.comment-form textarea {
width: 100%;
diff --git a/internal/tenant/assets/js/tenant-blog.js b/internal/tenant/assets/js/tenant-blog.js
new file mode 100644
index 0000000..661d189
--- /dev/null
+++ b/internal/tenant/assets/js/tenant-blog.js
@@ -0,0 +1,10 @@
+(function() {
+ 'use strict';
+
+ const channel = new BroadcastChannel('writekit-studio');
+ channel.onmessage = function(event) {
+ if (event.data.type === 'settings-changed') {
+ location.reload();
+ }
+ };
+})();
diff --git a/internal/build/assets/js/post.js b/internal/tenant/assets/js/tenant-post.js
similarity index 100%
rename from internal/build/assets/js/post.js
rename to internal/tenant/assets/js/tenant-post.js
diff --git a/internal/tenant/templates/base.html b/internal/tenant/templates/base.html
new file mode 100644
index 0000000..cfd08db
--- /dev/null
+++ b/internal/tenant/templates/base.html
@@ -0,0 +1,63 @@
+
+
+
+
+
+ {{.Title}}
+
+
+
+ {{if .NoIndex}}{{end}}
+
+
+
+
+
+
+ {{if .OGImage}}{{end}}
+
+
+
+
+
+
+ {{if .OGImage}}{{end}}
+
+
+
+
+ {{if .FontURL}}{{end}}
+
+
+
+ {{if .StructuredData}}
+
+ {{end}}
+
+
+
+
+
+
+
+ {{template "content" .}}
+
+
+
+
+
+ {{block "scripts" .}}{{end}}
+
+
+
diff --git a/internal/build/templates/blog.html b/internal/tenant/templates/blog.html
similarity index 100%
rename from internal/build/templates/blog.html
rename to internal/tenant/templates/blog.html
diff --git a/internal/build/templates/home.html b/internal/tenant/templates/home.html
similarity index 100%
rename from internal/build/templates/home.html
rename to internal/tenant/templates/home.html
diff --git a/internal/build/templates/post.html b/internal/tenant/templates/post.html
similarity index 97%
rename from internal/build/templates/post.html
rename to internal/tenant/templates/post.html
index 10f1791..dc89b7c 100644
--- a/internal/build/templates/post.html
+++ b/internal/tenant/templates/post.html
@@ -42,7 +42,7 @@
{{define "scripts"}}
{{if or .InteractionConfig.ReactionsEnabled .InteractionConfig.CommentsEnabled}}
-
+