Add basic monochrome popup UI with Tailwind CSS
All checks were successful
check / check (push) Successful in 11s

Black-on-white, monospace, Universal Paperclips aesthetic.
All views: lock, setup/create/import, main account, send,
receive, add token, settings, and approval. Vanilla JS view
switching with stub state. README updated with full UI design
philosophy, external services documentation, and view descriptions.
This commit is contained in:
2026-02-24 10:12:19 +07:00
parent 065f0eaa81
commit d9eda1d503
8 changed files with 1333 additions and 40 deletions

View File

@@ -1,2 +1,372 @@
// AutistMask popup UI
// TODO: lock screen, account list, send/receive views
// AutistMask popup UI — view management and event wiring
// All wallet logic will live in background/; this file is purely UI.
const views = [
"lock",
"setup",
"create",
"import",
"main",
"send",
"receive",
"add-token",
"settings",
"approve",
];
function showView(name) {
for (const v of views) {
const el = document.getElementById(`view-${v}`);
if (el) {
el.classList.toggle("hidden", v !== name);
}
}
}
// -- mock state (will be replaced by background messaging) --
const state = {
locked: true,
hasWallet: false,
password: null,
accounts: [],
selectedAccount: 0,
tokens: [],
rpcUrl: "https://eth.llamarpc.com",
mnemonic: null,
};
// -- helpers --
function $(id) {
return document.getElementById(id);
}
function showError(id, msg) {
const el = $(id);
el.textContent = msg;
el.classList.remove("hidden");
}
function hideError(id) {
$(id).classList.add("hidden");
}
function truncateAddress(addr) {
if (!addr) return "";
return addr.slice(0, 6) + "..." + addr.slice(-4);
}
function updateAccountSelector() {
const sel = $("account-selector");
sel.innerHTML = "";
state.accounts.forEach((acct, i) => {
const opt = document.createElement("option");
opt.value = i;
opt.textContent = `Account ${i}: ${truncateAddress(acct.address)}`;
sel.appendChild(opt);
});
sel.value = state.selectedAccount;
$("current-address").textContent =
state.accounts[state.selectedAccount]?.address || "";
$("eth-balance").textContent =
state.accounts[state.selectedAccount]?.balance || "0.0000";
}
function updateTokenList() {
const list = $("token-list");
if (state.tokens.length === 0) {
list.innerHTML =
'<div class="text-muted text-xs py-1">no tokens added</div>';
return;
}
list.innerHTML = state.tokens
.map(
(t) =>
`<div class="py-1 border-b border-border-light flex justify-between">` +
`<span>${t.symbol}</span>` +
`<span>${t.balance || "0.0000"}</span>` +
`</div>`,
)
.join("");
}
function updateSendTokenSelect() {
const sel = $("send-token");
sel.innerHTML = '<option value="ETH">ETH</option>';
state.tokens.forEach((t) => {
const opt = document.createElement("option");
opt.value = t.address;
opt.textContent = t.symbol;
sel.appendChild(opt);
});
}
// -- init --
function init() {
// For now, always show setup (no wallet exists yet).
// Once background messaging is wired, this will check actual state.
if (!state.hasWallet) {
showView("setup");
} else if (state.locked) {
showView("lock");
} else {
showView("main");
}
// -- Lock screen --
$("btn-unlock").addEventListener("click", () => {
const pw = $("unlock-password").value;
if (!pw) {
showError("unlock-error", "enter a password");
return;
}
hideError("unlock-error");
// TODO: send unlock message to background
state.locked = false;
updateAccountSelector();
updateTokenList();
showView("main");
});
// -- Setup --
$("btn-setup-create").addEventListener("click", () => {
// TODO: request mnemonic generation from background
$("create-mnemonic").textContent =
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
showView("create");
});
$("btn-setup-import").addEventListener("click", () => {
showView("import");
});
// -- Create wallet --
$("btn-create-confirm").addEventListener("click", () => {
const pw = $("create-password").value;
const pw2 = $("create-password-confirm").value;
if (!pw) {
showError("create-error", "enter a password");
return;
}
if (pw !== pw2) {
showError("create-error", "passwords do not match");
return;
}
hideError("create-error");
// TODO: send create wallet message to background
state.hasWallet = true;
state.locked = false;
state.password = pw;
state.mnemonic = $("create-mnemonic").textContent;
state.accounts = [
{
address: "0x0000000000000000000000000000000000000001",
balance: "0.0000",
},
];
state.selectedAccount = 0;
updateAccountSelector();
updateTokenList();
showView("main");
});
$("btn-create-back").addEventListener("click", () => {
showView("setup");
});
// -- Import wallet --
$("btn-import-confirm").addEventListener("click", () => {
const mnemonic = $("import-mnemonic").value.trim();
const pw = $("import-password").value;
const pw2 = $("import-password-confirm").value;
if (!mnemonic) {
showError("import-error", "enter a seed phrase");
return;
}
if (!pw) {
showError("import-error", "enter a password");
return;
}
if (pw !== pw2) {
showError("import-error", "passwords do not match");
return;
}
hideError("import-error");
// TODO: validate mnemonic and send to background
state.hasWallet = true;
state.locked = false;
state.password = pw;
state.mnemonic = mnemonic;
state.accounts = [
{
address: "0x0000000000000000000000000000000000000001",
balance: "0.0000",
},
];
state.selectedAccount = 0;
updateAccountSelector();
updateTokenList();
showView("main");
});
$("btn-import-back").addEventListener("click", () => {
showView("setup");
});
// -- Main view --
$("btn-lock").addEventListener("click", () => {
state.locked = true;
$("unlock-password").value = "";
showView("lock");
});
$("btn-settings").addEventListener("click", () => {
$("settings-rpc").value = state.rpcUrl;
showView("settings");
});
$("account-selector").addEventListener("change", (e) => {
state.selectedAccount = parseInt(e.target.value, 10);
$("current-address").textContent =
state.accounts[state.selectedAccount]?.address || "";
$("eth-balance").textContent =
state.accounts[state.selectedAccount]?.balance || "0.0000";
});
$("btn-copy-address").addEventListener("click", () => {
const addr = state.accounts[state.selectedAccount]?.address;
if (addr) {
navigator.clipboard.writeText(addr);
}
});
$("btn-send").addEventListener("click", () => {
updateSendTokenSelect();
$("send-to").value = "";
$("send-amount").value = "";
$("send-gas-estimate").classList.add("hidden");
$("send-status").classList.add("hidden");
showView("send");
});
$("btn-receive").addEventListener("click", () => {
$("receive-address").textContent =
state.accounts[state.selectedAccount]?.address || "";
showView("receive");
});
$("btn-add-token").addEventListener("click", () => {
$("add-token-address").value = "";
$("add-token-info").classList.add("hidden");
hideError("add-token-error");
showView("add-token");
});
// -- Send --
$("btn-send-confirm").addEventListener("click", () => {
const to = $("send-to").value.trim();
const amount = $("send-amount").value.trim();
if (!to) {
showError("send-status", "enter a recipient address");
$("send-status").classList.remove("hidden");
return;
}
if (!amount || isNaN(parseFloat(amount))) {
showError("send-status", "enter a valid amount");
$("send-status").classList.remove("hidden");
return;
}
// TODO: construct and send transaction via background
const el = $("send-status");
el.textContent = "transaction sent (stub)";
el.classList.remove("hidden");
});
$("btn-send-back").addEventListener("click", () => {
showView("main");
});
// -- Receive --
$("btn-receive-copy").addEventListener("click", () => {
const addr = $("receive-address").textContent;
if (addr) {
navigator.clipboard.writeText(addr);
}
});
$("btn-receive-back").addEventListener("click", () => {
showView("main");
});
// -- Add Token --
$("btn-add-token-confirm").addEventListener("click", () => {
const addr = $("add-token-address").value.trim();
if (!addr || !addr.startsWith("0x")) {
showError("add-token-error", "enter a valid contract address");
return;
}
hideError("add-token-error");
// TODO: look up token name/symbol/decimals from contract via background
state.tokens.push({
address: addr,
symbol: "TKN",
decimals: 18,
balance: "0.0000",
});
updateTokenList();
showView("main");
});
$("btn-add-token-back").addEventListener("click", () => {
showView("main");
});
// -- Settings --
$("btn-save-rpc").addEventListener("click", () => {
state.rpcUrl = $("settings-rpc").value.trim();
// TODO: persist via background
});
$("btn-derive-account").addEventListener("click", () => {
const idx = state.accounts.length;
// TODO: derive from seed via background
state.accounts.push({
address: `0x${idx.toString(16).padStart(40, "0")}`,
balance: "0.0000",
});
updateAccountSelector();
});
$("btn-show-seed").addEventListener("click", () => {
const display = $("settings-seed-display");
if (display.classList.contains("hidden")) {
// TODO: require password re-entry, get from background
display.textContent = state.mnemonic || "(no seed loaded)";
display.classList.remove("hidden");
} else {
display.classList.add("hidden");
}
});
$("btn-import-additional").addEventListener("click", () => {
showView("import");
});
$("btn-settings-back").addEventListener("click", () => {
updateAccountSelector();
updateTokenList();
showView("main");
});
// -- Approval --
$("btn-approve").addEventListener("click", () => {
// TODO: send approval to background
showView("main");
});
$("btn-reject").addEventListener("click", () => {
// TODO: send rejection to background
showView("main");
});
}
document.addEventListener("DOMContentLoaded", init);