Palettes: pastel-lavender/peach/cloud presets + semantic status colors (#16)
- Three new light [data-preset] blocks alongside midnight (default) and smooth - --ok/--warn/--err semantic tokens with 4.5:1 contrast per preset - .toast success/error variants wired in new.html - ?theme= query param preview hook in layout head (screenshots only) - Preview screenshots in docs/palette-previews/ Closes #16
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 40 KiB |
@@ -7,10 +7,38 @@
|
|||||||
--font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
--font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
--font-mono: ui-monospace, "JetBrains Mono", "Fira Code", monospace;
|
--font-mono: ui-monospace, "JetBrains Mono", "Fira Code", monospace;
|
||||||
}
|
}
|
||||||
|
/* semantic status colors (dark preset values; light presets override below) */
|
||||||
|
:root {
|
||||||
|
--ok: #9CD49C; --warn: #E8C77B; --err: #F2A3B3;
|
||||||
|
--on-accent: #241B30; /* text placed on accent-colored backgrounds */
|
||||||
|
}
|
||||||
[data-preset="smooth"] {
|
[data-preset="smooth"] {
|
||||||
--bg: #F6F5FA; --surface: #FFFFFF; --surface-2: #DAD7E6;
|
--bg: #F6F5FA; --surface: #FFFFFF; --surface-2: #DAD7E6;
|
||||||
--muted: #B5B1C9; --muted-fg: #7A7796; --fg: #2A2A36;
|
--muted: #B5B1C9; --muted-fg: #7A7796; --fg: #2A2A36;
|
||||||
--accent: #7A7796; --border: #DAD7E6;
|
--accent: #7A7796; --border: #DAD7E6;
|
||||||
|
--ok: #456F45; --warn: #7A5E1B; --err: #9E4054;
|
||||||
|
--on-accent: #F6F5FA;
|
||||||
|
}
|
||||||
|
[data-preset="pastel-lavender"] {
|
||||||
|
--bg: #e6e0f5; --surface: #f1edfa; --surface-2: #cbb8e7;
|
||||||
|
--muted: #a99cc9; --muted-fg: #5f5390; --fg: #3E3059;
|
||||||
|
--accent: #806bb8; --border: #c4b6e0;
|
||||||
|
--ok: #3E6B3E; --warn: #7A5E1B; --err: #9E4054;
|
||||||
|
--on-accent: #f1edfa;
|
||||||
|
}
|
||||||
|
[data-preset="pastel-peach"] {
|
||||||
|
--bg: #ffe0d6; --surface: #fff0ea; --surface-2: #ffc4a8;
|
||||||
|
--muted: #d9a08c; --muted-fg: #7a4632; --fg: #4F2318;
|
||||||
|
--accent: #f9826c; --border: #ffc9b5;
|
||||||
|
--ok: #3E6B3E; --warn: #7A5E1B; --err: #9E4054;
|
||||||
|
--on-accent: #4F2318;
|
||||||
|
}
|
||||||
|
[data-preset="pastel-cloud"] {
|
||||||
|
--bg: #fff0f6; --surface: #fff7fb; --surface-2: #ffc8dd;
|
||||||
|
--muted: #d9b9c9; --muted-fg: #7d5670; --fg: #4A3355;
|
||||||
|
--accent: #a2d2ff; --border: #ffccd9;
|
||||||
|
--ok: #3E6B3E; --warn: #7A5E1B; --err: #9E4054;
|
||||||
|
--on-accent: #274a6b;
|
||||||
}
|
}
|
||||||
|
|
||||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
@@ -243,6 +271,9 @@ td a.slug:hover { color: var(--accent); }
|
|||||||
box-shadow: 0 4px 16px rgba(0,0,0,.25);
|
box-shadow: 0 4px 16px rgba(0,0,0,.25);
|
||||||
}
|
}
|
||||||
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
|
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
|
||||||
|
/* status variants (#16) */
|
||||||
|
.toast.success { border-color: var(--ok); color: var(--ok); }
|
||||||
|
.toast.error { border-color: var(--err); color: var(--err); }
|
||||||
|
|
||||||
/* protection section rhythm (#20) */
|
/* protection section rhythm (#20) */
|
||||||
.protect { display: flex; flex-direction: column; gap: 2px; }
|
.protect { display: flex; flex-direction: column; gap: 2px; }
|
||||||
|
|||||||
@@ -2,6 +2,13 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="stylesheet" href="/static/app.css">
|
<link rel="stylesheet" href="/static/app.css">
|
||||||
|
<script>
|
||||||
|
// preset preview hook (#16): ?theme=<name> sets data-preset for screenshots only
|
||||||
|
(function () {
|
||||||
|
var t = new URLSearchParams(location.search).get('theme');
|
||||||
|
if (t) document.documentElement.dataset.preset = t;
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "topbar"}}
|
{{define "topbar"}}
|
||||||
|
|||||||
@@ -74,10 +74,13 @@ function updateGutter() {
|
|||||||
content.addEventListener('input', updateGutter);
|
content.addEventListener('input', updateGutter);
|
||||||
updateGutter();
|
updateGutter();
|
||||||
|
|
||||||
function toast(msg) {
|
function toast(msg, kind) {
|
||||||
let t = document.querySelector('.toast');
|
let t = document.querySelector('.toast');
|
||||||
if (!t) { t = document.createElement('div'); t.className = 'toast'; document.body.appendChild(t); }
|
if (!t) { t = document.createElement('div'); t.className = 'toast'; document.body.appendChild(t); }
|
||||||
t.textContent = msg;
|
t.textContent = msg;
|
||||||
|
t.classList.remove('success', 'error');
|
||||||
|
if (kind === 'success') t.classList.add('success');
|
||||||
|
if (kind === 'error') t.classList.add('error');
|
||||||
t.classList.add('show');
|
t.classList.add('show');
|
||||||
clearTimeout(t._h);
|
clearTimeout(t._h);
|
||||||
t._h = setTimeout(() => t.classList.remove('show'), 2000);
|
t._h = setTimeout(() => t.classList.remove('show'), 2000);
|
||||||
@@ -162,12 +165,13 @@ async function create() {
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
showResult('Error: ' + (data.error || res.status), true);
|
showResult('Error: ' + (data.error || res.status), true);
|
||||||
|
toast('Create failed', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const url = location.origin + '/' + (data.custom_slug || data.id);
|
const url = location.origin + '/' + (data.custom_slug || data.id);
|
||||||
showResult('<a href="' + url + '">' + url + '</a>', false);
|
showResult('<a href="' + url + '">' + url + '</a>', false);
|
||||||
$('result').dataset.token = data.deletion_token || '';
|
$('result').dataset.token = data.deletion_token || '';
|
||||||
try { navigator.clipboard.writeText(url); toast('Copied'); } catch(e) {}
|
try { navigator.clipboard.writeText(url); toast('Copied', 'success'); } catch(e) {}
|
||||||
const dest = '/' + data.id + '?created=1&token=' + encodeURIComponent(data.deletion_token || '');
|
const dest = '/' + data.id + '?created=1&token=' + encodeURIComponent(data.deletion_token || '');
|
||||||
// password-protected: unlock now with the password we already have (#26)
|
// password-protected: unlock now with the password we already have (#26)
|
||||||
if ($('haspw').checked && data.id) {
|
if ($('haspw').checked && data.id) {
|
||||||
|
|||||||