UI polish: radius tokens, layered shadows, pill selections, default filenames, live search, hidden result card, em dash removal
CI / test (push) Successful in 17s
CI / docker (push) Skipped

Fixes #6 #7 #8 #9 #10 #11 #13 #14 #15
This commit is contained in:
2026-09-08 20:28:13 -05:00
parent f542ce0407
commit db17bd20b0
6 changed files with 128 additions and 44 deletions
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+48 -18
View File
@@ -3,7 +3,7 @@
--bg: #241B30; --surface: #2D2340; --surface-2: #3A2D52;
--muted: #7A6A9E; --muted-fg: #C0B2DE; --fg: #F2EDF8;
--accent: #C4A8F0; --border: #42355C;
--radius: 20px;
--radius-lg: 20px; --radius: 10px; --radius-sm: 999px;
--font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--font-mono: ui-monospace, "JetBrains Mono", "Fira Code", monospace;
}
@@ -31,15 +31,18 @@ body {
.logo { font-weight: 700; font-size: 27.6px; letter-spacing: -0.02em; text-decoration: none; color: var(--fg); }
.logo em { font-style: normal; color: var(--muted-fg); font-weight: 400; }
.topbar nav { display: flex; gap: 4px; }
.topbar nav a { color: var(--muted-fg); text-decoration: none; padding: 6px 12px; border-radius: 8px; font-size: 23.2px; }
.topbar nav a { color: var(--muted-fg); text-decoration: none; padding: 6px 12px; border-radius: var(--radius); font-size: 23.2px; }
.topbar nav a:hover { background: var(--surface-2); color: var(--fg); }
.topbar nav a.on { background: var(--accent); color: var(--bg); }
.topbar .spacer { flex: 1; }
.kbd { font-family: var(--font-mono); font-size: 18.9px; border: 1px solid var(--border); border-radius: 5px; padding: 2px 6px; color: var(--muted-fg); }
.kbd { font-family: var(--font-mono); font-size: 18.9px; border: 1px solid var(--border); border-radius: var(--radius); padding: 2px 6px; color: var(--muted-fg); }
.float {
background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
box-shadow: 0 2px 6px rgba(20,14,32,.25), 0 12px 32px rgba(20,14,32,.3);
background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
box-shadow:
0 1px 2px rgba(0, 0, 0, .10),
0 2px 6px rgba(0, 0, 0, .08),
0 8px 24px rgba(0, 0, 0, .07);
overflow: hidden;
}
@@ -62,7 +65,7 @@ body {
}
.editor-head select {
border: 1px solid var(--border); background: var(--surface-2); color: var(--muted-fg);
border-radius: 7px; padding: 4px 10px; font: inherit; font-size: 21.6px; cursor: pointer;
border-radius: var(--radius); padding: 4px 10px; font: inherit; font-size: 21.6px; cursor: pointer;
}
.editor-wrap { flex: 1; display: flex; min-height: 0; }
.gutter {
@@ -83,21 +86,21 @@ body {
.actionbar .btn { padding: 10px 26px; }
.btn {
background: var(--accent); color: var(--bg); border: none; cursor: pointer;
padding: 8px 18px; border-radius: 8px; font: inherit; font-size: 22.4px; font-weight: 600;
padding: 8px 18px; border-radius: var(--radius); font: inherit; font-size: 22.4px; font-weight: 600;
}
.btn:hover { filter: brightness(1.08); }
.hint { font-size: 20.7px; color: var(--muted-fg); }
.hint b { color: var(--fg); font-weight: 550; }
.seg { display: flex; flex-direction: column; gap: 2px; }
.seg label { display: flex; align-items: center; gap: 8px; padding: 5px 8px; border-radius: 7px; cursor: pointer; font-size: 22.4px; }
.seg label { display: flex; align-items: center; gap: 8px; padding: 5px 8px; border-radius: var(--radius); cursor: pointer; font-size: 22.4px; }
.seg label:hover { background: var(--surface-2); }
.seg input { accent-color: var(--accent); }
.toggle { display: flex; align-items: center; gap: 8px; font-size: 22.4px; cursor: pointer; padding: 5px 8px; border-radius: 7px; }
.toggle { display: flex; align-items: center; gap: 8px; font-size: 22.4px; cursor: pointer; padding: 5px 8px; border-radius: var(--radius); }
.toggle:hover { background: var(--surface-2); }
.toggle input { accent-color: var(--accent); }
.deck .row { display: flex; justify-content: space-between; align-items: center; font-size: 22.4px; padding: 4px 0; }
.row input[type="text"] {
border: 1px solid var(--border); border-radius: 7px; padding: 5px 8px; background: var(--bg);
border: 1px solid var(--border); border-radius: var(--radius); padding: 5px 8px; background: var(--bg);
color: var(--fg); font: inherit; font-size: 21.6px; width: 130px;
}
.created-banner {
@@ -109,10 +112,10 @@ body {
/* paste view */
.meta-bar { display: flex; align-items: center; gap: 12px; padding: 12px 18px; flex-wrap: wrap; }
.meta-bar h1 { font-size: 29.2px; font-weight: 600; }
.slug { font-family: var(--font-mono); font-size: 21.6px; color: var(--muted-fg); background: var(--surface-2); padding: 3px 9px; border-radius: 7px; }
.tag { font-size: 19.8px; color: var(--muted-fg); border: 1px solid var(--border); border-radius: 999px; padding: 2px 9px; }
.slug { font-family: var(--font-mono); font-size: 21.6px; color: var(--muted-fg); background: var(--surface-2); padding: 3px 9px; border-radius: var(--radius); }
.tag { font-size: 19.8px; color: var(--muted-fg); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 2px 9px; }
.meta-bar .spacer { flex: 1; }
.iconbtn { border: 1px solid var(--border); background: var(--surface-2); color: var(--muted-fg); border-radius: 8px; padding: 5px 12px; font: inherit; font-size: 21.6px; cursor: pointer; text-decoration: none; }
.iconbtn { border: 1px solid var(--border); background: var(--surface-2); color: var(--muted-fg); border-radius: var(--radius); padding: 5px 12px; font: inherit; font-size: 21.6px; cursor: pointer; text-decoration: none; }
.iconbtn:hover { color: var(--fg); border-color: var(--muted); }
.iconbtn.danger:hover { color: #ff8fa3; border-color: #ff8fa3; }
.code-head {
@@ -134,7 +137,7 @@ body {
.head-row h1 { font-size: 34.5px; font-weight: 600; }
.search {
display: flex; align-items: center; gap: 8px; background: var(--surface);
border: 1px solid var(--border); border-radius: 10px; padding: 8px 14px; width: 260px;
border: 1px solid var(--border); border-radius: var(--radius); padding: 8px 14px; width: 260px;
}
.search input { border: none; outline: none; background: transparent; color: var(--fg); font: inherit; font-size: 22.4px; width: 100%; }
table { width: 100%; border-collapse: collapse; font-size: 22.4px; table-layout: fixed; }
@@ -149,12 +152,12 @@ tr.row:hover td { background: var(--surface-2); }
tr.row:hover td a.slug { color: var(--accent); }
td a.slug { font-family: var(--font-mono); font-size: 21.6px; color: var(--fg); text-decoration: none; }
td a.slug:hover { color: var(--accent); }
.badge { font-size: 18.9px; border: 1px solid var(--border); color: var(--muted-fg); border-radius: 999px; padding: 1px 8px; }
.badge { font-size: 18.9px; border: 1px solid var(--border); color: var(--muted-fg); border-radius: var(--radius-sm); padding: 1px 8px; }
.badge.lock { color: var(--accent); border-color: var(--accent); }
.dim { color: var(--muted-fg); white-space: nowrap; }
.pager { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; font-size: 21.6px; color: var(--muted-fg); }
.pager .pg { display: flex; gap: 6px; }
.pager button { border: 1px solid var(--border); background: var(--surface-2); color: var(--muted-fg); border-radius: 7px; padding: 4px 11px; font: inherit; font-size: 21.6px; cursor: pointer; }
.pager button { border: 1px solid var(--border); background: var(--surface-2); color: var(--muted-fg); border-radius: var(--radius); padding: 4px 11px; font: inherit; font-size: 21.6px; cursor: pointer; }
.pager button:hover:not(:disabled) { color: var(--fg); border-color: var(--muted); }
.pager button.on { background: var(--accent); color: var(--bg); border-color: var(--accent); }
.pager button:disabled { opacity: .4; cursor: default; }
@@ -171,7 +174,7 @@ td a.slug:hover { color: var(--accent); }
.inner h1 { font-size: 29.2px; font-weight: 600; margin-bottom: 6px; }
.inner .sub { font-size: 22.4px; color: var(--muted-fg); margin-bottom: 20px; }
.pwinput {
width: 100%; padding: 10px 14px; border: 1px solid var(--border); border-radius: 9px;
width: 100%; padding: 10px 14px; border: 1px solid var(--border); border-radius: var(--radius);
background: var(--bg); color: var(--fg); font: inherit; font-size: 23.2px; outline: none; text-align: center;
letter-spacing: .12em;
}
@@ -180,4 +183,31 @@ td a.slug:hover { color: var(--accent); }
.err { display: none; margin-top: 12px; font-size: 21.6px; color: #ff8fa3; }
.center .foot { font-size: 20.7px; color: var(--muted-fg); padding: 14px; border-top: 1px solid var(--border); }
.btn-icon { padding: 6px 10px; font-size: 24.2px; line-height: 1; }
.btn-icon {
padding: 4px 8px; font-size: 24.2px; line-height: 1;
display: inline-flex; align-items: center; justify-content: center;
min-width: 40px; height: 36px;
}
/* selection controls: pill-style selected states (#14) */
.seg label, .toggle {
border: 1px solid transparent;
transition: background .12s ease, border-color .12s ease, color .12s ease;
}
.seg label:hover, .toggle:hover {
background: var(--surface-2);
color: var(--fg);
}
.seg label:has(input:checked),
.toggle:has(input:checked) {
background: var(--surface-2);
border-color: var(--accent);
color: var(--fg);
border-radius: var(--radius-sm);
}
.seg label:has(input:focus-visible),
.toggle:has(input:focus-visible) {
outline: 2px solid var(--accent);
outline-offset: 1px;
}
.seg input, .toggle input { accent-color: var(--accent); width: 16px; height: 16px; margin: 0; }
+37 -7
View File
@@ -25,7 +25,7 @@ let page = 1, total = 0;
const $ = id => document.getElementById(id);
function esc(s) { const d = document.createElement('div'); d.textContent = s == null ? '' : s; return d.innerHTML; }
function fmtSize(n) { if (n == null) return ''; if (n < 1024) return n + ' B'; if (n < 1048576) return (n/1024).toFixed(1) + ' KB'; return (n/1048576).toFixed(1) + ' MB'; }
function fmtSize(n) { if (n == null) return 'none'; if (n < 1024) return n + ' B'; if (n < 1048576) return (n/1024).toFixed(1) + ' KB'; return (n/1048576).toFixed(1) + ' MB'; }
function ago(ts) {
const s = Math.floor(Date.now()/1000) - ts;
if (s < 60) return s + 's ago';
@@ -34,28 +34,49 @@ function ago(ts) {
return Math.floor(s/86400) + 'd ago';
}
let filter = '';
function matchesFilter(it) {
if (!filter) return true;
const f = filter.toLowerCase();
return (it.title || '').toLowerCase().includes(f) || (it.id || '').toLowerCase().includes(f);
}
async function load() {
const filtered = filter.length > 0;
const off = (page - 1) * PER;
const res = await fetch('/api/public?limit=' + PER + '&offset=' + off);
const url = filtered
? '/api/public?limit=200&offset=0'
: '/api/public?limit=' + PER + '&offset=' + off;
const res = await fetch(url);
const data = await res.json();
total = data.total;
$('count').textContent = total.toLocaleString() + ' total';
const items = filtered ? data.items.filter(matchesFilter) : data.items;
$('count').textContent = filtered
? items.length.toLocaleString() + ' matches (of ' + total.toLocaleString() + ' total)'
: total.toLocaleString() + ' total';
const rows = $('rows');
if (data.items.length === 0) {
if (items.length === 0) {
rows.innerHTML = '';
$('empty').style.display = 'block';
$('empty').textContent = filtered ? 'No pastes match your search.' : 'No pastes yet. Create the first one.';
} else {
$('empty').style.display = 'none';
rows.innerHTML = data.items.map(it =>
rows.innerHTML = items.map(it =>
`<tr class="row" data-href="/${esc(it.id)}"><td><a class="slug" href="/${esc(it.id)}">${esc(it.id)}</a></td>` +
`<td class="title-cell">${it.title ? esc(it.title) : '<span class=dim></span>'}</td>` +
`<td class="title-cell">${it.title ? esc(it.title) : '<span class=dim>none</span>'}</td>` +
`<td><span class="badge">${esc(it.language || 'text')}</span></td>` +
`<td class="dim">${fmtSize(it.size)}</td><td class="dim">${it.view_count}</td><td class="dim">${ago(it.created_at)}</td></tr>`
).join('');
}
const pages = Math.max(1, Math.ceil(total / PER));
if (filtered) {
$('showing').textContent = 'Showing ' + items.length.toLocaleString() + ' matches for "' + filter + '"';
$('pg').innerHTML = '';
} else {
$('showing').textContent = total === 0 ? 'Nothing here yet' :
`Showing ${off+1}${Math.min(off+PER, total)} of ${total.toLocaleString()} · page ${page} of ${pages}`;
@@ -73,6 +94,7 @@ async function load() {
}
add('', page+1, {dis: page===pages});
$('pg').innerHTML = btns.join('');
}
}
$('pg').addEventListener('click', e => {
@@ -87,7 +109,15 @@ $('rows').addEventListener('click', e => {
if (!tr || e.target.closest('a')) return; // let real links (e.g. middle-click) work
window.location.href = tr.dataset.href;
});
$('filter').addEventListener('input', () => { page = 1; load(); });
let filterTimer = null;
$('filter').addEventListener('input', e => {
clearTimeout(filterTimer);
filterTimer = setTimeout(() => {
filter = e.target.value.trim();
page = 1;
load();
}, 200);
});
load();
setInterval(load, 30000); // auto-refresh history every 30s
</script>
+29 -5
View File
@@ -47,11 +47,11 @@
</div>
<div class="float side-section">
<h3>Custom URL</h3>
<div class="row"><span>/</span><input type="text" id="custom" placeholder="my-snippet"></div>
<div class="row" style="justify-content:flex-start; gap:6px; align-items:center;"><span>/</span><input type="text" id="custom" placeholder="my-snippet" style="text-align:left; flex:1; min-width:0;"></div>
</div>
<div class="float side-section">
<div class="float side-section" id="result-card" style="display:none">
<h3>Result</h3>
<div class="hint" id="result" style="word-break:break-all"></div>
<div class="hint" id="result" style="word-break:break-all">empty</div>
</div>
</div>
</div>
@@ -72,6 +72,29 @@ $('haspw').addEventListener('change', e => { $('password').style.display = e.tar
let guessed = ''; // last auto-detected language, '' = user override
function showResult(html, isError) {
$('result').innerHTML = html;
$('result').dataset.token = isError ? '' : ($('result').dataset.token || '');
$('result-card').style.display = 'block';
}
function defaultFilename(lang) {
const names = {
python: 'Python.py', go: 'main.go', javascript: 'script.js', rust: 'main.rs',
c: 'main.c', cpp: 'main.cpp', java: 'Main.java', bash: 'script.sh',
sql: 'query.sql', yaml: 'config.yaml', json: 'data.json',
markdown: 'notes.md', text: 'Text.txt',
};
return names[lang] || '';
}
// fill default filename when title is still blank
function maybeSetDefaultTitle(lang) {
const title = $('title');
if (lang && !title.value.trim()) {
const fn = defaultFilename(lang);
if (fn) title.value = fn;
}
}
async function guessLang() {
if (!content.value.trim()) return;
try {
@@ -84,6 +107,7 @@ async function guessLang() {
if (res.ok && data.language) {
guessed = data.language;
$('language').value = data.language;
maybeSetDefaultTitle(data.language);
}
} catch(e) {}
}
@@ -113,11 +137,11 @@ async function create() {
});
const data = await res.json();
if (!res.ok) {
$('result').textContent = 'Error: ' + (data.error || res.status);
showResult('Error: ' + (data.error || res.status), true);
return;
}
const url = location.origin + '/' + (data.custom_slug || data.id);
$('result').innerHTML = '<a href="' + url + '">' + url + '</a>';
showResult('<a href="' + url + '">' + url + '</a>', false);
$('result').dataset.token = data.deletion_token || '';
try { navigator.clipboard.writeText(url); } catch(e) {}
// show the paste