Upload files to "/"

This commit is contained in:
Obligod 2025-12-07 00:37:51 -08:00
parent 799756d52e
commit f4b4b37e8b
1 changed files with 333 additions and 0 deletions

333
shardwalkershop.html Normal file
View File

@ -0,0 +1,333 @@
<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Shardwalker Shop</title>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
<style>
:root {
--bg: #0b1220;
--card: #0f172a;
--ink: #e5e7eb;
--muted: #94a3b8;
--accent: #7c3aed;
--ok: #22c55e;
--bad: #ef4444
}
* {
box-sizing: border-box
}
body {
margin: 0;
font-family: Inter,system-ui,Segoe UI,Roboto,Ubuntu,sans-serif;
background: var(--bg);
color: var(--ink)
}
.wrap {
max-width: 1100px;
margin: 0 auto;
padding: 28px
}
header {
display: flex;
gap: 12px;
align-items: center;
justify-content: space-between;
margin-bottom: 18px
}
h1 {
margin: 0;
font-size: 28px;
font-weight: 800;
letter-spacing: .2px
}
nav a {
color: var(--muted);
text-decoration: none;
margin-left: 16px;
font-size: 14px
}
nav a:hover {
color: #fff
}
.health {
font-size: 13px;
color: var(--muted)
}
.pill {
display: inline-block;
padding: 2px 10px;
border-radius: 999px;
border: 1px solid currentColor
}
.ok {
color: var(--ok)
}
.bad {
color: var(--bad)
}
.grid {
display: grid;
gap: 18px;
grid-template-columns: repeat(auto-fit,minmax(260px,1fr))
}
.card {
background: var(--card);
border: 1px solid #1f2937;
border-radius: 18px;
overflow: hidden;
display: flex;
flex-direction: column
}
.hero {
aspect-ratio: 16/9;
background: #111827;
display: grid;
place-items: center;
font-weight: 800;
color: #c084fc
}
.body {
padding: 16px;
display: flex;
flex-direction: column;
gap: 10px
}
.name {
font-weight: 800;
font-size: 16px
}
.desc {
color: var(--muted);
font-size: 13px;
min-height: 34px
}
.row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px
}
.price {
font-weight: 800
}
.qty {
display: flex;
align-items: center;
gap: 6px
}
.qty button {
width: 28px;
height: 28px;
border-radius: 8px;
border: 1px solid #334155;
background: #111827;
color: var(--ink);
cursor: pointer
}
.qty input {
width: 52px;
text-align: center;
border: 1px solid #334155;
border-radius: 8px;
background: #0b1220;
color: var(--ink);
padding: 6px
}
.add {
margin-top: 8px;
background: #1e293b;
border: 1px solid #334155;
color: #fff;
border-radius: 12px;
padding: 10px 12px;
cursor: pointer
}
.add:hover {
filter: brightness(1.1)
}
.bar {
position: sticky;
bottom: 16px;
display: flex;
gap: 8px;
justify-content: flex-end
}
.checkout {
background: linear-gradient(135deg,#7c3aed,#22d3ee);
border: none;
border-radius: 14px;
color: #fff;
font-weight: 800;
padding: 12px 18px;
cursor: pointer
}
.ghost {
background: #111827;
border: 1px solid #334155;
border-radius: 14px;
color: var(--ink);
padding: 12px 14px
}
.cart-mini {
color: var(--muted);
font-size: 13px
}
</style>
<div class="wrap">
<header>
<div style="display:flex;align-items:center;gap:16px;">
<h1>Shardwalker Shop</h1>
<nav>
<a href="https://www.oblistudios.com/index.html">Home</a>
<a href="https://www.oblistudios.com/roadmap.html">Roadmap</a>
<a href="https://www.oblistudios.com/ASAshop.html">ASA Shop</a>
<a href="https://www.oblistudios.com/shardwalkershop.html">Shardwalker Shop</a>
</nav>
</div>
<div class="health">Shop API: <span id="apiPill" class="pill">checking…</span></div>
</header>
<div class="grid" id="grid"></div>
<div class="bar">
<div class="ghost cart-mini" id="cartInfo">Cart is empty</div>
<button class="checkout" id="checkoutBtn">Checkout</button>
</div>
</div>
<script>
/* --- Configure your Stripe prices (same mode as server key) --- */
const CATALOG = [
{ sku: 'sw_base', name: 'Shardwalker Base Game', priceId: 'price_LIVE_BASE', cents: 1500, hero: 'BASE' },
{ sku: 'sw_deluxe', name: 'Shardwalker Deluxe Edition', priceId: 'price_LIVE_DELUXE', cents: 2999, hero: 'DELUXE' },
{ sku: 'sw_cosm1', name: 'Cosmetic Pack: Voidglass', priceId: 'price_LIVE_COSM1', cents: 199, hero: 'VOID' },
{ sku: 'sw_cosm2', name: 'Cosmetic Pack: Astral Weave', priceId: 'price_LIVE_COSM2', cents: 199, hero: 'ASTRAL' },
{ sku: 'sw_sound', name: 'Official Soundtrack (MP3/FLAC)', priceId: 'price_LIVE_SOUND', cents: 299, hero: 'OST' },
{ sku: 'sw_founder', name: 'Founder Supporter Bundle', priceId: 'price_LIVE_FOUNDER', cents: 3599, hero: 'FDR' },
];
const API_BASE = 'https://pay.oblistudios.com';
const CHECKOUT_ENDPOINT = API_BASE + '/create-checkout-session';
const grid = document.getElementById('grid');
const cartInfo = document.getElementById('cartInfo');
const btn = document.getElementById('checkoutBtn');
const pill = document.getElementById('apiPill');
const cart = new Map(); // priceId -> quantity
const dollars = (cents) => '$' + (cents / 100).toFixed(2);
const setPill = (ok) => { pill.className = 'pill ' + (ok ? 'ok' : 'bad'); pill.textContent = ok ? 'ONLINE' : 'OFFLINE'; };
async function health() {
try {
const r = await fetch(API_BASE + '/healthz', { cache: 'no-store' });
setPill(r.ok);
} catch { setPill(false); }
}
health(); setInterval(health, 15000);
// render cards
for (const p of CATALOG) {
const el = document.createElement('div');
el.className = 'card';
el.innerHTML = `
<div class="hero">${p.hero}</div>
<div class="body">
<div class="name">${p.name}</div>
<div class="desc">Secure Stripe checkout. Instant Discord fulfillment.</div>
<div class="row">
<div class="price">${dollars(p.cents)}</div>
<div class="qty">
<button data-act="dec" aria-label="decrease"></button>
<input data-qty type="number" min="0" value="0" step="1" inputmode="numeric" aria-label="quantity">
<button data-act="inc" aria-label="increase">+</button>
</div>
</div>
<button class="add">Add to Cart</button>
</div>`;
const dec = el.querySelector('[data-act="dec"]');
const inc = el.querySelector('[data-act="inc"]');
const qty = el.querySelector('[data-qty]'); /* fixed selector */
const add = el.querySelector('.add');
dec.onclick = () => qty.value = Math.max(0, (+qty.value || 0) - 1);
inc.onclick = () => qty.value = (+qty.value || 0) + 1;
add.onclick = () => {
const q = Math.max(0, Math.floor(+qty.value || 0));
if (q === 0) cart.delete(p.priceId); else cart.set(p.priceId, q);
renderCart();
};
grid.appendChild(el);
}
function renderCart() {
if (cart.size === 0) { cartInfo.textContent = 'Cart is empty'; return; }
let items = 0, total = 0;
for (const [priceId, q] of cart) {
const prod = CATALOG.find(x => x.priceId === priceId);
if (!prod) continue;
items += q; total += q * prod.cents;
}
cartInfo.textContent = `${items} item${items > 1 ? 's' : ''} • ${dollars(total)}`;
}
btn.onclick = async () => {
if (cart.size === 0) { alert('Your cart is empty.'); return; }
btn.disabled = true; btn.textContent = 'Creating session…';
try {
const line_items = Array.from(cart.entries()).map(([price, quantity]) => ({ price, quantity }));
const r = await fetch(CHECKOUT_ENDPOINT, {
method: 'POST',
headers: { 'content-type': 'application/json', 'accept': 'application/json' },
body: JSON.stringify({
line_items,
mode: 'payment',
// optional: success/cancel redirects
success_url: 'https://www.oblistudios.com/shardwalkershop.html?ok=true',
cancel_url: 'https://www.oblistudios.com/shardwalkershop.html?cancel=true'
})
});
if (!r.ok) {
const txt = await r.text();
alert('Checkout failed:\n' + txt.slice(0, 300));
btn.disabled = false; btn.textContent = 'Checkout'; return;
}
const j = await r.json();
if (j && j.url) window.location = j.url;
else { alert('Missing session URL from server.'); btn.disabled = false; btn.textContent = 'Checkout'; }
} catch (e) {
alert('Network error: ' + (e?.message || e));
btn.disabled = false; btn.textContent = 'Checkout';
}
};
</script>