Files
instantadpay/public/admin.html
T
martbost 9d14af0898 Top menu: 'How it works' and 'Ad packages' anchor to homepage sections (#how, #packages); sections clear the sticky nav
The Ad packages link pointed at /#ladder, an id that did not exist on the homepage.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 16:54:55 -05:00

351 lines
26 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="robots" content="noindex,nofollow">
<title>Admin | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910h">
<style>
.adm-table{width:100%;table-layout:auto}
.adm-table td.act,.adm-table th:last-child{width:1%;white-space:nowrap} /* the action column keeps its full width; text columns give way */
.adm-table td:first-child{overflow-wrap:anywhere;word-break:break-word}
.adm-table th,.adm-table td{padding:8px 10px;text-align:left;vertical-align:top;border-bottom:1px solid var(--line);font-size:13.5px}
.adm-table td.when{white-space:nowrap}
.adm-table th{color:var(--muted);font-size:11.5px;text-transform:uppercase;letter-spacing:.08em}
.adm-table .act{white-space:nowrap}
.adm-table .act button{margin-right:6px}
.st{display:inline-block;border-radius:999px;padding:2px 9px;font-size:11.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}
.st.active{background:rgba(67,232,195,.14);color:var(--mint)}
.st.paused{background:rgba(255,178,56,.14);color:var(--amber)}
.st.out{background:rgba(255,255,255,.08);color:var(--muted)}
.house-tag{display:inline-block;border:1px solid rgba(157,125,255,.5);color:var(--violet);border-radius:6px;padding:1px 6px;font-size:10.5px;font-weight:700;margin-left:6px;vertical-align:middle}
.adm-form p{margin:0 0 10px}
.adm-form label.small{display:block;margin-bottom:4px}
.trunc{max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block;vertical-align:bottom}
textarea.json{font-family:var(--mono);font-size:12.5px;width:100%;min-height:260px}
.ph-chips{display:inline-flex;gap:6px;flex-wrap:wrap;margin-left:6px;vertical-align:middle}
.ph-chips .chip-t{padding:2px 10px;font-size:11.5px;font-family:var(--mono);text-transform:none;letter-spacing:0;cursor:pointer;background:transparent}
.drip-steps{display:grid;gap:12px}
.drip-step{border:1px solid var(--line);border-radius:12px;padding:14px 16px;background:rgba(4,8,7,.4)}
.drip-step .ds-head{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:8px}
.drip-step .ds-n{font-family:var(--mono);font-size:12px;color:var(--mint);letter-spacing:.08em}
.drip-step .ds-when{display:flex;align-items:center;gap:6px;font-size:13px;color:var(--muted)}
.drip-step .ds-when input{width:80px;text-align:right}
.drip-step .ds-when b{color:var(--ink)}
.drip-step .ds-tools{margin-left:auto;display:flex;gap:6px;flex-wrap:wrap}
.drip-step .ds-tools .btn{padding:6px 12px;font-size:12px}
.drip-step input.ds-subject{width:100%;margin-bottom:8px;font-weight:700}
.drip-step textarea.ds-body{width:100%;min-height:200px;font-size:13.5px;line-height:1.5}
.rates-form{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px 22px}
.rates-form .rf{display:flex;flex-direction:column;gap:4px}
.rates-form .rf label{font-size:13px;font-weight:700}
.rates-form .rf .hint{font-size:11.5px;color:var(--muted)}
.rates-form .rf input[type=number]{max-width:160px}
.rates-form .rf.wide{grid-column:1/-1}
.rates-form .sub-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px}
.rates-form .sub-grid label{font-size:12px;font-weight:600;color:var(--muted);display:flex;flex-direction:column;gap:3px}
.rates-form table.tiers{width:auto;font-size:13px}
.rates-form table.tiers td,.rates-form table.tiers th{padding:4px 8px;border:0}
.rates-form table.tiers input{width:90px}
.kv-form{display:grid;gap:8px}
.kv-row{display:grid;grid-template-columns:220px 1fr auto;gap:10px;align-items:center}
.kv-row .k{font-family:var(--mono);font-size:12.5px;overflow-wrap:anywhere}
.kv-row input[type=checkbox]{width:auto;justify-self:start}
@media(max-width:700px){.kv-row{grid-template-columns:1fr}}
</style>
</head>
<body class="bo-body">
<!-- signed-out: admin sign-in -->
<div id="authArea">
<div class="wrap" style="max-width:520px">
<section class="hero" style="padding:64px 0 10px;text-align:left">
<p class="eyebrow">InstantAdPay</p>
<h1 style="font-size:34px">Admin <em>sign in</em></h1>
<p class="lead" style="font-size:16px">Only the admin address can sign in here. A one-time code goes to that inbox.</p>
</section>
<div class="card">
<p><input id="adEmail" type="email" placeholder="Admin email" autocomplete="email" style="width:100%"></p>
<p id="adCodeRow" hidden><input id="adCode" inputmode="numeric" placeholder="6-digit code from your email" style="width:100%"></p>
<p id="adErr" class="small" style="color:var(--bad)" hidden></p>
<p style="margin:0;display:flex;gap:10px">
<button class="btn" id="adSend" type="button">Send code</button>
<button class="btn" id="adVerify" type="button" hidden>Sign in</button>
</p>
<p class="small muted" style="margin:14px 0 0"><a href="/my">Back to the member area</a></p>
</div>
</div>
</div>
<!-- signed-in: admin shell -->
<div id="adminArea" class="bo" hidden>
<aside class="bo-side" id="boSide">
<a class="logo" href="/"><img src="/logo.png" alt="InstantAdPay" style="height:32px;display:block"></a>
<span class="byline">Brought to you by the <b>Crypto Team Build Network</b></span>
<nav class="bo-menu" aria-label="Admin menu">
<button data-pane="overview" class="on" type="button"><svg viewBox="0 0 24 24"><rect x="3" y="3" width="8" height="8" rx="2"/><rect x="13" y="3" width="8" height="8" rx="2"/><rect x="3" y="13" width="8" height="8" rx="2"/><rect x="13" y="13" width="8" height="8" rx="2"/></svg>Overview</button>
<button data-pane="house" type="button"><svg viewBox="0 0 24 24"><path d="M3 11l14-5v12L3 13v-2z"/><path d="M17 8a4 4 0 0 1 0 8M7 13v5a2 2 0 0 0 4 0v-3"/></svg>House ads</button>
<button data-pane="campaigns" type="button"><svg viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h10"/></svg>All campaigns</button>
<button data-pane="members" type="button"><svg viewBox="0 0 24 24"><circle cx="12" cy="7" r="3.2"/><circle cx="5" cy="17" r="2.6"/><circle cx="19" cy="17" r="2.6"/><path d="M12 10v3M12 13l-5 2M12 13l5 2"/></svg>Members</button>
<button data-pane="reports" type="button"><svg viewBox="0 0 24 24"><path d="M12 3l9 16H3z"/><path d="M12 10v4M12 17v.5"/></svg>Reports<span class="pill" id="repBadge" hidden></span></button>
<button data-pane="traffic" type="button"><svg viewBox="0 0 24 24"><path d="M3 17l6-6 4 4 8-8"/><path d="M14 7h7v7"/></svg>Traffic</button>
<button data-pane="pnl" type="button"><svg viewBox="0 0 24 24"><path d="M4 19V5M4 19h16"/><path d="M8 15l3-4 3 2 5-6"/></svg>P&amp;L</button>
<button data-pane="settings" type="button"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></svg>Settings</button>
</nav>
<div class="bo-links">
<span class="bo-cap">Site</span>
<a href="/my">Member area</a>
<a href="/ledger" target="_blank" rel="noopener">Live ledger</a>
<a href="/" target="_blank" rel="noopener">Home page</a>
</div>
<div class="bo-foot">
<span id="adWho" class="small muted">…</span>
<a href="#" id="adLogout" class="small">Log out</a>
</div>
</aside>
<div class="bo-main">
<header class="bo-top">
<button id="boBurger" aria-label="Menu" type="button">☰</button>
<h2 id="boTitle" style="margin:0">Overview</h2>
<span class="small muted" id="chainLine"></span>
</header>
<main class="bo-content">
<div class="pane" id="pane-overview">
<div class="grid" style="grid-template-columns:repeat(auto-fit,minmax(200px,1fr));margin-top:0">
<div class="statx"><div><div class="nv" id="ovAccounts">0</div><div class="lb">Accounts</div></div></div>
<div class="statx c-cyan"><div><div class="nv" id="ovMembers">0</div><div class="lb">On-chain members</div></div></div>
<div class="statx c-violet"><div><div class="nv" id="ovActive">0</div><div class="lb">Active campaigns</div><span class="chip flat" id="ovCampSub"></span></div></div>
<div class="statx c-amber"><div><div class="nv" id="ovReports">0</div><div class="lb">Open reports</div><span class="chip flat" id="ovBurnSub"></span></div></div>
<div class="statx"><div><div class="nv" id="ovDrips">0</div><div class="lb">Follow-ups in flight</div><span class="chip flat" id="ovDripSub"></span></div></div>
</div>
<div class="grid c2" style="margin-top:16px">
<div class="card">
<div class="card-head"><h3>Campaigns by format</h3><span class="sub">all owners</span></div>
<div id="ovByType" class="small muted">…</div>
</div>
<div class="card">
<div class="card-head"><h3>Chain</h3><span class="sub">live config</span></div>
<div id="ovChain" class="small">…</div>
</div>
</div>
<div class="card">
<div class="card-head"><h3>Quick actions</h3></div>
<p style="display:flex;gap:10px;flex-wrap:wrap;margin:0">
<button class="btn small" type="button" data-goto="house">Place a house ad</button>
<button class="btn small sec" type="button" data-goto="campaigns">Review campaigns</button>
<button class="btn small sec" type="button" data-goto="members">Members</button>
</p>
</div>
</div>
<div class="pane" id="pane-house" hidden>
<div class="card adm-form">
<div class="card-head"><h3>New house ad</h3><span class="sub">free: nothing is charged, the budget is only a delivery cap</span></div>
<div class="grid c3">
<p><select id="hType" style="width:100%">
<option value="banner">Banner (per impression)</option>
<option value="text">Text ad (per impression)</option>
<option value="login">Login ad (full-screen after sign-in)</option>
<option value="solo">Solo ad (inbox delivery)</option>
<option value="video">Video ad (watch to earn)</option>
<option value="featured">Featured link (rotation)</option>
<option value="visits">Verified visits</option>
</select></p>
<p><input id="hName" placeholder="Campaign name" style="width:100%"></p>
<p><input id="hBudget" type="number" min="10" placeholder="Delivery cap in credits (default 100,000)" style="width:100%"></p>
</div>
<p><input id="hTarget" placeholder="Target URL (https://…)" autocomplete="off" style="width:100%"></p>
<p class="small muted" style="margin-top:-4px">Banner and text targets are framed in the ad viewer, so they must allow framing (checked on submit).</p>
<div id="hBannerRow">
<p><input id="hImage" placeholder="Image URL (https://… or /uploads/…)" autocomplete="off" style="width:100%"></p>
<p><input type="file" id="hImageFile" accept="image/png,image/jpeg,image/webp,image/gif" hidden>
<button type="button" class="btn small sec" id="hImageUploadBtn">Upload image</button>
<span id="hImageInfo" class="small muted"></span></p>
<p><select id="hSize" style="width:100%"></select></p>
</div>
<div id="hTextRow" hidden>
<p><input id="hTitle" maxlength="60" placeholder="Headline (max 60)" style="width:100%"></p>
<p><input id="hBody" maxlength="140" placeholder="Ad text (max 140)" style="width:100%"></p>
</div>
<div id="hSoloRow" hidden>
<p><input id="hSoloTitle" maxlength="80" placeholder="Subject line (max 80)" style="width:100%"></p>
<p><textarea id="hSoloBody" rows="8" placeholder="Message body. Simple HTML is fine (bold, lists, links, images)." style="width:100%"></textarea></p>
<p><input id="hSoloCta" maxlength="30" placeholder="Call-to-action label (default: Learn more)" style="width:100%"></p>
<p class="small muted" id="hSoloHint"></p>
</div>
<div id="hVideoRow" hidden>
<p><input id="hVideoUrl" placeholder="Video URL (direct https link ending .mp4 or .webm, or /uploads/…)" autocomplete="off" style="width:100%"></p>
<p><input type="file" id="hVideoFile" accept="video/mp4,video/webm" hidden>
<button type="button" class="btn small sec" id="hVideoUploadBtn">Upload video</button>
<span id="hVideoInfo" class="small muted"></span></p>
<p><input id="hVideoTitle" maxlength="80" placeholder="Video title (optional)" style="width:100%"></p>
<p><select id="hWatchSecs" style="width:100%"></select></p>
<p><input id="hVideoCta" maxlength="30" placeholder="Call-to-action label (default: Learn more)" style="width:100%"></p>
</div>
<div id="hFeatRow" hidden>
<p><input id="hFeatTitle" maxlength="70" placeholder="Headline for the featured link" style="width:100%"></p>
<div class="grid c2">
<p><select id="hFeatDays" style="width:100%"></select></p>
<p><input id="hFeatStart" type="number" min="0" value="0" placeholder="Start in N days (0 = today)" style="width:100%"></p>
</div>
<p class="small muted" id="hFeatHint"></p>
</div>
<div id="hVisitsRow" hidden>
<p><input id="hVisitTitle" maxlength="80" placeholder="Headline (what members see before visiting)" style="width:100%"></p>
<p><input id="hVisitCount" type="number" min="20" placeholder="How many verified visits?" style="width:100%"></p>
<p class="small muted" id="hVisitHint"></p>
</div>
<p id="hErr" class="small" style="color:var(--bad)" hidden></p>
<button class="btn" id="hCreate" type="button">Place house ad</button>
</div>
<div class="card">
<div class="card-head"><h3>House ads</h3><span class="sub" id="houseSub">running free</span></div>
<div class="tablewrap"><table class="adm-table" id="houseTable"></table></div>
</div>
<div class="card">
<div class="card-head"><h3>Wall fallback ads</h3><span class="sub" id="wallAdsSub">shown on member walls in positions they have not earned or filled, when no upline banner exists</span></div>
<p class="small muted">A member's wall has three positions. Position 1 is theirs. Positions 2 and 3 show an upline's banner until the member earns them (2 and 5 qualifying buyers); when there is no upline banner, one of these ads shows instead. They rotate in order across walls. Leave the list empty to fall back to a plain InstantAdPay card.</p>
<div id="wallAdsList" class="drip-steps"></div>
<p style="display:flex;gap:10px;flex-wrap:wrap;margin:12px 0 0">
<button class="btn small" id="wallAdsSave" type="button">Save wall ads</button>
<button class="btn small sec" id="wallAdsAdd" type="button">+ Add a wall ad</button>
</p>
<p id="wallAdsErr" class="small" style="color:var(--bad)" hidden></p>
</div>
</div>
<div class="pane" id="pane-campaigns" hidden>
<div class="card">
<div class="card-head"><h3>All campaigns</h3><span class="sub" id="campSub">newest first</span></div>
<p style="margin:0 0 10px"><input id="campFilter" placeholder="Filter by owner, name, type or status" style="width:100%"></p>
<div class="tablewrap"><table class="adm-table" id="campTable"></table></div>
</div>
</div>
<div class="pane" id="pane-members" hidden>
<div class="card">
<div class="card-head"><h3>Holding tank</h3><span class="sub" id="tankAdmSub">free members with no sponsor, and who adopted whom</span></div>
<div class="tablewrap"><table class="adm-table" id="tankWait"></table></div>
<p class="small muted" style="margin:12px 0 6px">Adoptions (newest first)</p>
<div class="tablewrap"><table class="adm-table" id="tankAdopt"></table></div>
</div>
<div class="card">
<div class="card-head"><h3>Members</h3><span class="sub" id="memSub">newest first</span></div>
<p style="margin:0 0 10px"><input id="memFilter" placeholder="Filter by email, username, member # or sponsor" style="width:100%"></p>
<p class="small muted">Sponsor = the token the account joined under (username, share code, or member #). Editing it re-points free referrals and future purchases. On-chain sponsorship is permanent once activated.</p>
<div class="tablewrap"><table class="adm-table" id="memTable"></table></div>
</div>
</div>
<div class="pane" id="pane-reports" hidden>
<div class="card">
<div class="card-head"><h3>Ad reports</h3><span class="sub">members flagging ads</span></div>
<div class="tablewrap"><table class="adm-table" id="repTable"></table></div>
</div>
<div class="card">
<div class="card-head"><h3>Pending burns</h3><span class="sub">on-chain credit burns waiting to be executed</span></div>
<div class="tablewrap"><table class="adm-table" id="burnTable"></table></div>
</div>
</div>
<div class="pane" id="pane-traffic" hidden>
<div class="card">
<div class="card-head"><h3>Where visitors come from</h3><span class="sub" id="trfSub"></span></div>
<div class="chips" id="trfRange" style="margin:0 0 10px"><button type="button" class="chip-t" data-days="7">7 days</button><button type="button" class="chip-t on" data-days="30">30 days</button><button type="button" class="chip-t" data-days="90">90 days</button><button type="button" class="chip-t" data-days="365">Year</button></div>
<p class="muted small" style="margin:0 0 10px">Page views are public-page loads by referring domain (crawlers skipped; our own pages and no referrer count as direct). Join-page views are invite-link opens. Signups, registered and $20+ buyers are accounts whose first-touch source was that domain; legacy arrivals show as legacy:brand:domain.</p>
<div class="tablewrap"><table class="adm-table" id="trfSources"></table></div>
</div>
<div class="grid c2">
<div class="card"><div class="card-head"><h3>Landing pages</h3><span class="sub">page views by page</span></div><div class="tablewrap"><table class="adm-table" id="trfPaths"></table></div></div>
<div class="card"><div class="card-head"><h3>Angles</h3><span class="sub">join-page hook copy</span></div><div class="tablewrap"><table class="adm-table" id="trfAngles"></table></div></div>
</div>
<div class="card"><div class="card-head"><h3>By day</h3><span class="sub">page views, signups</span></div><div class="tablewrap"><table class="adm-table" id="trfDaily"></table></div></div>
<div class="card" id="promoAdmin">
<div class="card-head"><h3>Partner promo codes</h3><span class="sub">free ad credits for members who redeem a partner's code</span></div>
<p class="muted small" style="margin:0 0 10px">Give a site owner a code. Their members redeem it on a join link (<code>instantadpay.com/join/martbost?promo=CODE</code>) or in the "Have a promo code?" box on the Overview. One use per account; uses and the last redemptions are listed below.</p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:8px;align-items:end">
<label class="small">Code<input id="pcCode" type="text" placeholder="TRAFFICWAVE" style="text-transform:uppercase"></label>
<label class="small">Credits<input id="pcCredits" type="number" min="1" placeholder="250"></label>
<label class="small">Partner<input id="pcPartner" type="text" placeholder="site or owner"></label>
<label class="small">Max uses (0 = unlimited)<input id="pcMax" type="number" min="0" value="0"></label>
<label class="small">Expires (optional)<input id="pcExpires" type="date"></label>
<button type="button" class="btn small" id="pcSave">Save code</button>
</div>
<p class="small" id="pcMsg" hidden style="margin:8px 0 0"></p>
<div class="tablewrap" style="margin-top:12px"><table class="adm-table" id="pcTable"></table></div>
<div class="tablewrap" style="margin-top:12px"><table class="adm-table" id="pcRecent"></table></div>
</div>
</div>
<div class="pane" id="pane-pnl" hidden>
<div class="card">
<div class="card-head"><h3>Profit and loss</h3><span class="sub">read from the chain index; periods are by block (about 43,200 Polygon blocks a day)</span></div>
<div class="chips" id="pnlPeriods"><button type="button" class="chip-t" data-days="7">7 days</button><button type="button" class="chip-t on" data-days="30">30 days</button><button type="button" class="chip-t" data-days="90">90 days</button><button type="button" class="chip-t" data-days="0">All time</button></div>
<div class="grid" id="pnlTiles" style="grid-template-columns:repeat(auto-fit,minmax(200px,1fr));margin-top:12px"></div>
</div>
<div class="grid c2">
<div class="card">
<div class="card-head"><h3>Where the money went</h3><span class="sub">per period</span></div>
<div class="tablewrap"><table class="adm-table" id="pnlSplit"></table></div>
</div>
<div class="card">
<div class="card-head"><h3>Wallets</h3><span class="sub">live balances</span></div>
<div class="tablewrap"><table class="adm-table" id="pnlWallets"></table></div>
<p class="small muted" style="margin:10px 0 0">Fixed monthly cost (USD) for the net line: <input type="number" id="pnlFixed" min="0" step="1" style="width:110px"> <button class="btn small sec" type="button" id="pnlFixedSave">Save</button></p>
</div>
</div>
<div class="card">
<div class="card-head"><h3>Automatic credit burner</h3><span class="sub">settles pending campaign spend on-chain from the engine wallet</span></div>
<p class="small" id="burnerLine">…</p>
<p><button class="btn small sec" type="button" id="burnerRun">Run now</button></p>
</div>
</div>
<div class="pane" id="pane-settings" hidden>
<div class="card">
<div class="card-head"><h3>Follow-up emails</h3><span class="sub" id="dripSub">sent to every new free member, hours after sign-up</span></div>
<p class="small muted">One card per email. Placeholders fill in per reader: click a chip to drop it where your cursor is.
<span class="ph-chips" id="phChips">
<button type="button" class="chip-t" data-ph="{{link}}" title="the reader's own invite link">{{link}}</button>
<button type="button" class="chip-t" data-ph="{{sponsor}}" title="their sponsor's name">{{sponsor}}</button>
<button type="button" class="chip-t" data-ph="{{site}}" title="https://instantadpay.com">{{site}}</button>
<button type="button" class="chip-t" data-ph="{{paid:words if they bought|words if they have not}}" title="two versions: bought / not yet">{{paid:…|…}}</button>
<button type="button" class="chip-t" data-ph="{{footer}}" title="unsubscribe line + disclaimer (keep it at the end)">{{footer}}</button>
</span></p>
<div id="dripSteps" class="drip-steps"></div>
<p id="dripErr" class="small" style="color:var(--bad)" hidden></p>
<p style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin:14px 0 0">
<button class="btn small" id="dripSave" type="button">Save sequence</button>
<button class="btn small sec" id="dripAdd" type="button">+ Add an email</button>
<button class="btn small sec" id="dripReset" type="button">Reset to defaults</button>
</p>
</div>
<div class="card">
<div class="card-head"><h3>Rates</h3><span class="sub">credit prices, rewards and limits</span></div>
<div id="ratesForm" class="rates-form"></div>
<p id="ratesErr" class="small" style="color:var(--bad)" hidden></p>
<p style="margin:14px 0 0"><button class="btn small" id="ratesSave" type="button">Save rates</button></p>
</div>
<div class="card">
<div class="card-head"><h3>Site settings</h3><span class="sub">public: everything here is served to the browser, never put a secret in it</span></div>
<div id="siteForm" class="kv-form"></div>
<p style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin:12px 0 0">
<input id="siteNewKey" placeholder="new setting name" style="max-width:220px">
<button class="btn small sec" id="siteAddKey" type="button">+ Add setting</button>
</p>
<p id="siteErr" class="small" style="color:var(--bad)" hidden></p>
<p style="margin:14px 0 0"><button class="btn small" id="siteSave" type="button">Save site settings</button></p>
</div>
</div>
</main>
</div>
</div>
<script src="/assets/common.js?v=20260912d"></script>
<script src="/assets/admin.js?v=20260912b"></script>
</body>
</html>