Files
app/index.html
T
2026-10-04 02:44:52 +00:00

111 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Persimmons — Passwords, PATs & Plenty of P's</title>
<meta name="description" content="Useful P's for humans + agents.">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🍊</text></svg>">
<style>
:root{--ink:#241a14;--muted:#75675e;--line:#eadfd6;--paper:#fffdfa;--soft:#fff5ea;--orange:#f47b20;--orange2:#dc6916;--green:#2f7d4a;--red:#a23b2a;--shadow:0 20px 56px rgba(83,48,23,.075)}
*{box-sizing:border-box}[hidden]{display:none!important}html,body{margin:0;min-height:100%;background:var(--paper);color:var(--ink);font-family:ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
body{padding:24px}main{width:min(980px,100%);margin:auto;padding:30px 0 64px}
.row,.actions,.cardtop,.shellTop,.groupHead,.choicegrid{display:flex;align-items:center}
.hero{padding:18px 0 36px;text-align:center}.heroInner{width:min(900px,100%);margin:0 auto}.hero h1{display:grid;row-gap:.045em;margin:0;font-size:clamp(40px,6vw,68px);line-height:.9;letter-spacing:-.052em}
.hero h1 span{display:block}.heroName,.heroPermissions,.heroPromise{font-size:1em;line-height:.9}.heroName,.heroPromise{font-family:ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-weight:800;letter-spacing:-.052em}.heroFruit{display:inline!important;font-size:.62em;vertical-align:.08em;margin-right:.08em}.heroPermissions{color:var(--orange);font-family:Georgia,"Times New Roman",serif;font-style:italic;font-weight:600;letter-spacing:-.035em;margin:0}.heroPromise{color:var(--ink)}
.heroFlow{margin-top:28px;font-size:clamp(40px,6vw,68px);line-height:1.2}.pLine{margin:0 0 8px;font-size:clamp(17px,2vw,21px);font-weight:730;letter-spacing:-.018em}.lede{margin:0 auto;max-width:680px;color:var(--muted);font-size:15px;line-height:1.5}
button,.buttonLink{appearance:none;border:1px solid var(--line);border-radius:11px;background:#fff;padding:9px 13px;font:inherit;font-size:13px;font-weight:730;color:var(--ink);cursor:pointer;text-decoration:none;display:inline-flex;align-items:center;justify-content:center}button:hover,.buttonLink:hover{border-color:#d9c7ba}.primary{background:var(--orange);border-color:var(--orange);color:#fff}.primary:hover{background:var(--orange2);border-color:var(--orange2)}.ctaOutline{border-color:var(--orange);color:var(--orange);background:#fff}.ctaOutline:hover{border-color:var(--orange2);color:var(--orange2);background:var(--soft)}.danger{color:var(--red)}button:disabled{opacity:.5;cursor:default}
.vaultShell{border:1px solid var(--line);border-radius:22px;background:#fff;box-shadow:var(--shadow);overflow:hidden}.shellTop{justify-content:space-between;gap:16px;padding:18px 20px;border-bottom:1px solid var(--line);background:linear-gradient(180deg,#fff,#fffdfa)}
.shellTop h2{margin:0;font-size:20px;letter-spacing:-.02em}.hint{color:var(--muted);font-size:12px;line-height:1.5}.shellActions{display:flex;gap:8px;flex-wrap:wrap}.vaultGroup{padding:18px 20px}.vaultGroup+.vaultGroup{border-top:1px solid var(--line)}.groupHead{justify-content:space-between;gap:16px;margin-bottom:12px}.groupRule{border-top:1px solid var(--line);margin:0 -20px 16px}.groupHead strong{font-size:15px}.groupHead .hint{margin-top:3px}
.repoControls{min-width:min(360px,100%)}select,input,textarea{width:100%;border:1px solid var(--line);border-radius:10px;background:#fff;padding:10px 11px;font:inherit;color:var(--ink);outline:none}select:focus,input:focus,textarea:focus{border-color:#f0a36c;box-shadow:0 0 0 3px rgba(244,123,32,.08)}
.state{min-height:18px;margin-bottom:8px;color:var(--muted);font-size:12px}.state.good{color:var(--green)}.state.bad{color:var(--red)}.list{display:grid;gap:10px}.empty{padding:24px 14px;border:1px dashed var(--line);border-radius:13px;color:var(--muted);text-align:center;font-size:13px;line-height:1.5}.emptyCta{display:flex;align-items:center;justify-content:center;border:0;padding:18px 14px}
.connection{border:1px solid var(--line);border-radius:14px;padding:14px;background:#fff}.cardtop{justify-content:space-between;gap:14px;align-items:flex-start}.title{font:760 14px ui-monospace,SFMono-Regular,Menlo,monospace;overflow-wrap:anywhere}.account{margin-top:4px;color:var(--muted);font-size:12px}.badge{font-size:12px;white-space:nowrap}.badge.active{color:var(--green)}.badge.revoked{color:var(--red)}
.where{margin-top:10px;color:var(--muted);font-size:12px}.mask{margin:9px 0 10px;padding:9px 10px;border-radius:9px;background:var(--soft);font:13px ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.04em}.scope{display:inline-block;margin:3px 4px 0 0;padding:3px 7px;border:1px solid var(--line);border-radius:999px;font-size:11px}.meta{margin-top:8px;color:var(--muted);font-size:12px;line-height:1.5}.actions{flex-wrap:wrap;gap:8px;margin-top:11px}.rotatebox{display:none;margin-top:11px;padding-top:11px;border-top:1px solid var(--line)}.rotatebox.open{display:grid;grid-template-columns:1fr auto;gap:8px}
.scrim{position:fixed;inset:0;display:grid;place-items:center;padding:18px;background:rgba(36,26,20,.32);z-index:20}.scrim[hidden]{display:none}.composer{width:min(660px,100%);max-height:calc(100vh - 36px);overflow:auto;border:1px solid var(--line);border-radius:20px;background:var(--paper);box-shadow:0 30px 90px rgba(50,30,18,.24);padding:22px}.composerTop{display:flex;justify-content:space-between;gap:16px;align-items:flex-start}.composerTop h2{margin:0;font-size:22px}.close{padding:6px 10px}.step{margin-top:22px}.step[hidden]{display:none}.stepnum{color:var(--orange);font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:.08em}.question{margin:6px 0 14px;font-size:18px;font-weight:760}
.choicegrid{align-items:stretch;gap:10px;flex-wrap:wrap}.choice{flex:1 1 130px;flex-direction:column;align-items:flex-start;justify-content:center;text-align:left;padding:14px}.choice strong{display:block;width:100%}.choice small{display:block;width:100%;margin-top:4px;color:var(--muted);font-weight:500;line-height:1.25}.choice.selected{border-color:var(--orange);box-shadow:0 0 0 2px rgba(244,123,32,.1)}
.field{display:grid;gap:6px;margin-bottom:12px}.field label{font-size:12px;font-weight:720;color:var(--muted)}.fieldHelp{margin-top:-2px;color:var(--muted);font-size:11px}.checks{display:flex;gap:8px;flex-wrap:wrap}.check{display:flex;gap:6px;align-items:center;border:1px solid var(--line);border-radius:999px;padding:7px 9px;font-size:12px}.check input{width:auto}.advanced{margin-top:14px;border-top:1px solid var(--line);padding-top:13px}.advanced summary{cursor:pointer;font-size:13px;font-weight:720}.advanced .inside{margin-top:12px}.composerNav{display:flex;justify-content:space-between;gap:10px;margin-top:18px}
.footerNote{font-weight:400;font-style:normal;padding:18px 12px 0;color:var(--muted);font-size:11.5px;line-height:1.5;text-align:center}.footerNote strong{display:block;color:var(--ink);margin-bottom:3px}.apiP{font-style:italic;font-weight:inherit}
@media(max-width:720px){body{padding:16px}.hero{padding-top:22px}.shellTop,.groupHead{align-items:flex-start;flex-direction:column}.shellActions{width:100%}.shellActions button{flex:1}.repoControls{width:100%;min-width:0}.choicegrid{display:grid;grid-template-columns:1fr 1fr}.rotatebox.open{grid-template-columns:1fr}}
</style>
</head>
<body>
<main>
<section class="hero">
<div class="heroInner">
<h1><span class="heroName">Persimmons</span><span class="heroPermissions">permissions</span><span class="heroPromise">put plainly</span></h1>
<div class="heroFlow" aria-label="Orange to water to robot">🍊 → 💧 → 🤖</div>
</div>
</section>
<section class="vaultShell">
<div class="shellTop">
<div><h2>Your Persimmons</h2><div class="hint"><span id="userIdentity">Resolving signed-in user…</span></div></div>
<div class="shellActions"><button id="refreshAll">Refresh</button><button id="signOutButton" hidden>Sign out</button><button class="primary" id="addButton">+ Add</button></div>
</div>
<div class="vaultGroup">
<div class="groupHead">
<div><strong>👤 My vault</strong><div class="hint">Available to you across Teahub.</div></div>
</div>
<div class="groupRule" aria-hidden="true"></div>
<div id="userState" class="state">Loading…</div>
<div id="userList" class="list"></div>
</div>
<div class="vaultGroup">
<div class="groupHead">
<div><strong>📦 Repository vaults</strong><div class="hint">Caller-authorized repository secrets.</div></div>
<div class="repoControls"><select id="repoSelect"><option value="">Loading repositories…</option></select></div>
</div>
<div id="repoState" class="state">Discovering writable repositories…</div>
<div id="repoList" class="list"></div>
</div>
</section>
</main>
<div class="scrim" id="composerScrim" hidden>
<section class="composer" role="dialog" aria-modal="true" aria-labelledby="composerTitle">
<div class="composerTop"><div><div class="stepnum">Add a Persimmon</div><h2 id="composerTitle">Simple now, reusable later.</h2><div class="hint" id="stepSummary">What are you connecting?</div></div><button class="close" id="closeComposer">×</button></div>
<div class="step" id="step1">
<div class="question">What are you connecting?</div>
<div class="choicegrid">
<button class="choice provider" data-provider="Gitea"><strong>Gitea</strong><small>Personal access token</small></button>
<button class="choice provider" data-provider="Cloudflare"><strong>Cloudflare</strong><small>API token</small></button>
<button class="choice provider" data-provider="Fly"><strong>Fly</strong><small>Access token</small></button>
<button class="choice provider" data-provider="Other"><strong>Other</strong><small>Password, key or token</small></button>
</div>
</div>
<div class="step" id="step2" hidden>
<div class="question">Where should it be available?</div>
<div class="choicegrid">
<button class="choice vault" data-vault="user"><strong>👤 My vault</strong><small>Reusable by you across Teahub.</small></button>
<button class="choice vault" data-vault="repository"><strong>📦 A repository</strong><small>Only in that repository context.</small></button>
</div>
<div class="field" id="composerRepoField" hidden style="margin-top:12px"><label>Repository</label><select id="composerRepo"><option value="">Choose a repository…</option></select></div>
</div>
<div class="step" id="step3" hidden>
<div class="question">Name it, then add only what it needs.</div>
<div class="field"><label>Name</label><input id="connectionName" placeholder="e.g. GITEA_TOKEN" autocomplete="off"><div class="fieldHelp">This becomes the Gitea secret name and the Persimmon title.</div></div>
<div class="field"><label id="accountLabel">Account</label><input id="accountInput" placeholder=""></div>
<div class="field" id="typeField" hidden><label>Type</label><select id="typeInput"><option>Password</option><option>PAT</option><option>API key</option><option>Token</option><option>Other</option></select></div>
<div class="field"><label id="valueLabel">Value</label><input id="valueInput" type="password" autocomplete="new-password" spellcheck="false"></div>
<div class="field" id="scopeField"><label>Intended capabilities</label><div class="checks" id="scopeChoices"></div></div>
<details class="advanced"><summary>Advanced</summary><div class="inside">
<div class="field"><label>Expires</label><input id="expiresInput" placeholder="optional"></div>
<div class="field"><label>Notes</label><textarea id="notesInput" placeholder="optional"></textarea></div>
</div></details>
</div>
<div class="state" id="composerState"></div>
<div class="composerNav"><button id="backButton" hidden>Back</button><div class="actions"><button class="primary" id="nextButton">Next</button><button class="primary" id="saveButton" hidden>Save to vault</button></div></div>
</section>
</div>
<script type="module" src="./app.js"></script>
</body>
</html>