Main_Website-Oblistudios/ASAshop.html

524 lines
19 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>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Shop • ObliStudios</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<style>
:root {
--bg: #0b1220; /* deep space */
--panel: #0f1730; /* cards/nav */
--muted: #97a4c0; /* secondary text */
--text: #e8eeff; /* primary text */
--accent: #4aa3ff; /* bright blue */
--accent-2: #2b7cff; /* darker hover */
--good: #1fd4a0;
--bad: #ff6b6b;
--ring: 0 0 0 2px rgba(74,163,255,.35);
--radius: 14px;
}
* {
box-sizing: border-box
}
html, body {
height: 100%
}
body {
margin: 0;
background: radial-gradient(1200px 800px at 10% -10%, #122041 0%, transparent 60%), var(--bg);
color: var(--text);
font: 16px/1.45 Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
}
a {
color: var(--accent);
text-decoration: none
}
a:hover {
color: var(--accent-2)
}
/* Layout */
header {
position: sticky;
top: 0;
z-index: 20;
backdrop-filter: blur(8px);
background: linear-gradient(180deg, rgba(11,18,32,.9) 0%, rgba(11,18,32,.6) 100%);
border-bottom: 1px solid rgba(255,255,255,.06);
}
.nav {
max-width: 1100px;
margin: 0 auto;
display: flex;
align-items: center;
gap: 18px;
padding: 14px 18px
}
.brand {
display: flex;
align-items: center;
gap: 10px;
font-weight: 700;
letter-spacing: .3px
}
.brand-badge {
width: 28px;
height: 28px;
border-radius: 8px;
background: linear-gradient(135deg,#3a66ff,#6ee7ff)
}
.nav a {
color: var(--text);
opacity: .9
}
.nav a:hover {
opacity: 1
}
.nav .spacer {
flex: 1
}
.cart-btn {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--accent);
color: #001023;
font-weight: 700;
padding: 10px 14px;
border-radius: 10px
}
.cart-btn:hover {
background: var(--accent-2);
color: white
}
main {
max-width: 1100px;
margin: 32px auto;
padding: 0 18px
}
/* Toolbar */
.toolbar {
display: grid;
grid-template-columns: 1fr 220px 160px;
gap: 12px;
margin: 12px 0 22px
}
.input, select {
background: var(--panel);
color: var(--text);
border: 1px solid rgba(255,255,255,.07);
border-radius: 10px;
padding: 12px 12px;
}
.input:focus, select:focus {
outline: none;
box-shadow: var(--ring);
border-color: transparent
}
/* Grid */
.grid {
display: grid;
grid-template-columns: repeat( auto-fit, minmax(240px, 1fr) );
gap: 16px
}
.card {
background: linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.02));
border: 1px solid rgba(255,255,255,.08);
border-radius: var(--radius);
overflow: hidden;
display: flex;
flex-direction: column
}
.card .img {
height: 160px;
background: #0e1326;
display: flex;
align-items: center;
justify-content: center
}
.card .img canvas, .card .img img {
max-width: 100%;
max-height: 100%
}
.card .body {
padding: 14px
}
.card h3 {
margin: 0 0 6px;
font-size: 18px
}
.price {
font-weight: 700
}
.meta {
color: var(--muted);
font-size: 13px
}
.chip {
display: inline-block;
padding: 3px 8px;
border-radius: 999px;
background: #112651;
color: #8cc4ff;
font-size: 12px;
margin-top: 8px
}
.btn {
margin-top: 12px;
background: var(--accent);
border: none;
color: #001023;
font-weight: 700;
padding: 10px 12px;
border-radius: 10px;
cursor: pointer
}
.btn:hover {
background: var(--accent-2);
color: white
}
/* Cart panel */
.drawer {
position: fixed;
top: 0;
right: -420px;
width: 380px;
height: 100%;
background: var(--panel);
border-left: 1px solid rgba(255,255,255,.08);
box-shadow: -8px 0 40px rgba(0,0,0,.45);
transition: right .3s ease;
z-index: 60;
display: flex;
flex-direction: column
}
.drawer.open {
right: 0
}
.drawer header {
position: sticky;
top: 0;
padding: 16px;
backdrop-filter: none;
background: transparent;
border-bottom: 1px solid rgba(255,255,255,.08)
}
.drawer h4 {
margin: 0
}
.drawer .items {
flex: 1;
overflow: auto;
padding: 12px 16px;
display: flex;
flex-direction: column;
gap: 10px
}
.row {
display: flex;
align-items: center;
gap: 10px;
background: rgba(255,255,255,.02);
border: 1px solid rgba(255,255,255,.06);
border-radius: 12px;
padding: 10px
}
.row .title {
flex: 1
}
.qty {
display: inline-flex;
align-items: center;
gap: 8px
}
.qty button {
width: 26px;
height: 26px;
border-radius: 8px;
border: 1px solid rgba(255,255,255,.15);
background: transparent;
color: var(--text);
cursor: pointer
}
.qty button:hover {
background: rgba(255,255,255,.06)
}
.drawer .foot {
padding: 16px;
border-top: 1px solid rgba(255,255,255,.08)
}
.total {
display: flex;
justify-content: space-between;
margin-bottom: 10px
}
.checkout {
width: 100%;
padding: 12px;
border-radius: 10px;
background: var(--good);
color: #001c12;
border: none;
font-weight: 800;
cursor: pointer
}
.checkout:hover {
filter: brightness(1.08)
}
footer {
max-width: 1100px;
margin: 40px auto;
padding: 20px 18px;
color: var(--muted)
}
@media (max-width: 820px) {
.toolbar {
grid-template-columns: 1fr 1fr;
}
}
</style>
</head>
<body>
<header>
<nav class="nav">
<div class="brand">
<div class="brand-badge"></div>
<a href="https://www.oblistudios.com">ObliStudios</a>
</div>
<a href="https://www.oblistudios.com/ASAservers.html">Servers</a>
<div class="spacer"></div>
<a class="cart-btn" href="#" id="openCart">🛒 <span id="cartCount">0</span></a>
</nav>
</header>
<main>
<h1 style="margin:6px 0 4px; font-size:28px">Shop</h1>
<p style="margin:0 0 18px; color:var(--muted)">Merch, Servers, and server perks. Secure checkout via Stripe Payment Links.</p>
<div class="toolbar">
<input id="search" class="input" placeholder="Search products…" />
<select id="category">
<option value="all">All categories</option>
<option value="merch">Merch</option>
<option value="server">Servers</option>
<option value="perks">Server Perks</option>
</select>
<select id="sort">
<option value="featured">Sort: Featured</option>
<option value="price-asc">Price: Low → High</option>
<option value="price-desc">Price: High → Low</option>
<option value="name">Name</option>
</select>
</div>
<section class="grid" id="grid"></section>
</main>
<aside class="drawer" id="drawer" aria-hidden="true">
<header>
<h4>Cart</h4>
</header>
<div class="items" id="cartItems"></div>
<div class="foot">
<div class="total">
<div>Total</div>
<div id="cartTotal" class="price">$0.00</div>
</div>
<button class="checkout" id="checkoutBtn">Checkout</button>
<div style="margin-top:8px; font-size:12px; color:var(--muted)">Checkout uses Stripe Payment Links per item or a generic link. Configure below in <code>products</code>.</div>
</div>
</aside>
<footer>
<div>© <span id="year"></span> ObliStudios </div>
</footer>
<script>
// ======= Demo products (edit to your real products) =======
const products = [
{ id: 'Server', name: 'Server Class 1', price: 25.00, category: 'server', tag: 'Server monthly', img: 'img/PrivateServerCLASS01.png', payment: 'https://buy.stripe.com/test_123tee' },
{ id: 'Server', name: 'Server Class 2', price: 50.00, category: 'server', tag: 'Server monthly', img: 'img/PrivateServerCLASS02.png', payment: 'https://buy.stripe.com/test_123mug' },
{ id: 'Server', name: 'Server Class 3', price: 75.00, category: 'server', tag: 'Server monthly', img: 'img/PrivateServerCLASS03.png', payment: 'https://buy.stripe.com/test_123ost' },
{ id: 'perk1', name: 'ASA Server Slot x30 days', price: 5.00, category: 'perks', tag: 'Server Perk monthly', img: 'img/ServerSlotX30.png', payment: 'https://buy.stripe.com/test_123perk' },
{ id: 'perk1', name: 'Mutated Creatures', price: 1.50, category: 'perks', tag: 'Dino Pack One Time Payment 10 non-breedable', img: 'img/MutatedCretures.png', payment: 'https://buy.stripe.com/test_123perk' },
{ id: 'perk1', name: 'Starter Pack', price: 1.50, category: 'perks', tag: 'Starter Pack One Time Payment', img: 'img/StarterPack.png', payment: 'https://buy.stripe.com/test_123perk' },
{ id: 'Server', name: 'Small Map manipulation', price: 5.00, category: 'server', tag: 'Small Map manipulation Pack One Time Payment', img: 'img/SmallMap.png', payment: 'https://buy.stripe.com/test_123perk' },
{ id: 'Server', name: 'Medium Map manipulation', price: 10.00, category: 'server', tag: 'Medium Map manipulation Pack One Time Payment', img: 'img/MediumMap.png', payment: 'https://buy.stripe.com/test_123perk' },
{ id: 'Server', name: 'Large Map manipulation', price: 15.00, category: 'server', tag: 'Large Map manipulation Pack One Time Payment', img: 'img/LargeMap.png', payment: 'https://buy.stripe.com/test_123perk' },
];
// ======= State =======
const state = { q:'', cat:'all', sort:'featured', cart: loadCart() };
// ======= Helpers =======
const $ = sel => document.querySelector(sel);
const fmt = n => `$${n.toFixed(2)}`;
function saveCart(){ localStorage.setItem('obli.cart', JSON.stringify(state.cart)); }
function loadCart(){ try{ return JSON.parse(localStorage.getItem('obli.cart')||'{}'); }catch{ return {}; } }
function cartCount(){ return Object.values(state.cart).reduce((a,b)=>a+b,0); }
function cartTotal(){ return Object.entries(state.cart).reduce((sum,[id,qty])=>{
const p = products.find(p=>p.id===id); return sum + (p?p.price*qty:0);
},0); }
// ======= Render products =======
function render(){
const grid = $('#grid');
let list = products.filter(p=> state.cat==='all' || p.category===state.cat);
if(state.q){ const q=state.q.toLowerCase(); list = list.filter(p=> p.name.toLowerCase().includes(q) || (p.tag||'').toLowerCase().includes(q)); }
switch(state.sort){
case 'price-asc': list.sort((a,b)=>a.price-b.price); break;
case 'price-desc': list.sort((a,b)=>b.price-a.price); break;
case 'name': list.sort((a,b)=>a.name.localeCompare(b.name)); break;
default: /* featured */ break;
}
grid.innerHTML = list.map(p=>cardHtml(p)).join('');
updateCartUi();
}
function cardHtml(p){
const inCart = state.cart[p.id]||0;
return `<article class="card">
<div class="img">${p.img?`<img src="${p.img}" alt="${p.name}">`:`<canvas data-id="${p.id}" width="320" height="160"></canvas>`}</div>
<div class="body">
<h3>${p.name}</h3>
<div class="meta">${p.tag || ''}</div>
<div class="price" style="margin-top:6px">${fmt(p.price)}</div>
<div class="chip">${p.category}</div>
<button class="btn" data-add="${p.id}">${inCart?`Add another`:`Add to cart`}</button>
</div>
</article>`;
}
// ======= Cart UI =======
function updateCartUi(){
$('#cartCount').textContent = cartCount();
$('#cartTotal').textContent = fmt(cartTotal());
const wrap = $('#cartItems');
const rows = Object.entries(state.cart);
wrap.innerHTML = rows.length? rows.map(([id,qty])=> rowHtml(id,qty)).join('') : '<div style="color:var(--muted)">Cart is empty.</div>';
}
function rowHtml(id, qty){
const p = products.find(x=>x.id===id);
return `<div class="row">
<div class="title"><div style="font-weight:600">${p.name}</div><div class="meta">${fmt(p.price)} each</div></div>
<div class="qty">
<button data-dec="${id}"></button>
<div>${qty}</div>
<button data-inc="${id}">+</button>
</div>
<div style="width:72px; text-align:right">${fmt(p.price*qty)}</div>
</div>`;
}
// ======= Events =======
$('#search').addEventListener('input', e=>{ state.q=e.target.value; render(); });
$('#category').addEventListener('change', e=>{ state.cat=e.target.value; render(); });
$('#sort').addEventListener('change', e=>{ state.sort=e.target.value; render(); });
document.body.addEventListener('click', e=>{
const add = e.target.closest('[data-add]');
const inc = e.target.closest('[data-inc]');
const dec = e.target.closest('[data-dec]');
if(add){ const id = add.getAttribute('data-add'); state.cart[id]=(state.cart[id]||0)+1; saveCart(); render(); }
if(inc){ const id = inc.getAttribute('data-inc'); state.cart[id]=(state.cart[id]||0)+1; saveCart(); updateCartUi(); }
if(dec){ const id = dec.getAttribute('data-dec'); state.cart[id]=Math.max(0,(state.cart[id]||0)-1); if(state.cart[id]===0) delete state.cart[id]; saveCart(); render(); }
});
const drawer = $('#drawer');
$('#openCart').addEventListener('click', e=>{ e.preventDefault(); drawer.classList.add('open'); drawer.setAttribute('aria-hidden','false'); updateCartUi(); });
document.addEventListener('keydown', e=>{ if(e.key==='Escape') { drawer.classList.remove('open'); drawer.setAttribute('aria-hidden','true'); }});
// Checkout strategy: if a single item, go to its Stripe Payment Link
// otherwise, open a generic link (configure below) or email summary.
const GENERIC_CHECKOUT = 'https://buy.stripe.com/test_generic';
$('#checkoutBtn').addEventListener('click', ()=>{
const entries = Object.entries(state.cart);
if(!entries.length) return alert('Your cart is empty.');
if(entries.length===1){
const [id, qty] = entries[0];
const p = products.find(p=>p.id===id);
if(p && p.payment){ window.open(p.payment, '_blank'); return; }
}
// Fallback: generic link (Stripe payment link for a bundle) or mailto
if(GENERIC_CHECKOUT && GENERIC_CHECKOUT.startsWith('http')){
window.open(GENERIC_CHECKOUT, '_blank');
} else {
const lines = entries.map(([id,qty])=>{ const p=products.find(p=>p.id===id); return `${qty} x ${p?.name || id}`; }).join('%0A');
window.location.href = `mailto:sales@oblistudios.com?subject=Order%20Request&body=${lines}`;
}
});
// Minimal placeholder thumbnails (generated on canvas so page can ship without assets)
function paintPlaceholders(){
document.querySelectorAll('canvas[data-id]').forEach(cv=>{
const id = cv.getAttribute('data-id');
const ctx = cv.getContext('2d');
const g = ctx.createLinearGradient(0,0,cv.width,cv.height);
g.addColorStop(0,'#0f1b3a'); g.addColorStop(1,'#1b2f5d');
ctx.fillStyle = g; ctx.fillRect(0,0,cv.width,cv.height);
ctx.strokeStyle = 'rgba(255,255,255,.15)';
for(let i=0;i<8;i++){ ctx.beginPath(); ctx.moveTo(0,i*22); ctx.lineTo(cv.width, i*22); ctx.stroke(); }
ctx.fillStyle = '#8cc4ff';
ctx.font = 'bold 18px Inter';
ctx.fillText(products.find(p=>p.id===id)?.name || id, 16, cv.height-16);
});
}
// Boot
document.getElementById('year').textContent = new Date().getFullYear();
render();
paintPlaceholders();
</script>
</body>
</html>