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