From 3090ed92b0ee7363d58f49c3a3ffb19d3a000754 Mon Sep 17 00:00:00 2001 From: artheru Date: Wed, 10 Jun 2026 05:22:16 +0800 Subject: [PATCH] toast notifications (0.32.0): elegant feedback for copies/actions - src/toast.js: dependency-free toast + copyToast helpers - wired copyToast into all token/accesskey/url/key copy actions (silent before) - replaces jarring browser dialogs for copy feedback; consistent polished UX --- index.html | 2 +- package.json | 2 +- src/AgentPlatform.jsx | 11 ++++++----- src/Mansion.jsx | 3 ++- src/Scene2D.jsx | 2 +- src/styles.css | 6 ++++++ src/toast.js | 16 ++++++++++++++++ 7 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 src/toast.js diff --git a/index.html b/index.html index 009c5a2..d86f918 100644 --- a/index.html +++ b/index.html @@ -7,7 +7,7 @@ name="description" content="amerc is an agent mercenary layer for embedding, bringing, and hosting agents across vertical software boundaries." /> - + diff --git a/package.json b/package.json index 61e10b2..e6c414d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "amerc-site", - "version": "0.31.0-activity", + "version": "0.32.0-toasts", "private": true, "type": "module", "scripts": { diff --git a/src/AgentPlatform.jsx b/src/AgentPlatform.jsx index ececd17..c475fdc 100644 --- a/src/AgentPlatform.jsx +++ b/src/AgentPlatform.jsx @@ -1,5 +1,6 @@ import React, { useCallback, useEffect, useState } from 'react'; import { api } from './api.js'; +import { copyToast } from './toast.js'; const STATUS_COLOR = { online: '#36f0b0', pending: '#f2b85f', lost: '#ff9f5a', down: '#ff7a8c' }; const TAG_EMOJI = { backend: '๐Ÿ› ๏ธ', model: '๐Ÿง ', frontend: '๐ŸŽจ', ui: '๐Ÿ–Œ๏ธ', web: '๐ŸŒ', data: '๐Ÿ“Š', devops: 'โš™๏ธ', ml: '๐Ÿค–', security: '๐Ÿ”', api: '๐Ÿ”Œ', bot: '๐Ÿค–', chat: '๐Ÿ’ฌ', test: '๐Ÿงช', docs: '๐Ÿ“„', design: 'โœ๏ธ', ops: '๐Ÿšฆ', research: '๐Ÿ”ฌ' }; @@ -92,7 +93,7 @@ function ClassModal({ id, auth, onClose }) { {err &&

{err}

}
{auth.user ? : Log in to subscribe.} - {sub && navigator.clipboard?.writeText(sub)}>{sub} โง‰} + {sub && copyToast(sub)}>{sub} โง‰}

Instances

@@ -178,7 +179,7 @@ function KeysCard() { setName(e.target.value)} required /> - {created &&

โœ… minted โ€” copy now, shown once:

navigator.clipboard?.writeText(created.key)}>{created.key} โง‰
} + {created &&

โœ… minted โ€” copy now, shown once:

copyToast(created.key)}>{created.key} โง‰
} {err &&

{err}

} {keys.map((k) => (
@@ -257,7 +258,7 @@ export function BoothDashboard({ auth, setRoute }) { {insts.map((i) => (
#{i.id} {i.status} - {i.accesskey && navigator.clipboard?.writeText(i.accesskey)}>{i.accesskey.slice(0, 14)}โ€ฆ โง‰} + {i.accesskey && copyToast(i.accesskey)}>{i.accesskey.slice(0, 14)}โ€ฆ โง‰}
))} @@ -270,8 +271,8 @@ export function BoothDashboard({ auth, setRoute }) {
e.stopPropagation()}>

Instance #{newInst.id} created

Run your broker against this instance, then it shows online. The accesskey is the relay token.

- - + +
diff --git a/src/Mansion.jsx b/src/Mansion.jsx index 2e66a85..9107917 100644 --- a/src/Mansion.jsx +++ b/src/Mansion.jsx @@ -1,5 +1,6 @@ import React, { useCallback, useEffect, useState } from 'react'; import { api } from './api.js'; +import { copyToast } from './toast.js'; // amerc Mansion โ€” a hub of amerc services. First service: Showcase (reverse proxy). export default function Mansion({ auth, setRoute }) { @@ -33,7 +34,7 @@ function Showcase({ handle }) { catch (e2) { setErr(e2.message); } }; const del = async (id) => { if (!window.confirm('Delete this showcase?')) return; try { await api(`/showcase/${id}`, { method: 'DELETE' }); load(); } catch (e2) { setErr(e2.message); } }; - const copy = (t) => navigator.clipboard?.writeText(t); + const copy = (t) => copyToast(t); return (
diff --git a/src/Scene2D.jsx b/src/Scene2D.jsx index a70b063..7bc70b3 100644 --- a/src/Scene2D.jsx +++ b/src/Scene2D.jsx @@ -6,7 +6,7 @@ import Mansion from './Mansion.jsx'; import TavernGame from './TavernGame.jsx'; import { api } from './api.js'; -const RELEASE = '0.31.0-activity'; +const RELEASE = '0.32.0-toasts'; const NAV = [ { id: 'home', label: 'Tavern' }, diff --git a/src/styles.css b/src/styles.css index 2883040..f496a03 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1146,3 +1146,9 @@ button { .act-ico { font-size: 16px; flex: none; } .act-text { font-size: 13px; color: #c9b8e6; flex: 1; } .act-time { font-size: 11px; color: #6f5b8c; flex: none; } + +/* toasts (0.32) */ +#amerc-toasts { position: fixed; bottom: 20px; right: 20px; z-index: 99999; display: flex; flex-direction: column; gap: 8px; pointer-events: none; } +.amerc-toast { background: #142238; color: #eaf2ff; border: 1px solid #2e4a6e; border-left: 3px solid #36f0b0; border-radius: 9px; padding: 11px 16px; font-family: Inter, sans-serif; font-size: 13px; box-shadow: 0 10px 28px rgba(0,0,0,0.55); opacity: 0; transform: translateX(24px); transition: opacity .25s ease, transform .25s ease; max-width: 320px; } +.amerc-toast.in { opacity: 1; transform: none; } +.amerc-toast.t-err { border-left-color: #ff7a8c; } diff --git a/src/toast.js b/src/toast.js new file mode 100644 index 0000000..f001f6c --- /dev/null +++ b/src/toast.js @@ -0,0 +1,16 @@ +// Tiny dependency-free toast notifications, callable from anywhere. +export function toast(msg, type = 'ok', ms = 2600) { + let c = document.getElementById('amerc-toasts'); + if (!c) { c = document.createElement('div'); c.id = 'amerc-toasts'; document.body.appendChild(c); } + const t = document.createElement('div'); + t.className = `amerc-toast t-${type}`; + t.textContent = msg; + c.appendChild(t); + requestAnimationFrame(() => t.classList.add('in')); + setTimeout(() => { t.classList.remove('in'); setTimeout(() => t.remove(), 300); }, ms); +} + +export function copyToast(text, label = 'Copied') { + try { navigator.clipboard.writeText(text).then(() => toast(`${label} โœ“`)).catch(() => toast('Copy failed', 'err')); } + catch { toast('Copy failed', 'err'); } +}