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:
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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 |
@@ -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.
|
||||
Reference in New Issue
Block a user