UI polish: radius tokens, layered shadows, pill selections, default filenames, live search, hidden result card, em dash removal
Fixes #6 #7 #8 #9 #10 #11 #13 #14 #15
This commit is contained in:
Binary file not shown.
Binary file not shown.
+48
-18
@@ -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; }
|
||||
|
||||
+51
-21
@@ -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,45 +34,67 @@ 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));
|
||||
$('showing').textContent = total === 0 ? 'Nothing here yet' :
|
||||
`Showing ${off+1}–${Math.min(off+PER, total)} of ${total.toLocaleString()} · page ${page} of ${pages}`;
|
||||
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}`;
|
||||
|
||||
const btns = [];
|
||||
const add = (label, target, opts={}) => btns.push(`<button ${opts.on?'class="on"':''} ${opts.dis?'disabled':''} data-p="${target}">${label}</button>`);
|
||||
add('‹', page-1, {dis: page===1});
|
||||
const win = new Set([1, 2, page-1, page, page+1, pages]);
|
||||
let last = 0;
|
||||
for (let i = 1; i <= pages; i++) {
|
||||
if (win.has(i)) {
|
||||
if (last && i - last > 1) btns.push('<span class="dim">…</span>');
|
||||
add(String(i), i, {on: i===page});
|
||||
last = i;
|
||||
const btns = [];
|
||||
const add = (label, target, opts={}) => btns.push(`<button ${opts.on?'class="on"':''} ${opts.dis?'disabled':''} data-p="${target}">${label}</button>`);
|
||||
add('‹', page-1, {dis: page===1});
|
||||
const win = new Set([1, 2, page-1, page, page+1, pages]);
|
||||
let last = 0;
|
||||
for (let i = 1; i <= pages; i++) {
|
||||
if (win.has(i)) {
|
||||
if (last && i - last > 1) btns.push('<span class="dim">…</span>');
|
||||
add(String(i), i, {on: i===page});
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
add('›', page+1, {dis: page===pages});
|
||||
$('pg').innerHTML = btns.join('');
|
||||
}
|
||||
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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user