Main_Website-Oblistudios/shardwalkershop.html

334 lines
9.9 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<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>