Spaces:
Running
Running
Download index.html from Signalloom-ai/LoomLensAI: direct link, hf CLI and curl.
- Browser
- Download file 53 kB
-
https://huggingface.co/spaces/Signalloom-ai/LoomLensAI/resolve/main/index.html
- Command line
-
hf download hf://spaces/Signalloom-ai/LoomLensAI/index.html
-
curl -L -o index.html https://huggingface.co/spaces/Signalloom-ai/LoomLensAI/resolve/main/index.html
53 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>LoomLens AI™ — API Token Budget Calculator · SignalloomAI™</title>
<style>
:root {
--bg: #0a0e1a;
--surface: #111827;
--surface2: #1a2235;
--border: #1e2d4a;
--accent: #8b5cf6;
--accent-bright: #a78bfa;
--text: #e8edf5;
--text-dim: #8899bb;
--text-muted: #4a6a9a;
--green: #10b981;
--yellow: #f59e0b;
--red: #f87171;
--orange: #fb923c;
--radius: 10px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--text);
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
min-height: 100vh;
padding: 40px 20px;
}
.header {
text-align: center;
margin-bottom: 32px;
max-width: 680px;
margin-left: auto;
margin-right: auto;
}
.eyebrow {
font-size: 11px;
letter-spacing: 3px;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 10px;
font-weight: 600;
}
h1 { font-size: 32px; font-weight: 700; letter-spacing: -0.5px; margin-bottom: 14px; }
.header p { font-size: 15px; color: var(--text-dim); line-height: 1.7; max-width: 540px; margin: 0 auto 16px; }
/* Pricing banner */
.pricing-bar {
display: inline-flex;
align-items: center;
gap: 16px;
padding: 8px 20px;
border-radius: 99px;
background: rgba(139,92,246,0.1);
border: 1px solid rgba(139,92,246,0.25);
margin-bottom: 4px;
flex-wrap: wrap;
justify-content: center;
}
.pricing-item { font-size: 12px; font-weight: 600; color: var(--text-dim); }
.pricing-item strong { color: var(--accent-bright); }
.pricing-divider { width: 1px; height: 14px; background: rgba(139,92,246,0.3); }
.badge-free {
background: rgba(16,185,129,0.15);
border: 1px solid rgba(16,185,129,0.3);
color: var(--green);
padding: 2px 10px;
border-radius: 99px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.5px;
}
.badge-pro {
background: rgba(245,158,11,0.15);
border: 1px solid rgba(245,158,11,0.3);
color: var(--yellow);
padding: 2px 10px;
border-radius: 99px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.5px;
}
.layout {
display: grid;
grid-template-columns: 1fr 380px;
gap: 24px;
max-width: 1060px;
margin: 0 auto;
align-items: start;
}
@media (max-width: 820px) { .layout { grid-template-columns: 1fr; } }
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 22px;
}
.card-title {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1.5px;
color: var(--text-muted);
margin-bottom: 16px;
padding-bottom: 10px;
border-bottom: 1px solid var(--border);
}
/* Quota meter */
.quota-bar-wrap {
margin-bottom: 16px;
padding: 10px 14px;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 8px;
}
.quota-label {
display: flex;
justify-content: space-between;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-muted);
margin-bottom: 6px;
}
.quota-count { color: var(--accent-bright); }
.quota-track {
height: 6px;
background: var(--surface);
border-radius: 99px;
overflow: hidden;
}
.quota-fill {
height: 100%;
background: linear-gradient(90deg, var(--accent), var(--accent-bright));
border-radius: 99px;
transition: width 0.4s ease;
}
.quota-fill.warn { background: linear-gradient(90deg, var(--yellow), var(--orange)); }
.quota-fill.done { background: var(--green); }
.quota-remaining { font-size: 11px; color: var(--text-muted); margin-top: 5px; }
.api-key-bar { display: flex; align-items: center; justify-content: space-between; gap: 8px; background: rgba(139,92,246,0.08); border: 1px solid rgba(139,92,246,0.2); border-radius: 8px; padding: 8px 12px; margin-bottom: 10px; }
.credit-row { display: flex; align-items: center; gap: 8px; }
.credit-label { font-size: 12px; color: var(--text-muted); }
.credit-balance { font-size: 13px; font-weight: 700; color: var(--accent-bright); }
.api-key-link { font-size: 11px; color: var(--text-muted); }
.api-key-link a { color: var(--accent-bright); text-decoration: none; }
.quota-remaining strong { color: var(--text-dim); }
/* Prompt area */
.prompt-area {
width: 100%;
min-height: 200px;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text);
font-size: 13px;
font-family: 'Cascadia Code','Fira Code','SF Mono',Consolas,monospace;
padding: 12px 14px;
resize: vertical;
outline: none;
line-height: 1.6;
transition: border-color 0.2s;
}
.prompt-area:focus { border-color: var(--accent); }
.prompt-area::placeholder { color: var(--text-muted); }
.prompt-area.limit-reached { opacity: 0.5; cursor: not-allowed; }
.token-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 8px;
font-size: 11px;
color: var(--text-muted);
flex-wrap: wrap;
gap: 6px;
}
.token-count { font-weight: 700; color: var(--accent-bright); }
.token-count.high { color: var(--yellow); }
.token-count.very-high { color: var(--red); }
/* Field rows */
.field-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
margin-top: 14px;
}
.field { display: flex; flex-direction: column; gap: 6px; }
.field label {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-muted);
}
.field select { width: 100%; }
.field select {
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text);
font-size: 13px;
padding: 8px 10px;
font-family: inherit;
cursor: pointer;
outline: none;
}
.field select option { background: var(--surface2); }
.range-wrap { position: relative; }
.range-val {
position: absolute;
right: 0; top: -18px;
font-size: 12px;
font-weight: 700;
color: var(--accent-bright);
}
input[type=range] { width: 100%; accent-color: var(--accent); cursor: pointer; margin-top: 4px; }
/* Model picker header */
.model-picker-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 10px;
margin: 14px 0 8px;
}
.mph-left { display: flex; align-items: center; gap: 8px; }
.mph-title { font-size: 13px; font-weight: 700; color: var(--text); }
.mph-count { font-size: 11px; color: var(--text-muted); background: rgba(255,255,255,0.05); padding: 2px 8px; border-radius: 99px; }
.mph-btn {
font-size: 11px; font-weight: 600; color: var(--accent-bright);
background: rgba(139,92,246,0.1); border: 1px solid rgba(139,92,246,0.25);
border-radius: 6px; padding: 4px 10px; cursor: pointer;
transition: background 0.15s;
}
.mph-btn:hover { background: rgba(139,92,246,0.2); }
/* Tier rows */
.tier-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 4px;
margin: 10px 0 6px;
}
.tier-hdr-left { display: flex; align-items: center; gap: 8px; }
.tier-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.tier-dot.t1 { background: #8b5cf6; box-shadow: 0 0 6px rgba(139,92,246,0.5); }
.tier-dot.t2 { background: #f59e0b; box-shadow: 0 0 6px rgba(245,158,11,0.4); }
.tier-dot.t3 { background: #10b981; box-shadow: 0 0 6px rgba(16,185,129,0.4); }
.tier-name { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; color: var(--text-muted); }
.tier-count { font-size: 10px; color: var(--text-muted); opacity: 0.7; }
.tier-actions { display: flex; gap: 4px; }
.tier-sel-btn {
font-size: 10px; font-weight: 600; color: var(--text-muted);
background: transparent; border: 1px solid var(--border);
border-radius: 4px; padding: 2px 7px; cursor: pointer;
transition: all 0.15s;
}
.tier-sel-btn:hover { color: var(--text); border-color: var(--accent); background: rgba(139,92,246,0.08); }
/* Collapsible tier toggle (tiers 2 & 3) */
.tier-toggle {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 10px;
border-radius: 8px;
border: 1px dashed var(--border);
background: transparent;
cursor: pointer;
user-select: none;
transition: all 0.15s;
margin-top: 8px;
}
.tier-toggle:hover { border-color: var(--accent); background: rgba(139,92,246,0.05); }
.tier-toggle .tier-name { color: var(--text-secondary); font-size: 12px; text-transform: none; letter-spacing: 0; font-weight: 600; }
.tier-arrow { font-size: 9px; color: var(--text-muted); transition: transform 0.2s; }
.tier-toggle.open .tier-arrow { transform: rotate(90deg); }
.tier-section { display: none; grid-template-columns: 1fr 1fr; gap: 6px; padding-top: 8px; position: relative; }
.tier-section.open { display: grid; }
.tier-section.locked { opacity: 0.45; pointer-events: none; }
.pro-badge {
font-size: 9px; font-weight: 800; letter-spacing: 1px;
background: rgba(245,158,11,0.18); color: #f59e0b;
border: 1px solid rgba(245,158,11,0.35);
padding: 1px 6px; border-radius: 4px;
}
.free-badge {
font-size: 9px; font-weight: 800; letter-spacing: 1px;
background: rgba(16,185,129,0.15); color: #10b981;
border: 1px solid rgba(16,185,129,0.3);
padding: 1px 6px; border-radius: 4px;
}
.tier-lock { font-size: 11px; }
.tier-toggle.locked { opacity: 0.5; cursor: default; }
.tier-toggle.locked .tier-actions { pointer-events: none; }
.tier-gate {
display: none;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 18px 20px;
background: rgba(245,158,11,0.07);
border: 1px solid rgba(245,158,11,0.2);
border-radius: 12px;
margin: 12px 0;
text-align: center;
}
.tier-gate-title { font-size: 13px; font-weight: 700; color: var(--text); }
.tier-gate-sub { font-size: 12px; color: var(--text-muted); line-height: 1.5; }
.tier-gate-btn {
font-size: 13px; font-weight: 700; color: #0c0a14;
background: linear-gradient(135deg, #f59e0b, #d97706);
border: none; border-radius: 8px; padding: 9px 22px; cursor: pointer;
transition: opacity 0.15s;
}
.tier-gate-btn:hover { opacity: 0.85; }
.tier-gate-note { font-size: 11px; color: var(--text-muted); }
.models-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
}
.model-chip {
display: flex;
align-items: center;
gap: 7px;
padding: 7px 10px;
border-radius: 8px;
border: 1px solid var(--border);
background: var(--surface2);
cursor: pointer;
font-size: 12px;
color: var(--text-dim);
transition: all 0.15s;
user-select: none;
}
.model-chip:hover { border-color: var(--accent); color: var(--text); }
.model-chip.selected { border-color: var(--accent); background: rgba(139,92,246,0.12); color: var(--accent-bright); }
.model-chip input { display: none; }
.chip-dot {
width: 8px; height: 8px;
border-radius: 50%;
border: 1.5px solid currentColor;
flex-shrink: 0;
}
.model-chip.selected .chip-dot { background: var(--accent); border-color: var(--accent); }
.provider-tag {
margin-left: auto;
font-size: 9px;
font-weight: 700;
letter-spacing: 0.5px;
text-transform: uppercase;
padding: 2px 5px;
border-radius: 4px;
opacity: 0.75;
}
.provider-tag.openai { background: rgba(16,163,74,0.2); color: #10b981; }
.provider-tag.anthropic { background: rgba(139,92,246,0.2); color: #a78bfa; }
.provider-tag.google { background: rgba(251,191,36,0.2); color: #f59e0b; }
.provider-tag.deepseek { background: rgba(239,68,68,0.2); color: #f87171; }
.provider-tag.xai { background: rgba(255,255,255,0.08); color: #e8edf5; }
.provider-tag.minimax { background: rgba(59,130,246,0.2); color: #60a5fa; }
.provider-tag.meta { background: rgba(14,116,144,0.2); color: #22d3ee; }
/* Pro upsell bar */
.pro-bar {
margin-top: 14px;
padding: 10px 14px;
background: rgba(245,158,11,0.06);
border: 1px solid rgba(245,158,11,0.2);
border-radius: 8px;
font-size: 12px;
color: var(--text-muted);
display: flex;
align-items: center;
gap: 10px;
}
.pro-bar-icon { font-size: 20px; flex-shrink: 0; }
.pro-bar strong { color: var(--yellow); }
.pro-bar a { color: var(--yellow); font-weight: 700; text-decoration: none; }
.pro-bar a:hover { text-decoration: underline; }
/* CTA */
.btn-estimate {
width: 100%;
padding: 13px;
border-radius: 8px;
border: none;
background: var(--accent);
color: #fff;
font-size: 14px;
font-weight: 700;
cursor: pointer;
margin-top: 14px;
transition: background 0.2s, transform 0.1s;
font-family: inherit;
}
.btn-estimate:hover:not(:disabled) { background: var(--accent-bright); }
.btn-estimate:active:not(:disabled) { transform: scale(0.98); }
.btn-estimate:disabled { opacity: 0.4; cursor: not-allowed; }
.btn-estimate.loading { position: relative; color: transparent; pointer-events: none; }
.btn-estimate.loading::after {
content: '';
position: absolute;
width: 18px; height: 18px;
border: 2px solid rgba(255,255,255,0.3);
border-top-color: #fff;
border-radius: 50%;
top: 50%; left: 50%;
transform: translate(-50%,-50%);
animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: translate(-50%,-50%) rotate(360deg); } }
.error-msg {
background: rgba(248,113,113,0.08);
border: 1px solid rgba(248,113,113,0.25);
border-radius: 8px;
padding: 10px 12px;
font-size: 13px;
color: var(--red);
margin-top: 8px;
display: none;
}
/* Results */
.results-panel { display: flex; flex-direction: column; gap: 10px; }
.result-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 14px 16px;
}
.result-card.cheapest { border-color: var(--green); box-shadow: 0 0 16px rgba(16,185,129,0.07); }
.result-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
.result-model { font-size: 13px; font-weight: 700; color: var(--text-dim); }
.result-card.cheapest .result-model { color: var(--green); }
.cheapest-badge {
font-size: 9px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
padding: 3px 9px; border-radius: 99px;
background: rgba(16,185,129,0.15); border: 1px solid rgba(16,185,129,0.3); color: var(--green);
}
.cost-display { display: flex; align-items: baseline; gap: 5px; margin-bottom: 8px; }
.cost-amount { font-size: 24px; font-weight: 700; letter-spacing: -1px; }
.cost-unit { font-size: 12px; color: var(--text-muted); }
.cost-bands { display: flex; gap: 5px; font-size: 11px; margin-bottom: 7px; flex-wrap: wrap; }
.cost-band { padding: 2px 7px; border-radius: 6px; background: var(--surface2); }
.cost-band.low { color: var(--green); } .cost-band.mid { color: var(--yellow); } .cost-band.high { color: var(--red); }
.token-row { font-size: 11px; color: var(--text-muted); display: flex; gap: 10px; flex-wrap: wrap; }
.loom-token-row { font-size: 11px; color: var(--accent); margin-top: 4px; }
.loom-token-row b { color: var(--accent-bright); }
.token-row b { color: var(--text-dim); }
.savings-banner {
background: rgba(16,185,129,0.07);
border: 1px solid rgba(16,185,129,0.2);
border-radius: var(--radius);
padding: 11px 15px;
display: flex;
align-items: center;
gap: 10px;
font-size: 13px;
color: var(--text-dim);
}
.savings-banner strong { color: var(--green); }
.savings-icon { font-size: 20px; line-height: 1; }
.postmortem {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 13px 15px;
font-size: 12px;
color: var(--text-muted);
line-height: 1.8;
}
.postmortem h4 { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 1.5px; color: var(--text-dim); margin-bottom: 7px; }
.postmortem .fact { display: flex; gap: 8px; }
.postmortem .fact::before { content: '→'; color: var(--accent); min-width: 14px; }
.postmortem .fact b { color: var(--text-dim); }
.empty-state { text-align: center; padding: 40px 20px; color: var(--text-muted); font-size: 14px; line-height: 1.7; }
.empty-icon { font-size: 36px; margin-bottom: 12px; opacity: 0.3; display: block; }
.attr { text-align: center; margin-top: 28px; font-size: 12px; color: var(--text-muted); }
.attr a { color: var(--accent); text-decoration: none; }
.attr a:hover { text-decoration: underline; }
.integrity-footer { text-align: center; padding: 20px 20px 28px; font-size: 11px; color: var(--text-muted); border-top: 1px solid var(--border); margin-top: 32px; }
.integrity-footer a { color: var(--text-muted); text-decoration: none; margin: 0 8px; }
.integrity-footer a:hover { color: var(--accent); text-decoration: underline; }
.integrity-footer .sep { margin: 0 4px; opacity: 0.4; }
.integrity-badge { display: inline-flex; align-items: center; gap: 5px; background: rgba(16,185,129,0.1); border: 1px solid rgba(16,185,129,0.25); color: #10b981; padding: 3px 10px; border-radius: 99px; font-size: 10px; font-weight: 600; margin-bottom: 10px; letter-spacing: 0.3px; }
</style>
</head>
<body>
<div class="header">
<div class="eyebrow">By SignalloomAI™ — Free Tool</div>
<h1>LoomLens AI™ — API Token Budget Calculator</h1>
<p>Find the best model to fit your prompt.™</p>
<div class="pricing-bar">
<span class="badge-free">FREE</span>
<span class="pricing-item">5 estimates / day</span>
<span class="pricing-divider"></span>
<span class="badge-pro">PRO</span>
<span class="pricing-item"><strong>$5/month</strong> — unlimited estimates</span>
</div>
</div>
<div class="layout">
<!-- LEFT: Prompt + params -->
<div>
<div class="card">
<div class="card-title">Your Prompt</div>
<!-- Quota meter -->
<div class="quota-bar-wrap" id="quota_box">
<div class="quota-label">
<span>Daily Estimates</span>
<span class="quota-count" id="quota_count">5 / 5 remaining</span>
</div>
<div class="quota-track">
<div class="quota-fill done" id="quota_fill" style="width:100%"></div>
</div>
<div class="quota-remaining" id="quota_remaining">You have <strong>5 free estimates today</strong></div>
</div>
<!-- API key + credit balance -->
<div class="api-key-bar" id="api_key_bar" style="display:none">
<div class="credit-row">
<span class="credit-label">💎 LoomTokens</span>
<span class="credit-balance" id="credit_balance">—</span>
</div>
<div class="api-key-link" id="api_key_link">
<a href="https://signalloomai.com/loomlensai-build.html" target="_blank">Connect API key</a> for pay-per-call estimates
</div>
<div class="api-key-input-row" id="api_key_input_row" style="display:none">
<input type="password" id="api_key_input" placeholder="Paste your API key (slo_...)" style="flex:1;background:var(--bg-2);border:1px solid var(--border);border-radius:6px;padding:4px 8px;color:var(--text);font-size:12px;" />
<button onclick="saveApiKey()" style="background:var(--accent);border:none;border-radius:6px;padding:4px 10px;color:white;font-size:12px;cursor:pointer;">Save</button>
<button onclick="clearApiKey()" style="background:transparent;border:1px solid var(--border);border-radius:6px;padding:4px 8px;color:var(--text-muted);font-size:11px;cursor:pointer;">Clear</button>
</div>
</div>
<textarea id="prompt_input" class="prompt-area"
placeholder="Paste your prompt here... e.g. You are an expert medical coder. Review the following clinical notes and assign the appropriate ICD-10 codes..." spellcheck="false"></textarea>
<div class="token-bar">
<span id="char_count">0 chars</span>
<span id="token_estimate" class="token-count">~0 tokens</span>
</div>
<div class="field-row">
<div class="field">
<label>Complexity</label>
<select id="complexity">
<option value="simple">Simple — Q&A, extraction</option>
<option value="standard" selected>Standard — drafting, chat, summarization</option>
<option value="reasoning">Reasoning — analysis, chain-of-thought</option>
<option value="coding">Coding — generation, review, refactor</option>
</select>
</div>
<div class="field">
<label>Agent Turns</label>
<div class="range-wrap">
<span class="range-val" id="turns_val">1</span>
<input type="range" id="num_turns" min="1" max="50" value="1" />
</div>
<span style="font-size:11px;color:var(--text-muted)" id="turns_hint">1 prompt round trip</span>
</div>
</div>
<!-- Model Picker Header -->
<div class="model-picker-header">
<div class="mph-left">
<span class="mph-title">Models</span>
<span class="mph-count" id="model_count">9 selected</span>
</div>
<button class="mph-btn" id="btn_select_all" onclick="selectAllModels()">Select all</button>
</div>
<!-- Tier 1 — Major Models -->
<div class="tier-header" id="tier1_hdr">
<div class="tier-hdr-left">
<span class="tier-dot t1"></span>
<span class="tier-name">Major Models</span>
<span class="tier-count" id="tier1_count">9 / 9</span>
<span class="free-badge">FREE</span>
</div>
<div class="tier-actions">
<button class="tier-sel-btn" onclick="toggleAllTier('tier1', true)">All</button>
<button class="tier-sel-btn" onclick="toggleAllTier('tier1', false)">None</button>
</div>
</div>
<div class="models-grid" id="tier1_grid">
<label class="model-chip selected" id="chip-openai/gpt-5.3-codex">
<input type="checkbox" value="openai/gpt-5.3-codex" checked />
<span class="chip-dot"></span>GPT-5.3 Codex<span class="provider-tag openai">OpenAI</span>
</label>
<label class="model-chip selected" id="chip-openai/gpt-4o">
<input type="checkbox" value="openai/gpt-4o" checked />
<span class="chip-dot"></span>GPT-4o<span class="provider-tag openai">OpenAI</span>
</label>
<label class="model-chip selected" id="chip-openai/gpt-4o-mini">
<input type="checkbox" value="openai/gpt-4o-mini" checked />
<span class="chip-dot"></span>GPT-4o Mini<span class="provider-tag openai">OpenAI</span>
</label>
<label class="model-chip selected" id="chip-anthropic/claude-opus-4-6">
<input type="checkbox" value="anthropic/claude-opus-4-6" checked />
<span class="chip-dot"></span>Claude Opus 4<span class="provider-tag anthropic">Anthropic</span>
</label>
<label class="model-chip selected" id="chip-anthropic/claude-sonnet-4-20250514">
<input type="checkbox" value="anthropic/claude-sonnet-4-20250514" checked />
<span class="chip-dot"></span>Claude Sonnet 4<span class="provider-tag anthropic">Anthropic</span>
</label>
<label class="model-chip selected" id="chip-google/gemini-2.5-pro">
<input type="checkbox" value="google/gemini-2.5-pro" checked />
<span class="chip-dot"></span>Gemini 2.5 Pro<span class="provider-tag google">Google</span>
</label>
<label class="model-chip selected" id="chip-google/gemini-2.5-flash">
<input type="checkbox" value="google/gemini-2.5-flash" checked />
<span class="chip-dot"></span>Gemini 2.5 Flash<span class="provider-tag google">Google</span>
</label>
<label class="model-chip selected" id="chip-deepseek/deepseek-chat-v3">
<input type="checkbox" value="deepseek/deepseek-chat-v3" checked />
<span class="chip-dot"></span>DeepSeek Chat v3<span class="provider-tag deepseek">DeepSeek</span>
</label>
<label class="model-chip selected" id="chip-xai/grok-3">
<input type="checkbox" value="xai/grok-3" checked />
<span class="chip-dot"></span>xAI Grok-3<span class="provider-tag xai">xAI</span>
</label>
</div>
<!-- Paywall gate: shown to free users -->
<div class="tier-gate" id="tier_gate">
<div class="tier-gate-title">🔒 Pro / API Access Required for Tiers 2 & 3</div>
<div class="tier-gate-sub">Next Tier and Emerging models are visible here — upgrade to unlock them, or connect your API key for pay-per-call access to all 24 models</div>
<button class="tier-gate-btn" onclick="unlockPro()">Upgrade to Pro — $5/month</button>
<div class="tier-gate-note">Free: 5 estimates/day on Tier 1 · Pro unlimited · API: pay-per-call, all models</div>
</div>
<!-- Tier 2 — Next Tier -->
<div class="tier-toggle" id="tier2_toggle" onclick="toggleTier('tier2')">
<div class="tier-hdr-left">
<span class="tier-arrow" id="tier2_arrow">▶</span>
<span class="tier-dot t2"></span>
<span class="tier-name">Next Tier</span>
<span class="tier-count" id="tier2_count">0 / 10</span>
<span class="pro-badge">PRO</span>
<span class="tier-lock">🔒</span>
</div>
<div class="tier-actions" onclick="event.stopPropagation()">
<button class="tier-sel-btn" onclick="toggleAllTier('tier2', true)">All</button>
<button class="tier-sel-btn" onclick="toggleAllTier('tier2', false)">None</button>
</div>
</div>
<div class="tier-section" id="tier2_section">
<label class="model-chip" id="chip-openai/gpt-5.4">
<input type="checkbox" value="openai/gpt-5.4" />
<span class="chip-dot"></span>GPT-5.4<span class="provider-tag openai">OpenAI</span>
</label>
<label class="model-chip" id="chip-openai/gpt-5.4-mini">
<input type="checkbox" value="openai/gpt-5.4-mini" />
<span class="chip-dot"></span>GPT-5.4 Mini<span class="provider-tag openai">OpenAI</span>
</label>
<label class="model-chip" id="chip-openai/gpt-5.4-nano">
<input type="checkbox" value="openai/gpt-5.4-nano" />
<span class="chip-dot"></span>GPT-5.4 Nano<span class="provider-tag openai">OpenAI</span>
</label>
<label class="model-chip" id="chip-anthropic/claude-haiku-4-20250514">
<input type="checkbox" value="anthropic/claude-haiku-4-20250514" />
<span class="chip-dot"></span>Claude Haiku 4<span class="provider-tag anthropic">Anthropic</span>
</label>
<label class="model-chip" id="chip-google/gemini-2.0-flash">
<input type="checkbox" value="google/gemini-2.0-flash" />
<span class="chip-dot"></span>Gemini 2.0 Flash<span class="provider-tag google">Google</span>
</label>
<label class="model-chip" id="chip-deepseek/deepseek-reasoner">
<input type="checkbox" value="deepseek/deepseek-reasoner" />
<span class="chip-dot"></span>DeepSeek Reasoner<span class="provider-tag deepseek">DeepSeek</span>
</label>
<label class="model-chip" id="chip-xai/grok-3-mini">
<input type="checkbox" value="xai/grok-3-mini" />
<span class="chip-dot"></span>xAI Grok-3 Mini<span class="provider-tag xai">xAI</span>
</label>
<label class="model-chip" id="chip-minimax/MiniMax-M2.7">
<input type="checkbox" value="minimax/MiniMax-M2.7" />
<span class="chip-dot"></span>MiniMax M2.7<span class="provider-tag minimax">MiniMax</span>
</label>
<label class="model-chip" id="chip-minimax/M2.1-Ultra">
<input type="checkbox" value="minimax/M2.1-Ultra" />
<span class="chip-dot"></span>MiniMax M2.1 Ultra<span class="provider-tag minimax">MiniMax</span>
</label>
<label class="model-chip" id="chip-local/llama-3.3-70b">
<input type="checkbox" value="local/llama-3.3-70b" />
<span class="chip-dot"></span>Llama 3.3 70B<span class="provider-tag meta">Meta</span>
</label>
</div>
<!-- Tier 3 — Emerging / Reasoning -->
<div class="tier-toggle" id="tier3_toggle" onclick="toggleTier('tier3')">
<div class="tier-hdr-left">
<span class="tier-arrow" id="tier3_arrow">▶</span>
<span class="tier-dot t3"></span>
<span class="tier-name">Emerging & Reasoning</span>
<span class="tier-count" id="tier3_count">0 / 5</span>
<span class="pro-badge">PRO</span>
<span class="tier-lock">🔒</span>
</div>
<div class="tier-actions" onclick="event.stopPropagation()">
<button class="tier-sel-btn" onclick="toggleAllTier('tier3', true)">All</button>
<button class="tier-sel-btn" onclick="toggleAllTier('tier3', false)">None</button>
</div>
</div>
<div class="tier-section" id="tier3_section">
<label class="model-chip" id="chip-openai/o3">
<input type="checkbox" value="openai/o3" />
<span class="chip-dot"></span>OpenAI o3<span class="provider-tag openai">OpenAI</span>
</label>
<label class="model-chip" id="chip-openai/o4-mini">
<input type="checkbox" value="openai/o4-mini" />
<span class="chip-dot"></span>OpenAI o4-mini<span class="provider-tag openai">OpenAI</span>
</label>
<label class="model-chip" id="chip-anthropic/claude-opus-4">
<input type="checkbox" value="anthropic/claude-opus-4" />
<span class="chip-dot"></span>Claude Opus 4 (full)<span class="provider-tag anthropic">Anthropic</span>
</label>
<label class="model-chip" id="chip-google/gemini-2.0-pro">
<input type="checkbox" value="google/gemini-2.0-pro" />
<span class="chip-dot"></span>Gemini 2.0 Pro<span class="provider-tag google">Google</span>
</label>
<label class="model-chip" id="chip-deepseek/deepseek-v3-0324">
<input type="checkbox" value="deepseek/deepseek-v3-0324" />
<span class="chip-dot"></span>DeepSeek V3 (0324)<span class="provider-tag deepseek">DeepSeek</span>
</label>
</div>
<!-- Pro upsell (shown when quota exhausted) -->
<div class="pro-bar" id="pro_bar" style="display:none">
<div class="pro-bar-icon">💎</div>
<div>Out of free estimates today. <strong>Get unlimited for $5/month</strong> — <a href="https://signalloom-landing.pages.dev/loomlensai-pro.html" target="_blank">Upgrade Now</a></div>
</div>
<button class="btn-estimate" id="btn_estimate" onclick="runEstimate()">Estimate Cost</button>
<div class="error-msg" id="error_msg"></div>
<div class="attr" style="margin-top:14px">
Powered by <a href="https://signalloomai.com" target="_blank">Signal Loom AI</a> · <a href="https://signalloomai.com/prompt-integrity.html" target="_blank">Prompt Integrity</a>
</div>
</div>
</div>
<!-- RIGHT: Results -->
<div class="results-panel" id="results_panel">
<div class="empty-state">
<span class="empty-icon">⚡</span>
<div>Paste your prompt and click <strong>Estimate Cost</strong><br/>to see cost comparison across all models</div>
</div>
</div>
</div>
<script>
const API_URL = 'https://api.signalloomai.com/v1/estimate';
const API_KEY_STORAGE = 'sl_api_key';
const FREE_DAILY = 5;
const STORAGE_KEY = 'sl_token_est_quota';
const CHARS_PER_TOKEN = 3.8;
// ── Quota management ─────────────────────────────────
function getQuota() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return { count: 0, date: todayKey() };
const q = JSON.parse(raw);
if (q.date !== todayKey()) return { count: 0, date: todayKey() }; // reset daily
return q;
} catch { return { count: 0, date: todayKey() }; }
}
function saveQuota(q) {
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(q)); } catch {}
}
function todayKey() {
return new Date().toISOString().slice(0, 10); // YYYY-MM-DD
}
function useQuota() {
const q = getQuota();
if (q.count >= FREE_DAILY) return false;
q.count++;
saveQuota(q);
return true;
}
function remaining() {
return Math.max(0, FREE_DAILY - getQuota().count);
}
function renderQuotaUI() {
const rem = remaining();
const used = FREE_DAILY - rem;
const pct = (rem / FREE_DAILY) * 100;
const fill = document.getElementById('quota_fill');
const count = document.getElementById('quota_count');
const remEl = document.getElementById('quota_remaining');
const proBar = document.getElementById('pro_bar');
const btn = document.getElementById('btn_estimate');
const textarea = document.getElementById('prompt_input');
count.textContent = `${rem} / ${FREE_DAILY} remaining`;
fill.style.width = pct + '%';
fill.className = 'quota-fill' + (rem === 0 ? ' done' : rem <= 1 ? ' warn' : ' done');
remEl.innerHTML = rem > 0
? `You have <strong>${rem} free estimate${rem === 1 ? '' : 's'} today</strong>`
: `<strong>No free estimates remaining today.</strong> Upgrade to Pro for unlimited.`;
if (rem === 0) {
proBar.style.display = 'flex';
btn.disabled = true;
btn.textContent = 'Daily limit reached — Upgrade to Pro';
textarea.classList.add('limit-reached');
} else {
proBar.style.display = 'none';
btn.disabled = false;
btn.textContent = `Estimate Cost (${rem} left today)`;
textarea.classList.remove('limit-reached');
}
}
// ── Prompt token counter ─────────────────────────────
const promptInput = document.getElementById('prompt_input');
const charCount = document.getElementById('char_count');
const tokenEst = document.getElementById('token_estimate');
promptInput.addEventListener('input', () => {
const chars = promptInput.value.length;
const tokens = Math.ceil(chars / CHARS_PER_TOKEN);
charCount.textContent = chars.toLocaleString() + ' chars';
tokenEst.textContent = '~' + tokens.toLocaleString() + ' tokens';
tokenEst.className = 'token-count' + (tokens > 80000 ? ' very-high' : tokens > 30000 ? ' high' : '');
});
// ── Turns slider ───────────────────────────────────
document.getElementById('num_turns').addEventListener('input', function() {
document.getElementById('turns_val').textContent = this.value;
document.getElementById('turns_hint').textContent =
this.value == 1 ? '1 prompt round trip' : this.value + ' prompt round trips';
});
// ── Model chip toggle ────────────────────────────────
document.querySelectorAll('.model-chip').forEach(chip => {
chip.addEventListener('click', () => {
chip.classList.toggle('selected');
chip.querySelector('input').checked = !chip.querySelector('input').checked;
});
});
// ── Tier toggle ──────────────────────────────────────
function toggleTier(tier) {
const tog = document.getElementById(tier + '_toggle');
const sec = document.getElementById(tier + '_section');
const isOpen = tog.classList.toggle('open');
sec.classList.toggle('open', isOpen);
}
// ── Select / deselect all in a tier ──────────────────
function toggleAllTier(tier, select) {
const gridId = tier === 'tier1' ? tier + '_grid' : tier + '_section';
const grid = document.getElementById(gridId);
if (!grid) return;
grid.querySelectorAll('input').forEach(inp => { inp.checked = select; syncChip(inp); });
updateTierCount(tier);
updateModelCount();
}
// ── Global select all / deselect all ─────────────────
function selectAllModels() {
const btn = document.getElementById('btn_select_all');
const isSelectAll = btn.textContent === 'Select all';
document.querySelectorAll('.models-grid input, .tier-section input').forEach(inp => {
inp.checked = isSelectAll;
syncChip(inp);
});
['tier1','tier2','tier3'].forEach(t => updateTierCount(t));
updateModelCount();
btn.textContent = isSelectAll ? 'Deselect all' : 'Select all';
}
// ── Sync chip visual state with checkbox ─────────────
function syncChip(inp) {
const chip = inp.closest('.model-chip');
if (!chip) return;
chip.classList.toggle('selected', inp.checked);
}
// ── Update per-tier count badge ───────────────────────
function updateTierCount(tier) {
const gridId = tier === 'tier1' ? tier + '_grid' : tier + '_section';
const grid = document.getElementById(gridId);
if (!grid) return;
const all = grid.querySelectorAll('input');
const checked = grid.querySelectorAll('input:checked');
const countEl = document.getElementById(tier + '_count');
if (countEl) countEl.textContent = checked.length + ' / ' + all.length;
}
// ── Update total model count ──────────────────────────
function updateModelCount() {
const total = document.querySelectorAll('.models-grid input:checked, .tier-section input:checked').length;
const el = document.getElementById('model_count');
if (el) el.textContent = total + ' selected';
const btn = document.getElementById('btn_select_all');
if (btn) btn.textContent = total === 0 ? 'Select all' : 'Deselect all';
}
// ── Wire checkbox changes to chip visuals ────────────
function wireChipListeners() {
document.querySelectorAll('.model-chip input').forEach(inp => {
inp.addEventListener('change', () => {
syncChip(inp);
let tier;
const g = inp.closest('.models-grid');
const s = inp.closest('.tier-section');
if (g && g.id === 'tier1_grid') tier = 'tier1';
else if (s && s.id === 'tier2_section') tier = 'tier2';
else if (s && s.id === 'tier3_section') tier = 'tier3';
else tier = 'tier1';
updateTierCount(tier);
updateModelCount();
});
});
}
// ── Run estimate ────────────────────────────────────
// ── Credit bar ────────────────────────────────────────────
function initCreditBar() {
const bar = document.getElementById('api_key_bar');
const link = document.getElementById('api_key_link');
const inputRow = document.getElementById('api_key_input_row');
const apiKey = localStorage.getItem(API_KEY_STORAGE);
if (apiKey) {
bar.style.display = 'flex';
link.innerHTML = '<span style="font-size:11px;color:var(--green);">● API key connected</span> · <a href="#" onclick="toggleApiKeyInput();return false;" style="color:var(--text-muted);">[change]</a>';
inputRow.style.display = 'none';
updateCreditDisplay(apiKey);
} else {
bar.style.display = 'flex';
link.innerHTML = '<a href="#" onclick="toggleApiKeyInput();return false;" style="color:var(--accent-bright);">+ Connect API key</a> for pay-per-call estimates';
inputRow.style.display = 'none';
}
}
function toggleApiKeyInput() {
const row = document.getElementById('api_key_input_row');
row.style.display = row.style.display === 'none' ? 'flex' : 'none';
if (row.style.display === 'flex') {
document.getElementById('api_key_input').focus();
}
}
function saveApiKey() {
const key = document.getElementById('api_key_input').value.trim();
if (key) {
localStorage.setItem(API_KEY_STORAGE, key);
initCreditBar();
}
}
function clearApiKey() {
localStorage.removeItem(API_KEY_STORAGE);
document.getElementById('api_key_input').value = '';
initCreditBar();
}
let _lastBalanceBefore = 0;
async function updateCreditDisplay(apiKey) {
const el = document.getElementById('credit_balance');
if (!apiKey) { el.textContent = '—'; _lastBalanceBefore = 0; return; }
try {
const res = await fetch('https://api.signalloomai.com/v1/loomcredits/balance', {
headers: { 'Authorization': 'Bearer ' + apiKey }
});
if (res.ok) {
const data = await res.json();
const bal = data.balance || 0;
_lastBalanceBefore = bal;
el.textContent = bal.toLocaleString() + ' LoomTokens';
} else {
el.textContent = '—';
_lastBalanceBefore = 0;
}
} catch {
el.textContent = '—';
_lastBalanceBefore = 0;
}
}
function loomBalanceDelta() {
return _lastBalanceBefore;
}
function setApiKey(key) {
localStorage.setItem(API_KEY_STORAGE, key);
initCreditBar();
}
async function runEstimate() {
const btn = document.getElementById('btn_estimate');
const err = document.getElementById('error_msg');
err.style.display = 'none';
if (!useQuota()) {
err.textContent = 'Daily limit reached. Upgrade to Pro for unlimited estimates.';
err.style.display = 'block';
renderQuotaUI();
return;
}
const promptText = promptInput.value.trim();
if (!promptText) {
err.textContent = 'Please paste a prompt first.'; err.style.display = 'block';
renderQuotaUI(); // restore quota on error
return;
}
const baseTokens = Math.ceil(promptText.length / CHARS_PER_TOKEN);
const numTurns = parseInt(document.getElementById('num_turns').value);
const complexity = document.getElementById('complexity').value;
const targetModels = [...document.querySelectorAll('.model-chip input:checked')].map(i => i.value);
if (targetModels.length === 0) {
err.textContent = 'Please select at least one model.'; err.style.display = 'block';
renderQuotaUI(); return;
}
const payload = {
job_description: `Prompt (${baseTokens} tokens, ${numTurns} turn${numTurns > 1 ? 's' : ''})`,
num_agents: 1,
estimated_runtime_minutes: Math.max(1, Math.ceil(baseTokens / 1000 * numTurns * 0.3)),
complexity,
target_models: targetModels,
include_postmortem: true,
};
btn.disabled = true; btn.classList.add('loading');
try {
const res = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
const data = await res.json();
if (!res.ok) throw new Error(data.detail || data.error || 'Request failed');
// Fetch fresh balance BEFORE the call for delta calculation
const apiKey = localStorage.getItem(API_KEY_STORAGE);
if (apiKey) {
await updateCreditDisplay(apiKey); // sets _lastBalanceBefore
const balanceBefore = _lastBalanceBefore;
renderResults(data, baseTokens, numTurns);
// Update balance AFTER call, then show delta
await updateCreditDisplay(apiKey); // sets _lastBalanceAfter
const balanceAfter = _lastBalanceBefore; // now holds post-call balance
const delta = balanceAfter - balanceBefore;
document.querySelectorAll('.credit-change').forEach(el => {
if (delta < 0) {
el.textContent = (delta).toLocaleString() + ' after';
el.style.color = '#ef4444';
} else {
el.textContent = balanceAfter.toLocaleString() + ' balance';
el.style.color = 'var(--green)';
}
});
} else {
renderResults(data, baseTokens, numTurns);
}
} catch (e) {
err.textContent = 'Error: ' + e.message; err.style.display = 'block';
} finally {
btn.disabled = false; btn.classList.remove('loading');
renderQuotaUI();
}
}
// ── Render ─────────────────────────────────────────
function renderResults(data, baseTokens, numTurns) {
const panel = document.getElementById('results_panel');
const sorted = [...data.models].sort((a, b) => a.estimated_cost_mid - b.estimated_cost_mid);
const cheapest = sorted[0].model;
let html = '';
if (sorted.length > 1) {
const exp = sorted[sorted.length - 1];
html += `<div class="savings-banner">
<div class="savings-icon">💡</div>
<div>Switching from <strong>${fmtModel(exp.model)}</strong> to <strong>${fmtModel(cheapest)}</strong>
saves <strong>$${(exp.estimated_cost_mid - sorted[0].estimated_cost_mid).toFixed(4)}</strong> per run</div>
</div>`;
}
for (const m of sorted) {
html += `<div class="result-card${m.model === cheapest ? ' cheapest' : ''}">
<div class="result-header">
<div class="result-model">${fmtModel(m.model)}</div>
${m.model === cheapest ? '<span class="cheapest-badge">★ Cheapest</span>' : ''}
</div>
<div class="cost-display">
<span class="cost-amount">$${m.estimated_cost_mid.toFixed(4)}</span>
<span class="cost-unit">per run</span>
</div>
<div class="cost-bands">
<span class="cost-band low">Low $${m.estimated_cost_low.toFixed(4)}</span>
<span class="cost-band mid">Mid $${m.estimated_cost_mid.toFixed(4)}</span>
<span class="cost-band high">High $${m.estimated_cost_high.toFixed(4)}</span>
</div>
<div class="token-row">
<span>In: <b>${fmtNum(m.input_tokens_estimate)}</b></span>
<span>Out: <b>${fmtNum(m.output_tokens_estimate)}</b></span>
<span>Total: <b>${fmtNum(m.total_tokens_estimate)}</b></span>
</div>
<div class="loom-token-row">
<span>💎 <b>100</b> LoomTokens · $${(data.billing && data.billing.call_cost_usd ? data.billing.call_cost_usd : 0.10).toFixed(2)} · <span class="credit-change" id="credit_change_${m.model.replace('/', '_')}">—</span></span>
</div>
</div>`;
}
if (data.postmortem) {
const pm = data.postmortem;
html += `<div class="postmortem"><h4>Estimate Details</h4>
<div class="fact">Base prompt: <b>${baseTokens.toLocaleString()} tokens</b></div>
<div class="fact">Agent turns: <b>${pm.total_agent_turns.toLocaleString()}</b></div>
<div class="fact">Tokens/turn: <b>${pm.tokens_per_turn_mid_band.input.toLocaleString()} in</b> / <b>${pm.tokens_per_turn_mid_band.output.toLocaleString()} out</b></div>
<div class="fact">Complexity: <b>${pm.complexity_multiplier}</b></div>
<div class="fact">Models compared: <b>${pm.provider_count}</b></div>
</div>`;
}
panel.innerHTML = html;
}
function fmtModel(id) {
const map = {
'openai/gpt-5.3-codex': 'GPT-5.3 Codex', 'openai/gpt-4o': 'GPT-4o',
'openai/gpt-4o-mini': 'GPT-4o Mini', 'openai/gpt-5.4': 'GPT-5.4',
'openai/gpt-5.4-mini': 'GPT-5.4 Mini', 'openai/gpt-5.4-nano': 'GPT-5.4 Nano',
'anthropic/claude-opus-4-6': 'Claude Opus 4',
'anthropic/claude-sonnet-4-20250514': 'Claude Sonnet 4',
'anthropic/claude-haiku-4-20250514': 'Claude Haiku 4',
'google/gemini-2.5-pro': 'Gemini 2.5 Pro', 'google/gemini-2.5-flash': 'Gemini 2.5 Flash',
'google/gemini-2.0-flash': 'Gemini 2.0 Flash',
'deepseek/deepseek-chat-v3': 'DeepSeek Chat v3', 'deepseek/deepseek-reasoner': 'DeepSeek Reasoner',
'xai/grok-3': 'xAI Grok-3', 'xai/grok-3-mini': 'xAI Grok-3 Mini',
'minimax/MiniMax-M2.7': 'MiniMax M2.7', 'minimax/M2.1-Ultra': 'MiniMax M2.1 Ultra',
'local/llama-3.3-70b': 'Llama 3.3 70B',
'openai/o3': 'OpenAI o3', 'openai/o4-mini': 'OpenAI o4-mini',
'anthropic/claude-opus-4': 'Claude Opus 4 (full)',
'google/gemini-2.0-pro': 'Gemini 2.0 Pro',
'deepseek/deepseek-v3-0324': 'DeepSeek V3 (0324)',
};
return map[id] || id.split('/').pop();
}
function fmtNum(n) {
if (n >= 1e6) return (n/1e6).toFixed(1)+'M';
if (n >= 1e3) return (n/1e3).toFixed(0)+'k';
return n.toString();
}
// ── Pro / paywall check ──────────────────────────────
const STORAGE_KEY_PRO = 'sl_is_pro';
function isPro() {
return localStorage.getItem(STORAGE_KEY_PRO) === 'true' || !!localStorage.getItem('sl_api_key');
}
function applyPaywall() {
const locked = !isPro();
// Always show all tiers — just lock interaction for free users
['tier2','tier3'].forEach(tier => {
const sec = document.getElementById(tier + '_section');
const tog = document.getElementById(tier + '_toggle');
if (sec) {
sec.classList.toggle('locked', locked);
sec.classList.add('open'); // always visible
}
if (tog) {
tog.classList.toggle('locked', locked);
tog.classList.add('open'); // always expanded
const lockIcon = tog.querySelector('.tier-lock');
if (lockIcon) lockIcon.style.display = locked ? '' : 'none';
}
});
const gate = document.getElementById('tier_gate');
if (gate) gate.style.display = locked ? 'flex' : 'none';
const allBtn = document.getElementById('btn_select_all');
if (allBtn) allBtn.style.display = locked ? 'none' : '';
}
function toggleTier(tier) {
// Allow toggling only if unlocked
if (tier !== 'tier1' && !isPro()) return;
const sec = document.getElementById(tier + '_section');
const tog = document.getElementById(tier + '_toggle');
if (!sec || !tog) return;
const isOpen = sec.classList.toggle('open');
tog.classList.toggle('open', isOpen);
}
function unlockPro() {
window.open('https://signalloom-landing.pages.dev/loomlensai-pro.html', '_blank');
}
function setProFlag() {
// Wire this to your post-Stripe payment callback
localStorage.setItem(STORAGE_KEY_PRO, 'true');
applyPaywall();
}
// ── Init ────────────────────────────────────────────
initCreditBar();
renderQuotaUI();
wireChipListeners();
['tier1','tier2','tier3'].forEach(t => updateTierCount(t));
updateModelCount();
applyPaywall();
</script>
<div class="integrity-footer">
<div class="integrity-badge">🔒 No Training. No Logging. Your Prompts Stay Yours.</div>
<div>
<a href="https://signalloomai.com/prompt-integrity.html" target="_blank">Prompt Integrity</a>
<span class="sep">·</span>
<a href="https://signalloomai.com/privacy.html" target="_blank">Privacy Policy</a>
<span class="sep">·</span>
<a href="https://signalloomai.com/terms.html" target="_blank">Terms</a>
<span class="sep">·</span>
<a href="https://signalloomai.com" target="_blank">SignalloomAI™</a>
</div>
</div>
</body>
</html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>LoomLens AI™ — API Token Budget Calculator · SignalloomAI™</title>
<style>
:root {
--bg: #0a0e1a;
--surface: #111827;
--surface2: #1a2235;
--border: #1e2d4a;
--accent: #8b5cf6;
--accent-bright: #a78bfa;
--text: #e8edf5;
--text-dim: #8899bb;
--text-muted: #4a6a9a;
--green: #10b981;
--yellow: #f59e0b;
--red: #f87171;
--orange: #fb923c;
--radius: 10px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--text);
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
min-height: 100vh;
padding: 40px 20px;
}
.header {
text-align: center;
margin-bottom: 32px;
max-width: 680px;
margin-left: auto;
margin-right: auto;
}
.eyebrow {
font-size: 11px;
letter-spacing: 3px;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 10px;
font-weight: 600;
}
h1 { font-size: 32px; font-weight: 700; letter-spacing: -0.5px; margin-bottom: 14px; }
.header p { font-size: 15px; color: var(--text-dim); line-height: 1.7; max-width: 540px; margin: 0 auto 16px; }
/* Pricing banner */
.pricing-bar {
display: inline-flex;
align-items: center;
gap: 16px;
padding: 8px 20px;
border-radius: 99px;
background: rgba(139,92,246,0.1);
border: 1px solid rgba(139,92,246,0.25);
margin-bottom: 4px;
flex-wrap: wrap;
justify-content: center;
}
.pricing-item { font-size: 12px; font-weight: 600; color: var(--text-dim); }
.pricing-item strong { color: var(--accent-bright); }
.pricing-divider { width: 1px; height: 14px; background: rgba(139,92,246,0.3); }
.badge-free {
background: rgba(16,185,129,0.15);
border: 1px solid rgba(16,185,129,0.3);
color: var(--green);
padding: 2px 10px;
border-radius: 99px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.5px;
}
.badge-pro {
background: rgba(245,158,11,0.15);
border: 1px solid rgba(245,158,11,0.3);
color: var(--yellow);
padding: 2px 10px;
border-radius: 99px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.5px;
}
.layout {
display: grid;
grid-template-columns: 1fr 380px;
gap: 24px;
max-width: 1060px;
margin: 0 auto;
align-items: start;
}
@media (max-width: 820px) { .layout { grid-template-columns: 1fr; } }
.card {
background: var(--surface);