LoomLensAI / index.html
Aster Vale
feat: credit balance bar + LoomToken display + /mo pricing
5831832
Raw History Blame Contribute Delete
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...&#10;&#10;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&amp;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 &amp; 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>
<!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);