site: editorial controls + 1d-chess find — chunk 5/4

- Add EditAction + EditConfirm components (dev-only editorial UI bits)
- Stage 1d-chess find page (missed by earlier glob since slug starts with a digit)
This commit is contained in:
2026-05-04 17:57:21 -04:00
parent dab422abb0
commit 1ecdc10443
4 changed files with 284 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
---
interface Props {
label: string;
command?: string;
confirm: string;
action?: string;
variant?: 'inline' | 'block';
}
const {
label,
command,
confirm,
action = 'copy',
variant = 'block',
} = Astro.props;
---
<button
type="button"
class:list={['edit-action', `edit-action-${variant}`]}
data-edit-action={action}
data-command={command}
data-confirm={confirm}
>
<span class="edit-eyebrow">Edit</span>
<span class="edit-label">{label}</span>
</button>
<style>
.edit-action {
cursor: pointer;
background: var(--card-fill);
border: 1px dashed var(--accent);
border-radius: 6px;
color: var(--fg);
font-family: ui-sans-serif, system-ui, -apple-system, 'Helvetica Neue', sans-serif;
transition: background 0.15s ease, border-color 0.15s ease;
}
.edit-action:hover {
background: color-mix(in srgb, var(--accent) 14%, var(--bg));
}
.edit-action-inline {
display: inline-flex;
align-items: center;
gap: 0.55rem;
padding: 0.4rem 0.85rem;
font-size: 0.85rem;
}
.edit-action-block {
display: inline-flex;
align-items: center;
gap: 0.6rem;
padding: 0.55rem 1rem;
font-size: 0.9rem;
}
.edit-eyebrow {
font-size: 0.65rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--accent);
font-weight: 700;
}
.edit-label { font-weight: 600; }
</style>
+196
View File
@@ -0,0 +1,196 @@
---
// Singleton confirm dialog + toast for editorial actions.
// Included once in BaseLayout when import.meta.env.DEV is true.
// Listens for clicks on any [data-edit-action] element, presents a confirm
// modal, then copies the command to the clipboard on go.
---
<div id="edit-confirm" class="edit-confirm" hidden>
<div class="edit-confirm-overlay" data-close></div>
<div
class="edit-confirm-modal"
role="dialog"
aria-modal="true"
aria-labelledby="edit-confirm-title"
>
<h3 id="edit-confirm-title">Confirm</h3>
<p class="edit-confirm-description"></p>
<pre class="edit-confirm-command"></pre>
<div class="edit-confirm-actions">
<button type="button" class="edit-confirm-cancel" data-close>Cancel</button>
<button type="button" class="edit-confirm-go">Copy command</button>
</div>
</div>
</div>
<div id="edit-toast" class="edit-toast" hidden></div>
<script>
const dialog = document.getElementById('edit-confirm');
const desc = dialog?.querySelector('.edit-confirm-description');
const cmdEl = dialog?.querySelector('.edit-confirm-command');
const goBtn = dialog?.querySelector('.edit-confirm-go') as HTMLButtonElement | null;
const toast = document.getElementById('edit-toast');
let pendingCommand = '';
let toastTimer: ReturnType<typeof setTimeout> | undefined;
function showToast(text: string) {
if (!toast) return;
toast.textContent = text;
toast.hidden = false;
clearTimeout(toastTimer);
toastTimer = setTimeout(() => { toast.hidden = true; }, 2400);
}
function openModal({ command, description }: { command: string; description: string }) {
if (!dialog || !desc || !cmdEl) return;
pendingCommand = command;
desc.textContent = description;
cmdEl.textContent = command;
dialog.hidden = false;
setTimeout(() => goBtn?.focus(), 0);
}
function closeModal() {
if (!dialog) return;
dialog.hidden = true;
pendingCommand = '';
}
dialog?.addEventListener('click', (e) => {
const t = e.target as HTMLElement;
if (t.closest('[data-close]')) closeModal();
});
goBtn?.addEventListener('click', async () => {
const cmd = pendingCommand;
closeModal();
try {
await navigator.clipboard.writeText(cmd);
showToast('Copied — paste into Claude');
} catch {
showToast('Copy failed — see console');
console.error('clipboard write failed; command was:', cmd);
}
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && dialog && !dialog.hidden) closeModal();
});
document.addEventListener('click', (e) => {
const t = e.target as HTMLElement;
const btn = t.closest<HTMLElement>('[data-edit-action]');
if (!btn) return;
e.preventDefault();
let command = btn.dataset.command || '';
let description = btn.dataset.confirm || '';
if (btn.dataset.editAction === 'finds-collection') {
const checked = Array.from(
document.querySelectorAll<HTMLInputElement>('[data-find-checkbox]:checked')
);
if (checked.length === 0) {
showToast('Select at least one find first.');
return;
}
const slugs = checked.map((cb) => cb.dataset.slug).filter(Boolean).join(',');
command = `/build-finds ${slugs}`;
description = `Build a finds collection from ${checked.length} selected find${
checked.length === 1 ? '' : 's'
}.`;
}
openModal({ command, description });
});
</script>
<style>
.edit-confirm[hidden] { display: none; }
.edit-confirm {
position: fixed;
inset: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
.edit-confirm-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.5);
}
.edit-confirm-modal {
position: relative;
background: var(--bg);
border: 1px solid var(--rule);
border-radius: 10px;
padding: 1.25rem 1.4rem 1.1rem;
max-width: 28rem;
width: 100%;
box-shadow: 0 20px 60px -10px rgba(0, 0, 0, 0.4);
font-family: ui-sans-serif, system-ui, -apple-system, 'Helvetica Neue', sans-serif;
}
.edit-confirm-modal h3 {
margin: 0 0 0.5rem;
font-family: inherit;
font-size: 1.05rem;
}
.edit-confirm-description {
margin: 0 0 0.85rem;
color: var(--muted);
font-size: 0.92rem;
line-height: 1.45;
}
.edit-confirm-command {
margin: 0 0 1rem;
padding: 0.55rem 0.7rem;
background: var(--rule);
border-radius: 6px;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.85rem;
color: var(--fg);
white-space: pre-wrap;
word-break: break-all;
}
.edit-confirm-actions {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
.edit-confirm-actions button {
padding: 0.45rem 0.95rem;
font-family: inherit;
font-size: 0.9rem;
font-weight: 600;
border-radius: 6px;
cursor: pointer;
border: 1px solid var(--rule);
background: var(--bg);
color: var(--fg);
}
.edit-confirm-actions .edit-confirm-go {
background: var(--accent);
color: var(--bg);
border-color: var(--accent);
}
.edit-confirm-actions .edit-confirm-go:hover { opacity: 0.92; }
.edit-toast[hidden] { display: none; }
.edit-toast {
position: fixed;
bottom: 1.5rem;
left: 50%;
transform: translateX(-50%);
z-index: 1001;
padding: 0.6rem 1rem;
background: var(--fg);
color: var(--bg);
border-radius: 999px;
font-family: ui-sans-serif, system-ui, -apple-system, 'Helvetica Neue', sans-serif;
font-size: 0.85rem;
box-shadow: 0 4px 14px -4px rgba(0, 0, 0, 0.3);
}
</style>
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="240" height="240" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="1 1 44 44">
<g style="fill:none; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;">
<path d="M 22.5,11.63 L 22.5,6" style="fill:none; stroke:#000000; stroke-linejoin:miter;"/>
<path d="M 20,8 L 25,8" style="fill:none; stroke:#000000; stroke-linejoin:miter;"/>
<path d="M 22.5,25 C 22.5,25 27,17.5 25.5,14.5 C 25.5,14.5 24.5,12 22.5,12 C 20.5,12 19.5,14.5 19.5,14.5 C 18,17.5 22.5,25 22.5,25" style="fill:#ffffff; stroke:#000000; stroke-linecap:butt; stroke-linejoin:miter;"/>
<path d="M 12.5,37 C 18,40.5 27,40.5 32.5,37 L 32.5,30 C 32.5,30 41.5,25.5 38.5,19.5 C 34.5,13 25,16 22.5,23.5 L 22.5,27 L 22.5,23.5 C 20,16 10.5,13 6.5,19.5 C 3.5,25.5 12.5,30 12.5,30 L 12.5,37" style="fill:#ffffff; stroke:#000000;"/>
<path d="M 12.5,30 C 18,27 27,27 32.5,30" style="fill:none; stroke:#000000;"/>
<path d="M 12.5,33.5 C 18,30.5 27,30.5 32.5,33.5" style="fill:none; stroke:#000000;"/>
<path d="M 12.5,37 C 18,34 27,34 32.5,37" style="fill:none; stroke:#000000;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+11
View File
@@ -0,0 +1,11 @@
---
name: "1D Chess"
subtitle: "Chess puzzle reduced to a single line of squares"
date: 2026-05-03
link: https://rowan441.github.io/1dchess/chess.html
source: "Daring Fireball — Linked List"
topics: [general-delight]
tags: [puzzle, chess, web-toy, free]
---
Strip a chessboard to one row of eight squares and you'd think the game collapses — instead the puzzles get harder to see, because the spatial intuition that helps in 2D is gone. A small browser toy that takes about thirty seconds to learn and longer than expected to get good at. Free, no signup, no ads.