Add files via upload
This commit is contained in:
parent
fb106f68dc
commit
5513f12a2c
|
|
@ -354,6 +354,7 @@
|
||||||
<nav class="links" aria-label="Primary">
|
<nav class="links" aria-label="Primary">
|
||||||
|
|
||||||
<a aria-current="page" href="https://www.oblistudios.com">Home</a>
|
<a aria-current="page" href="https://www.oblistudios.com">Home</a>
|
||||||
|
<a aria-current="page" href="https://www.oblistudios.com/ASAshop.html"> Server Shop </a>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,520 @@
|
||||||
|
<!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="Servers">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' },
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
// ======= 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>
|
||||||
|
|
@ -429,6 +429,7 @@
|
||||||
<li><a href="#contact">Contact</a></li>
|
<li><a href="#contact">Contact</a></li>
|
||||||
<a href="https://www.oblistudios.com/roadmap.html">Roadmap</a>
|
<a href="https://www.oblistudios.com/roadmap.html">Roadmap</a>
|
||||||
<a href="https://www.oblistudios.com/ASAservers.html"> ASA Servers</a>
|
<a href="https://www.oblistudios.com/ASAservers.html"> ASA Servers</a>
|
||||||
|
<a href="https://www.oblistudios.com/shop.html"> Shop</a>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue