Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
HTML
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827<!DOCTYPE html><html><head> <meta charset="UTF-8"> <meta name="viewport" content="width=380"> <title>Keeps</title> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Comic+Relief:wght@400;700&family=Noto+Sans+Mono:wght@400;700&display=swap" rel="stylesheet"> <style> /* Fonts */ @font-face { font-family: 'YWFTProcessing-Regular'; src: url('https://aesthetic.computer/type/webfonts/ywft-processing-regular.woff2') format('woff2'), url('https://aesthetic.computer/type/webfonts/ywft-processing-regular.woff') format('woff'); font-weight: normal; font-style: normal; font-display: swap; } @font-face { font-family: 'YWFTProcessing-Bold'; src: url('https://aesthetic.computer/type/webfonts/ywft-processing-bold.woff2') format('woff2'), url('https://aesthetic.computer/type/webfonts/ywft-processing-bold.woff') format('woff'); font-weight: bold; font-style: normal; font-display: swap; }
/* CSS Variables */ :root { --bg-primary: #f7f7f7; --bg-secondary: #e8e8e8; --bg-tertiary: white; --text-primary: #111; --text-secondary: #333; --text-tertiary: #666; --border-color: #ddd; --ac-purple: rgb(205, 92, 155); --font-mono: 'Noto Sans Mono', 'SF Mono', 'Monaco', 'Menlo', 'Consolas', monospace; --font-display: 'YWFTProcessing-Regular', monospace; --font-fun: 'Comic Relief', 'Comic Sans MS', cursive; }
@media (prefers-color-scheme: dark) { :root { --bg-primary: #1e1e1e; --bg-secondary: #252526; --bg-tertiary: #2d2d30; --text-primary: #d4d4d4; --text-secondary: #d4d4d4; --text-tertiary: #858585; --border-color: #3e3e42; } }
[data-theme="light"] { --bg-primary: #f7f7f7; --bg-secondary: #e8e8e8; --bg-tertiary: white; --text-primary: #111; --text-secondary: #333; --text-tertiary: #666; --border-color: #ddd; }
[data-theme="dark"] { --bg-primary: #1e1e1e; --bg-secondary: #252526; --bg-tertiary: #2d2d30; --text-primary: #d4d4d4; --text-secondary: #d4d4d4; --text-tertiary: #858585; --border-color: #3e3e42; }
/* Reset */ * { box-sizing: border-box; margin: 0; padding: 0; }
::-webkit-scrollbar { width: 6px; } ::-webkit-scrollbar-track { background: var(--bg-secondary); } ::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 3px; } ::-webkit-scrollbar-thumb:hover { background: var(--text-tertiary); }
body { width: 380px; min-height: 500px; font-family: var(--font-mono); font-size: 13px; background: var(--bg-primary); color: var(--text-primary); position: relative; overflow-x: hidden; }
/* ============================================ STATE CONTAINER — fades between states ============================================ */ .state { display: none; width: 100%; min-height: 500px; opacity: 0; transition: opacity 150ms ease; } .state.active { display: flex; flex-direction: column; } .state.visible { opacity: 1; }
/* ============================================ STATE 1: NO WALLET ============================================ */ .no-wallet { align-items: center; justify-content: center; padding: 40px 32px; background: var(--bg-primary); }
.no-wallet .logo-title { font-family: var(--font-display); font-size: 36px; color: var(--text-tertiary); margin-bottom: 40px; letter-spacing: 2px; }
.no-wallet .action-buttons { display: flex; gap: 12px; width: 100%; max-width: 280px; }
.btn-outline { flex: 1; padding: 12px 20px; border: 1.5px solid var(--border-color); border-radius: 20px; background: transparent; color: var(--text-primary); font-family: var(--font-mono); font-size: 13px; cursor: pointer; transition: all 150ms ease; } .btn-outline:hover { border-color: var(--ac-purple); color: var(--ac-purple); }
/* Inline forms that slide down */ .inline-form { width: 100%; max-width: 300px; overflow: hidden; max-height: 0; opacity: 0; transition: max-height 250ms ease, opacity 200ms ease, margin 250ms ease; margin-top: 0; } .inline-form.open { max-height: 500px; opacity: 1; margin-top: 24px; }
.form-field { margin-bottom: 12px; }
.form-label { display: block; font-size: 11px; color: var(--text-tertiary); margin-bottom: 4px; }
.form-input { width: 100%; padding: 10px 12px; background: var(--bg-tertiary); border: 1.5px solid var(--border-color); border-radius: 8px; color: var(--text-primary); font-family: var(--font-mono); font-size: 13px; outline: none; transition: border-color 150ms ease; } .form-input:focus { border-color: var(--ac-purple); } .form-input.error { border-color: #e74c3c; }
textarea.form-input { min-height: 80px; resize: vertical; }
.form-submit { width: 100%; padding: 10px; border: none; border-radius: 20px; background: var(--ac-purple); color: white; font-family: var(--font-mono); font-size: 13px; cursor: pointer; transition: opacity 150ms ease; } .form-submit:hover { opacity: 0.85; } .form-submit:disabled { opacity: 0.5; cursor: not-allowed; }
.form-error { font-size: 11px; color: #e74c3c; margin-top: 8px; min-height: 16px; }
/* Import tabs */ .import-tabs { display: flex; gap: 0; margin-bottom: 12px; border-bottom: 1px solid var(--border-color); } .import-tab-btn { background: none; border: none; padding: 6px 12px; font-family: var(--font-mono); font-size: 11px; color: var(--text-tertiary); cursor: pointer; border-bottom: 2px solid transparent; transition: all 150ms ease; } .import-tab-btn:hover { color: var(--text-primary); } .import-tab-btn.active { color: var(--ac-purple); border-bottom-color: var(--ac-purple); } .import-tab-content { display: none; } .import-tab-content.active { display: block; }
/* ============================================ STATE 2: LOCKED ============================================ */ .locked { align-items: center; justify-content: center; padding: 40px 32px; background: var(--bg-secondary); }
.locked .logo-title { font-family: var(--font-display); font-size: 28px; color: var(--text-tertiary); margin-bottom: 32px; letter-spacing: 1px; }
.locked .unlock-field { width: 100%; max-width: 260px; position: relative; }
.locked .unlock-input { width: 100%; padding: 12px 40px 12px 14px; background: var(--bg-tertiary); border: 1.5px solid var(--border-color); border-radius: 10px; color: var(--text-primary); font-family: var(--font-mono); font-size: 14px; outline: none; transition: border-color 150ms ease; } .locked .unlock-input:focus { border-color: var(--ac-purple); } .locked .unlock-input.error { border-color: #e74c3c; animation: shake 300ms ease; }
.unlock-icon { position: absolute; right: 12px; top: 50%; transform: translateY(-50%); background: none; border: none; color: var(--text-tertiary); cursor: pointer; font-size: 16px; padding: 4px; line-height: 1; transition: color 150ms; } .unlock-icon:hover { color: var(--ac-purple); }
@keyframes shake { 0%, 100% { transform: translateX(0); } 20% { transform: translateX(-6px); } 40% { transform: translateX(6px); } 60% { transform: translateX(-4px); } 80% { transform: translateX(4px); } }
/* ============================================ STATE 3: UNLOCKED — keeps gallery ============================================ */ .unlocked { background: var(--bg-primary); padding: 0; min-height: 500px; }
/* Top bar */ .top-bar { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px; position: sticky; top: 0; z-index: 10; background: var(--bg-primary); }
.top-bar-left { display: flex; align-items: center; gap: 8px; }
.network-pill { font-size: 9px; font-family: var(--font-mono); padding: 3px 8px; border-radius: 10px; background: var(--bg-tertiary); border: 1px solid var(--border-color); color: var(--text-tertiary); cursor: pointer; transition: all 150ms; text-transform: lowercase; letter-spacing: 0.5px; } .network-pill:hover { border-color: var(--ac-purple); color: var(--ac-purple); }
.top-bar-right { display: flex; align-items: center; gap: 8px; }
.balance-toggle { font-size: 13px; background: none; border: none; color: var(--text-tertiary); cursor: pointer; font-family: var(--font-mono); padding: 3px 6px; border-radius: 6px; transition: all 150ms; } .balance-toggle:hover { color: var(--text-primary); background: var(--bg-secondary); } .balance-toggle .balance-amount { font-size: 11px; margin-left: 2px; }
.lock-btn { background: none; border: none; color: var(--text-tertiary); cursor: pointer; font-size: 14px; padding: 3px 4px; border-radius: 6px; transition: all 150ms; line-height: 1; } .lock-btn:hover { color: var(--text-primary); background: var(--bg-secondary); }
/* Keeps grid */ .keeps-container { padding: 4px 10px 20px; flex: 1; }
.keeps-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.keep-thumb { aspect-ratio: 1; border-radius: 8px; overflow: hidden; background: var(--bg-secondary); cursor: pointer; position: relative; transition: transform 150ms ease; } .keep-thumb:hover { transform: scale(1.03); } .keep-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; } .keep-thumb .keep-name-overlay { position: absolute; bottom: 0; left: 0; right: 0; padding: 4px 6px; background: linear-gradient(transparent, rgba(0,0,0,0.65)); color: white; font-size: 9px; font-family: var(--font-mono); opacity: 0; transition: opacity 150ms; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .keep-thumb:hover .keep-name-overlay { opacity: 1; }
/* Loading skeleton */ .keep-thumb.loading { animation: pulse 1.5s ease infinite; } @keyframes pulse { 0%, 100% { opacity: 0.6; } 50% { opacity: 0.3; } }
/* Empty state */ .keeps-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 60px 20px; color: var(--text-tertiary); font-size: 13px; text-align: center; } .keeps-empty a { color: var(--ac-purple); text-decoration: none; margin-top: 8px; font-size: 12px; } .keeps-empty a:hover { text-decoration: underline; }
/* Loading spinner */ .loading-indicator { display: flex; align-items: center; justify-content: center; padding: 40px; color: var(--text-tertiary); font-size: 12px; } .loading-dot { display: inline-block; width: 4px; height: 4px; border-radius: 50%; background: var(--text-tertiary); margin: 0 2px; animation: dotPulse 1.2s ease infinite; } .loading-dot:nth-child(2) { animation-delay: 0.2s; } .loading-dot:nth-child(3) { animation-delay: 0.4s; } @keyframes dotPulse { 0%, 100% { opacity: 0.3; transform: scale(0.8); } 50% { opacity: 1; transform: scale(1.2); } }
/* ============================================ KEEP DETAIL OVERLAY ============================================ */ .keep-detail-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 100; display: none; align-items: center; justify-content: center; opacity: 0; transition: opacity 200ms ease; } .keep-detail-overlay.open { display: flex; } .keep-detail-overlay.visible { opacity: 1; }
.keep-detail { width: 340px; max-height: 460px; background: var(--bg-tertiary); border-radius: 12px; overflow: hidden; transform: scale(0.85); transition: transform 200ms ease-out; display: flex; flex-direction: column; } .keep-detail-overlay.visible .keep-detail { transform: scale(1); }
.keep-detail-image { width: 100%; aspect-ratio: 1; object-fit: cover; display: block; background: var(--bg-secondary); }
.keep-detail-info { padding: 14px 16px; }
.keep-detail-title { font-family: var(--font-mono); font-size: 14px; font-weight: 700; color: var(--text-primary); margin-bottom: 4px; }
.keep-detail-artist { font-size: 11px; color: var(--text-tertiary); }
/* ============================================ SEED PHRASE BACKUP OVERLAY ============================================ */ .seed-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: var(--bg-primary); z-index: 200; display: none; flex-direction: column; align-items: center; padding: 24px 20px; overflow-y: auto; opacity: 0; transition: opacity 200ms ease; } .seed-overlay.open { display: flex; } .seed-overlay.visible { opacity: 1; }
.seed-overlay-title { font-family: var(--font-display); font-size: 20px; color: var(--text-primary); margin-bottom: 8px; }
.seed-overlay-desc { font-size: 11px; color: var(--text-tertiary); text-align: center; margin-bottom: 20px; max-width: 300px; line-height: 1.5; }
.seed-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; width: 100%; max-width: 340px; margin-bottom: 24px; }
.seed-word { background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 6px; padding: 8px 6px; font-family: var(--font-mono); font-size: 11px; color: var(--text-primary); text-align: center; }
.seed-word-num { font-size: 9px; color: var(--text-tertiary); margin-right: 2px; }
.seed-dismiss { padding: 12px 32px; border: 1.5px solid var(--border-color); border-radius: 20px; background: transparent; color: var(--text-primary); font-family: var(--font-mono); font-size: 13px; cursor: pointer; transition: all 150ms; } .seed-dismiss:hover { border-color: var(--ac-purple); color: var(--ac-purple); }
/* ============================================ CONNECTED DAPPS DOTS ============================================ */ .dapp-dots { display: flex; justify-content: center; gap: 6px; padding: 8px 10px 12px; }
.dapp-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--ac-purple); cursor: pointer; position: relative; transition: transform 150ms; } .dapp-dot:hover { transform: scale(1.5); }
.dapp-tooltip { position: absolute; bottom: 14px; left: 50%; transform: translateX(-50%); background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 8px; padding: 8px 12px; white-space: nowrap; font-size: 11px; color: var(--text-primary); display: none; z-index: 50; box-shadow: 0 2px 8px rgba(0,0,0,0.15); } .dapp-tooltip.show { display: block; } .dapp-tooltip .disconnect-link { color: #e74c3c; cursor: pointer; margin-left: 8px; font-size: 10px; } .dapp-tooltip .disconnect-link:hover { text-decoration: underline; } </style></head><body>
<!-- STATE 1: No Wallet --> <div class="state no-wallet" id="state-no-wallet"> <div class="logo-title">Keeps</div> <div class="action-buttons"> <button class="btn-outline" id="btn-show-create">Create</button> <button class="btn-outline" id="btn-show-import">Import</button> </div>
<!-- Create inline form --> <div class="inline-form" id="create-form"> <div class="form-field"> <label class="form-label">Password (min 8 characters)</label> <input type="password" class="form-input" id="create-password" placeholder="Create password" autocomplete="new-password"> </div> <div class="form-field"> <label class="form-label">Confirm password</label> <input type="password" class="form-input" id="create-password-confirm" placeholder="Confirm password" autocomplete="new-password"> </div> <button class="form-submit" id="btn-create-wallet">Create Wallet</button> <div class="form-error" id="create-error"></div> </div>
<!-- Import inline form --> <div class="inline-form" id="import-form"> <div class="import-tabs"> <button class="import-tab-btn active" data-import-tab="seed">Seed Phrase</button> <button class="import-tab-btn" data-import-tab="key">Private Key</button> </div> <div class="import-tab-content active" id="import-seed-tab"> <div class="form-field"> <label class="form-label">12 or 24 word seed phrase</label> <textarea class="form-input" id="import-mnemonic" placeholder="Enter seed phrase from Temple, Kukai, or any Tezos wallet" rows="3"></textarea> </div> </div> <div class="import-tab-content" id="import-key-tab"> <div class="form-field"> <label class="form-label">Private key (edsk...)</label> <input type="password" class="form-input" id="import-private-key" placeholder="edsk..."> </div> </div> <div class="form-field"> <label class="form-label">Password (min 8 characters)</label> <input type="password" class="form-input" id="import-password" placeholder="Create password" autocomplete="new-password"> </div> <button class="form-submit" id="btn-import-wallet">Import Wallet</button> <div class="form-error" id="import-error"></div> </div> </div>
<!-- STATE 2: Locked --> <div class="state locked" id="state-locked"> <div class="logo-title">Keeps</div> <div class="unlock-field"> <input type="password" class="unlock-input" id="unlock-password" placeholder="Password" autocomplete="current-password"> <button class="unlock-icon" id="btn-unlock" title="Unlock">🔓</button> </div> </div>
<!-- STATE 3: Unlocked --> <div class="state unlocked" id="state-unlocked"> <div class="top-bar"> <div class="top-bar-left"> <button class="network-pill" id="network-pill">ghostnet</button> </div> <div class="top-bar-right"> <button class="balance-toggle" id="balance-toggle">$</button> <button class="lock-btn" id="btn-lock" title="Lock">🔒</button> </div> </div>
<div class="keeps-container" id="keeps-container"> <!-- Grid, empty state, or loading will be rendered here by JS --> </div>
<div class="dapp-dots" id="dapp-dots"></div> </div>
<!-- Keep Detail Overlay --> <div class="keep-detail-overlay" id="keep-detail-overlay"> <div class="keep-detail" id="keep-detail"> <img class="keep-detail-image" id="keep-detail-image" src="" alt=""> <div class="keep-detail-info"> <div class="keep-detail-title" id="keep-detail-title"></div> <div class="keep-detail-artist" id="keep-detail-artist"></div> </div> </div> </div>
<!-- Seed Phrase Backup Overlay --> <div class="seed-overlay" id="seed-overlay"> <div class="seed-overlay-title">Back up your seed phrase</div> <div class="seed-overlay-desc">Write these 24 words down in order and store them somewhere safe. This is the only way to recover your wallet.</div> <div class="seed-grid" id="seed-grid"></div> <button class="seed-dismiss" id="seed-dismiss">I've saved these</button> </div>
<script src="popup.js"></script></body></html>