inital checkin

This commit is contained in:
2026-01-20 05:22:38 -06:00
parent 9784e14c77
commit c43603bfb5
75 changed files with 4327 additions and 0 deletions

347
templates/base/layout.html Normal file
View File

@@ -0,0 +1,347 @@
{% load static %}
<!DOCTYPE html>
<html lang="en" data-theme="{% if user.is_authenticated %}{{ user.profile.theme_preference }}{% else %}dark{% endif %}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Phantom Card Fam - Premium TCG Store{% endblock %}</title>
<link rel="stylesheet" href="{% static 'css/style.css' %}">
{% if not debug %}
<script async defer src="https://tianji.aimloperations.com/tracker.js" data-website-id="cmklg5jenh4wx14nrurt5yqyl"></script>
{% endif %}
<style>
:root {
--primary-color: #6366f1;
--secondary-color: #a855f7;
--bg-color: #0f172a;
--text-color: #f8fafc;
--card-bg: #1e293b;
--border-color: #334155;
}
[data-theme="light"] {
--primary-color: #4f46e5;
--secondary-color: #9333ea;
--bg-color: #f8fafc;
--text-color: #0f172a;
--card-bg: #ffffff;
--border-color: #e2e8f0;
}
body {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
margin: 0;
line-height: 1.5;
transition: background-color 0.3s, color 0.3s;
}
nav {
background-color: var(--card-bg);
border-bottom: 1px solid var(--border-color);
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.nav-brand {
font-size: 1.5rem;
font-weight: 800;
background: linear-gradient(to right, var(--primary-color), var(--secondary-color));
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
text-decoration: none;
}
.nav-links a {
color: var(--text-color);
text-decoration: none;
margin-left: 1.5rem;
font-weight: 500;
transition: color 0.2s;
}
.nav-links a:hover {
color: var(--primary-color);
}
.container {
max-width: 1200px;
margin: 2rem auto;
padding: 0 1rem;
}
.btn {
display: inline-block;
padding: 0.5rem 1rem;
background-color: var(--primary-color);
color: white;
text-decoration: none;
border-radius: 0.375rem;
font-weight: 600;
transition: opacity 0.2s;
border: none;
cursor: pointer;
}
.btn:hover {
opacity: 0.9;
}
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 2rem;
}
.tcg-card {
background-color: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 0.5rem;
overflow: hidden;
transition: transform 0.2s;
}
.tcg-card:hover {
transform: translateY(-4px);
}
.tcg-card img {
width: 100%;
height: auto;
display: block;
}
.tcg-card-body {
padding: 1rem;
}
.messages {
list-style: none;
padding: 0;
margin-bottom: 2rem;
}
.messages li {
padding: 1rem;
border-radius: 0.375rem;
margin-bottom: 0.5rem;
}
.messages .success { background-color: #064e3b; color: #a7f3d0; }
.messages .error { background-color: #7f1d1d; color: #fecaca; }
[data-theme="light"] .messages .success { background-color: #d1fae5; color: #065f46; }
[data-theme="light"] .messages .error { background-color: #fce7f3; color: #9d174d; }
/* Auth Modal Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s, visibility 0.3s;
backdrop-filter: blur(4px);
}
.modal-overlay.active {
opacity: 1;
visibility: visible;
}
.auth-modal {
background-color: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 0.75rem;
padding: 2rem;
max-width: 400px;
width: 90%;
text-align: center;
transform: translateY(20px);
transition: transform 0.3s;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.modal-overlay.active .auth-modal {
transform: translateY(0);
}
.auth-modal h2 {
margin-top: 0;
color: var(--primary-color);
font-size: 1.5rem;
margin-bottom: 1rem;
}
.auth-modal p {
margin-bottom: 2rem;
color: var(--text-color);
line-height: 1.6;
}
.auth-modal-actions {
display: flex;
gap: 1rem;
justify-content: center;
}
.btn-outline {
background-color: transparent;
border: 1px solid var(--primary-color);
color: var(--primary-color);
}
.btn-outline:hover {
background-color: rgba(99, 102, 241, 0.1);
}
.close-modal {
position: absolute;
top: 1rem;
right: 1rem;
background: none;
border: none;
color: var(--text-color);
font-size: 1.5rem;
cursor: pointer;
opacity: 0.5;
padding: 0;
line-height: 1;
}
.close-modal:hover {
opacity: 1;
}
</style>
</head>
<body>
<div style="background: #f59e0b; color: #000; text-align: center; padding: 0.5rem; font-weight: 600; font-size: 0.875rem;">
DEMO SITE: This is an example application. No real products, payments, or purchases are processed.
</div>
<nav>
<a href="{% url 'home' %}" class="nav-brand">Phantom Card Fam</a>
<div class="nav-links">
<a href="{% url 'store:card_list' %}">Browse</a>
<a href="{% url 'store:pack_list' %}">Packs</a>
{% if user.is_authenticated %}
<a href="{% url 'decks:deck_list' %}">Decks</a>
<a href="{% url 'users:vault' %}">Vault</a>
<a href="{% url 'store:my_packs' %}">My Packs</a>
<a href="{% url 'store:cart' %}">Cart ({{ user.cart.items.count|default:0 }})</a>
<a href="{% url 'users:profile' %}">Profile</a>
<form action="{% url 'logout' %}" method="post" style="display:inline;">
{% csrf_token %}
<button type="submit" class="btn" style="background:none; color:var(--text-color); margin-left:1rem;">Logout</button>
</form>
{% else %}
<a href="{% url 'decks:deck_list' %}"
class="auth-required"
data-feature-title="Deck Builder"
data-feature-desc="Build and save custom decks, analyze mana curves, and prepare your strategies for battle. Create an account to start building!">Decks</a>
<a href="{% url 'users:vault' %}"
class="auth-required"
data-feature-title="Collection Vault"
data-feature-desc="Track your entire card collection, monitor value trends, and manage your inventory in one place. Log in to access your Vault.">Vault</a>
<a href="{% url 'store:my_packs' %}"
class="auth-required"
data-feature-title="My Packs"
data-feature-desc="Open virtual packs, collect rare cards, and grow your digital library. Sign up now to start cracking packs!">My Packs</a>
<a href="{% url 'login' %}">Login</a>
<a href="{% url 'users:register' %}">Register</a>
{% endif %}
</div>
</nav>
<div class="container">
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% block content %}
{% endblock %}
</div>
<footer style="text-align: center; padding: 2rem; color: #64748b; font-size: 0.875rem;">
<p>&copy; 2026 Phantom Card Fam TCG Store.</p>
<p>Made by <a href="https://aimloperations.com" target="_blank" style="color: inherit; text-decoration: underline;">AI ML Operations, LLC</a></p>
</footer>
{% if not user.is_authenticated %}
<div id="authModal" class="modal-overlay">
<div class="auth-modal">
<button class="close-modal" aria-label="Close modal">&times;</button>
<h2 id="modalTitle">Login Required</h2>
<p id="modalDesc">Please log in to access this feature.</p>
<div class="auth-modal-actions">
<a href="{% url 'login' %}" class="btn">Log In</a>
<a href="{% url 'users:register' %}" class="btn btn-outline">Create Account</a>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const modal = document.getElementById('authModal');
const closeBtn = document.querySelector('.close-modal');
const modalTitle = document.getElementById('modalTitle');
const modalDesc = document.getElementById('modalDesc');
const authLinks = document.querySelectorAll('.auth-required');
function openModal(title, desc) {
modalTitle.textContent = title;
modalDesc.textContent = desc;
modal.classList.add('active');
document.body.style.overflow = 'hidden'; // Prevent scrolling
}
function closeModal() {
modal.classList.remove('active');
document.body.style.overflow = '';
}
authLinks.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const title = this.dataset.featureTitle || 'Login Required';
const desc = this.dataset.featureDesc || 'Please log in to access this feature.';
openModal(title, desc);
});
});
closeBtn.addEventListener('click', closeModal);
// Close on outside click
modal.addEventListener('click', function(e) {
if (e.target === modal) {
closeModal();
}
});
// Close on Escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && modal.classList.contains('active')) {
closeModal();
}
});
});
</script>
{% endif %}
</body>
</html>

View File

@@ -0,0 +1,83 @@
{% extends 'base/layout.html' %}
{% block content %}
<div style="display: grid; grid-template-columns: 350px 1fr; gap: 2rem; height: calc(100vh - 100px);">
<!-- Card Search Sidebar -->
<div
style="background: var(--card-bg); border-right: 1px solid var(--border-color); display: flex; flex-direction: column; overflow: hidden; border-radius: 0.5rem; border: 1px solid var(--border-color);">
<div style="padding: 1rem; border-bottom: 1px solid var(--border-color);">
<h3 style="margin: 0 0 1rem;">Add Cards</h3>
<form method="get">
<input type="text" name="q" value="{{ query|default:'' }}"
placeholder="Search {{ deck.game.name }} cards..."
style="width: 100%; padding: 0.5rem; border-radius: 0.25rem; background: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color);">
</form>
</div>
<div style="overflow-y: auto; flex: 1; padding: 1rem;">
{% if search_results %}
{% for card in search_results %}
<div style="display: flex; gap: 0.5rem; margin-bottom: 1rem; align-items: center;">
{% if card.image_url %}
<img src="{{ card.image_url }}"
style="width: 40px; height: 56px; object-fit: cover; border-radius: 2px;">
{% endif %}
<div style="flex: 1;">
<div style="font-size: 0.875rem; font-weight: 500;">{{ card.name }}</div>
<div style="font-size: 0.75rem; color: #94a3b8;">{{ card.set.code }}</div>
</div>
<form method="post">
{% csrf_token %}
<input type="hidden" name="card_id" value="{{ card.id }}">
<button type="submit" class="btn" style="padding: 0.25rem 0.5rem; font-size: 1.25rem;">+</button>
</form>
</div>
{% endfor %}
{% elif query %}
<p style="text-align: center; color: #94a3b8;">No cards found.</p>
{% else %}
<p style="text-align: center; color: #94a3b8; margin-top: 2rem;">Search to add cards.</p>
{% endif %}
</div>
</div>
<!-- Deck View -->
<div style="display: flex; flex-direction: column; overflow: hidden;">
<div style="margin-bottom: 1rem; display: flex; justify-content: space-between; align-items: center;">
<div>
<h1 style="margin: 0;">{{ deck.name }}</h1>
<p style="margin: 0.25rem 0 0; color: #94a3b8;">{{ deck.game.name }} &bull; {{ deck.cards.count }} cards
</p>
</div>
<button class="btn">Save & Close</button>
</div>
<div
style="flex: 1; overflow-y: auto; display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 1rem; align-content: start;">
{% for item in deck.cards.all %}
<div style="position: relative; group">
<div
style="aspect-ratio: 2.5/3.5; background: #000; border-radius: 4px; overflow: hidden; position: relative;">
{% if item.card.image_url %}
<img src="{{ item.card.image_url }}" style="width: 100%; height: 100%; object-fit: cover;">
{% endif %}
<div
style="position: absolute; bottom: 0; right: 0; background: rgba(0,0,0,0.8); color: white; padding: 0.25rem 0.5rem; border-top-left-radius: 4px; font-weight: 700;">
x{{ item.quantity }}
</div>
</div>
<div style="text-align: center; margin-top: 0.25rem;">
<a href="{% url 'decks:remove_card' deck.id item.card.id %}"
style="color: #ef4444; font-size: 0.75rem; text-decoration: none;">Remove</a>
</div>
</div>
{% empty %}
<div
style="grid-column: 1/-1; text-align: center; padding: 4rem; border: 2px dashed var(--border-color); border-radius: 0.5rem; color: #94a3b8;">
<p>Your deck is empty. Add cards from the left sidebar.</p>
</div>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,28 @@
{% extends 'base/layout.html' %}
{% block content %}
<div
style="max-width: 400px; margin: 4rem auto; background: var(--card-bg); padding: 2rem; border-radius: 0.5rem; border: 1px solid var(--border-color);">
<h2 style="margin-top: 0;">Create New Deck</h2>
<form method="post">
{% csrf_token %}
<div style="margin-bottom: 1rem;">
<label style="display: block; margin-bottom: 0.5rem;">Deck Name</label>
<input type="text" name="name" required
style="width: 100%; padding: 0.5rem; border-radius: 0.25rem; background: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color);">
</div>
<div style="margin-bottom: 1.5rem;">
<label style="display: block; margin-bottom: 0.5rem;">Game Format</label>
<select name="game" required
style="width: 100%; padding: 0.5rem; border-radius: 0.25rem; background: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color);">
{% for game in games %}
<option value="{{ game.id }}">{{ game.name }}</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn" style="width: 100%;">Create Deck</button>
</form>
</div>
{% endblock %}

View File

@@ -0,0 +1,49 @@
{% extends 'base.html' %}
{% block content %}
<div class="container py-4">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow-sm">
<div class="card-header bg-white">
<h4 class="mb-0">Import Deck</h4>
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="name" class="form-label">Deck Name</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="game" class="form-label">Game</label>
<select class="form-select" id="game" name="game" required>
{% for game in games %}
<option value="{{ game.id }}">{{ game.name }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label for="deckList" class="form-label">Deck List</label>
<div class="form-text mb-2">
Supported formats: Arena, MTGO, Moxfield, Plain Text.
<br>Example: <code>4 Lightning Bolt</code> or <code>1 Sheoldred, the Apocalypse (DMU) 107</code>
</div>
<textarea class="form-control" id="deckList" name="deck_list" rows="15" required
placeholder="1 Card Name&#10;1 Another Card&#10;...&#10;SIDEBOARD:&#10;1 Sideboard Card"></textarea>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">Import Deck</button>
<a href="{% url 'decks:deck_list' %}" class="btn btn-outline-secondary">Cancel</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,47 @@
{% extends 'base/layout.html' %}
{% block content %}
<div style="max-width: 800px; margin: 0 auto;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
<h1>My Decks</h1>
{% if user.profile.is_pro %}
<div class="col-md-6 text-end">
<a href="{% url 'decks:deck_import' %}" class="btn btn-outline-primary me-2">
<i class="bi bi-upload"></i> Import Deck
</a>
<a href="{% url 'decks:deck_create' %}" class="btn btn-primary">
<i class="bi bi-plus-lg"></i> Create New Deck
</a>
</div>
{% else %}
<a href="{% url 'users:profile' %}" class="btn" style="background: #475569;">Upgrade to Pro to Create Decks</a>
{% endif %}
</div>
{% if decks %}
<div class="card-grid">
{% for deck in decks %}
<div class="tcg-card">
<a href="{% url 'decks:deck_builder' deck.id %}"
style="text-decoration: none; color: inherit; display: block; height: 100%;">
<div
style="background: #334155; height: 120px; display: flex; align-items: center; justify-content: center; font-size: 3rem;">
🎴
</div>
<div class="tcg-card-body">
<h3 style="margin: 0;">{{ deck.name }}</h3>
<p style="margin: 0.25rem 0 0; color: #94a3b8; font-size: 0.875rem;">{{ deck.game.name }}</p>
<p style="margin: 0.5rem 0 0; font-size: 0.875rem;">{{ deck.cards.count }} cards</p>
</div>
</a>
</div>
{% endfor %}
</div>
{% else %}
<div style="text-align: center; padding: 4rem; background: var(--card-bg); border-radius: 0.5rem; color: #94a3b8;">
<p style="font-size: 1.25rem;">You haven't created any decks yet.</p>
</div>
{% endif %}
</div>
{% endblock %}

View File

@@ -0,0 +1,17 @@
{% extends 'base/layout.html' %}
{% block content %}
<div
style="max-width: 400px; margin: 4rem auto; background: var(--card-bg); padding: 2rem; border-radius: 0.5rem; border: 1px solid var(--border-color);">
<h2 style="margin-top: 0;">Login</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn" style="width: 100%;">Login</button>
</form>
<p style="margin-top: 1rem; text-align: center;">
Don't have an account? <a href="{% url 'users:register' %}" style="color: var(--primary-color);">Register
here</a>.
</p>
</div>
{% endblock %}

View File

@@ -0,0 +1,40 @@
{% extends 'base/layout.html' %}
{% block content %}
<div style="max-width: 1000px; margin: 0 auto;">
<div style="text-align: center; margin-bottom: 3rem;">
<h1 style="font-size: 2.5rem; margin-bottom: 0.5rem; background: linear-gradient(to right, #fbbf24, #d97706); -webkit-background-clip: text; color: transparent;">Community Bounty Board</h1>
<p style="color: #94a3b8; font-size: 1.125rem;">We're looking for these cards! Sell them to us for a bonus.</p>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1.5rem;">
{% for bounty in bounties %}
<div style="background: var(--card-bg); border: 1px solid #d97706; border-radius: 0.75rem; overflow: hidden; position: relative;">
<div style="background: #d97706; color: white; padding: 0.25rem 0.5rem; position: absolute; top: 0; right: 0; font-weight: 700; font-size: 0.75rem; border-bottom-left-radius: 0.5rem;">
WANTED
</div>
<div style="padding: 1.5rem;">
<h3 style="margin: 0 0 0.5rem; font-size: 1.25rem;">{{ bounty.card.name }}</h3>
<p style="margin: 0; color: #94a3b8; font-size: 0.875rem;">{{ bounty.card.set.name }}</p>
<div style="margin-top: 1.5rem; display: flex; justify-content: space-between; align-items: flex-end;">
<div>
<div style="font-size: 0.75rem; color: #94a3b8;">Buying At</div>
<div style="font-size: 1.5rem; font-weight: 800; color: #fbbf24;">${{ bounty.target_price }}</div>
</div>
<div style="text-align: right;">
<div style="font-size: 0.75rem; color: #94a3b8; margin-bottom: 0.25rem;">Qty Wanted: {{ bounty.quantity_wanted }}</div>
<button onclick="alert('Sell Offer Submitted! (Mockup)')" class="btn" style="padding: 0.25rem 0.75rem; font-size: 0.875rem;">I Have This</button>
</div>
</div>
</div>
</div>
{% empty %}
<div style="grid-column: 1 / -1; text-align: center; padding: 4rem; background: var(--card-bg); border-radius: 0.5rem; border: 1px dashed var(--border-color);">
<p style="color: #94a3b8; font-size: 1.25rem;">No active bounties at the moment.</p>
</div>
{% endfor %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,125 @@
{% extends 'base/layout.html' %}
{% block content %}
<div style="display: grid; grid-template-columns: 1fr 2fr; gap: 3rem;">
<!-- Image Column -->
<div>
<div
style="background: #000; border-radius: 0.75rem; overflow: hidden; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5);">
{% if card.image_url %}
<img src="{{ card.image_url }}" alt="{{ card.name }}" style="width: 100%; display: block;">
{% else %}
<div
style="aspect-ratio: 2.5/3.5; display: flex; align-items: center; justify-content: center; color: #64748b; background: #334155;">
No Image</div>
{% endif %}
</div>
<!-- External Links -->
<div style="margin-top: 1.5rem; display: grid; gap: 0.5rem;">
{% if card.tcgplayer_id %}
<a href="https://www.tcgplayer.com/product/{{ card.tcgplayer_id }}" target="_blank"
style="display: block; text-align: center; padding: 0.75rem; background: #27272a; color: white; border-radius: 0.375rem; text-decoration: none; font-size: 0.875rem;">
View on TCGPlayer
</a>
{% endif %}
<a href="https://www.ebay.com/sch/i.html?_nkw={{ card.name|urlencode }}+{{ card.set.name|urlencode }}"
target="_blank"
style="display: block; text-align: center; padding: 0.75rem; background: #27272a; color: white; border-radius: 0.375rem; text-decoration: none; font-size: 0.875rem;">
Search on eBay
</a>
</div>
</div>
<!-- Info Column -->
<div>
<div style="margin-bottom: 2rem;">
<h4 style="margin: 0; color: var(--primary-color);">{{ card.set.game.name }} &bull; {{ card.set.name }}</h4>
<h1 style="margin: 0.5rem 0 0; font-size: 2.5rem;">{{ card.name }}</h1>
<div style="margin-top: 1rem; display: flex; gap: 1rem; color: #94a3b8;">
<span>Rarity: <strong style="color: var(--text-color);">{{ card.rarity }}</strong></span>
<span>Collector #: <strong style="color: var(--text-color);">{{ card.collector_number }}</strong></span>
</div>
</div>
<h3 style="border-bottom: 1px solid var(--border-color); padding-bottom: 0.5rem; margin-bottom: 1rem;">Available
Listings</h3>
<div style="margin-bottom: 1rem;">
<label style="font-size: 0.875rem; color: #94a3b8;">Filter Condition:</label>
<div style="display: flex; gap: 0.5rem; margin-top: 0.25rem;">
<button onclick="filterCondition('ALL')" class="btn" style="padding: 0.25rem 0.75rem; font-size: 0.75rem; background: var(--card-bg); border: 1px solid var(--border-color);">All</button>
<button onclick="filterCondition('NM')" class="btn" style="padding: 0.25rem 0.75rem; font-size: 0.75rem; background: var(--card-bg); border: 1px solid var(--border-color);">NM</button>
<button onclick="filterCondition('LP')" class="btn" style="padding: 0.25rem 0.75rem; font-size: 0.75rem; background: var(--card-bg); border: 1px solid var(--border-color);">LP</button>
<button onclick="filterCondition('MP')" class="btn" style="padding: 0.25rem 0.75rem; font-size: 0.75rem; background: var(--card-bg); border: 1px solid var(--border-color);">MP</button>
<button onclick="filterCondition('HP')" class="btn" style="padding: 0.25rem 0.75rem; font-size: 0.75rem; background: var(--card-bg); border: 1px solid var(--border-color);">HP</button>
</div>
</div>
<div id="listings-container" style="display: flex; flex-direction: column; gap: 1rem;">
{% for listing in listings %}
<div class="listing-item" data-condition="{{ listing.condition }}"
style="display: flex; justify-content: space-between; align-items: center; background: var(--card-bg); padding: 1rem; border-radius: 0.5rem; border: 1px solid var(--border-color);">
<div style="display: flex; gap: 1rem; align-items: center;">
<span style="font-weight: 700; font-size: 1.25rem; width: 3rem; text-align: center;">{{
listing.condition }}</span>
<div>
<div style="font-size: 0.875rem; color: #94a3b8;">Condition</div>
{% if listing.is_foil %}
<span
style="background: linear-gradient(45deg, #f59e0b, #d97706); -webkit-background-clip: text; color: transparent; font-weight: 700; font-size: 0.75rem; text-transform: uppercase;">Foil</span>
{% endif %}
</div>
</div>
<div style="display: flex; align-items: center; gap: 2rem;">
<div style="text-align: right;">
<div style="font-weight: 700; font-size: 1.5rem;">${{ listing.price }}</div>
<div style="font-size: 0.75rem; color: #94a3b8;">{{ listing.quantity }} available</div>
</div>
{% if user.is_authenticated %}
<form action="{% url 'store:add_to_cart' listing.id %}" method="post">
{% csrf_token %}
<button type="submit" class="btn">Add to Cart</button>
</form>
{% else %}
<a href="{% url 'login' %}?next={{ request.path }}" class="btn" style="background: #334155;">Login
to Buy</a>
{% endif %}
</div>
</div>
{% empty %}
<div
style="text-align: center; padding: 2rem; background: var(--card-bg); border-radius: 0.5rem; border: 1px dashed var(--border-color);">
<p style="margin: 0; color: #94a3b8;">No listings currently available for this card.</p>
</div>
{% endfor %}
</div>
<!-- Proxy Service -->
<div style="margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--border-color);">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<h3 style="margin: 0 0 0.5rem;">Playtest Proxy Service</h3>
<p style="margin: 0; color: #94a3b8; font-size: 0.875rem;">Download a high-res proxy for playtesting. Credit offered if you buy later.</p>
</div>
<button onclick="alert('Proxy PDF generated! (Mockup)')" class="btn" style="background: transparent; border: 1px solid var(--border-color);">Download Proxy</button>
</div>
</div>
<script>
function filterCondition(cond) {
const items = document.querySelectorAll('.listing-item');
items.forEach(item => {
if (cond === 'ALL' || item.dataset.condition === cond) {
item.style.display = 'flex';
} else {
item.style.display = 'none';
}
});
}
</script>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,142 @@
{% extends 'base/layout.html' %}
{% load static %}
{% block content %}
<div style="display: grid; grid-template-columns: 250px 1fr; gap: 2rem;">
<!-- Sidebar Filters -->
<aside
style="background: var(--card-bg); padding: 1.5rem; border-radius: 0.5rem; border: 1px solid var(--border-color); height: fit-content;">
<h3 style="margin-top: 0;">Filters</h3>
<form method="get">
<div style="margin-bottom: 1rem;">
<label style="display: block; font-size: 0.875rem; margin-bottom: 0.5rem;">Game</label>
<select name="game"
style="width: 100%; padding: 0.5rem; border-radius: 0.25rem; background: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color);"
onchange="this.form.submit()">
<option value="">All Games</option>
{% for game in games %}
<option value="{{ game.slug }}" {% if current_game|slugify == game.slug %}selected{% endif %}>
{{game.name}}
</option>
{% endfor %}
</select>
</div>
{% if current_game %}
<div style="margin-bottom: 1rem;">
<label style="display: block; font-size: 0.875rem; margin-bottom: 0.5rem;">Set</label>
<select name="set"
style="width: 100%; padding: 0.5rem; border-radius: 0.25rem; background: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color);">
<option value="">All Sets</option>
{% for set in sets %}
<option value="{{ set.id }}" {% if request.GET.set|add:"0" == set.id %}selected{% endif %}>{{ set.name
}}</option>
{% endfor %}
</select>
</div>
{% endif %}
<div style="margin-bottom: 1rem;">
<label style="display: block; font-size: 0.875rem; margin-bottom: 0.5rem;">Search</label>
<input type="text" name="q" value="{{ search_query|default:'' }}" placeholder="Card name..."
style="width: 90%; padding: 0.5rem; border-radius: 0.25rem; background: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color);">
</div>
<button type="submit" class="btn" style="width: 100%;">Apply Filters</button>
<a href="{% url 'store:card_list' %}"
style="display: block; text-align: center; margin-top: 1rem; color: #94a3b8; font-size: 0.875rem; text-decoration: none;">Clear
Filters</a>
</form>
</aside>
<!-- Card Grid -->
<div>
<h2 style="margin-top: 0;">Browse Cards</h2>
<div class="card-grid">
{% for card in page_obj %}
<div class="tcg-card">
<a href="{% url 'store:card_detail' card.id %}" style="text-decoration: none; color: inherit;">
<div style="aspect-ratio: 2.5/3.5; background: #000; position: relative;">
<!-- Placeholder or Real Image -->
{% if card.image_url %}
<img src="{{ card.image_url }}" alt="{{ card.name }}"
style="width: 100%; height: 100%; object-fit: cover;">
{% else %}
<div
style="display: flex; align-items: center; justify-content: center; height: 100%; color: #64748b; background: #334155;">
No Image</div>
{% endif %}
</div>
<div class="tcg-card-body">
<h4
style="margin: 0 0 0.5rem; font-size: 1rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
{{ card.name }}</h4>
<div
style="display: flex; justify-content: space-between; align-items: center; font-size: 0.875rem; color: #94a3b8;">
<span>{{ card.set.code|default:card.set.game.name }}</span>
<span>{{ card.rarity }}</span>
</div>
<div
style="margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center;">
{% with card.listings.first as cheapest %}
{% if cheapest %}
<span style="font-weight: 700; color: var(--text-color);">From ${{ cheapest.price }}</span>
{% else %}
<span style="color: #64748b;">Out of Stock</span>
{% endif %}
{% endwith %}
<span id="stock-{{ card.id }}" class="stock-counter" data-card-id="{{ card.id }}" style="font-size: 0.75rem; color: #94a3b8; margin-left: auto;">...</span>
</div>
</div>
</a>
</div>
{% empty %}
<p>No cards found matching your criteria.</p>
{% endfor %}
</div>
<!-- Pagination -->
{% if page_obj.has_other_pages %}
<div style="margin-top: 2rem; display: flex; justify-content: center; gap: 0.5rem;">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}&game={{ current_game }}&q={{ search_query }}" class="btn"
style="padding: 0.25rem 0.5rem; font-size: 0.875rem;">Prev</a>
{% endif %}
<span
style="padding: 0.25rem 0.75rem; background: var(--card-bg); border-radius: 0.25rem; border: 1px solid var(--border-color);">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}&game={{ current_game }}&q={{ search_query }}" class="btn"
style="padding: 0.25rem 0.5rem; font-size: 0.875rem;">Next</a>
{% endif %}
</div>
{% endif %}
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
const stockCounters = document.querySelectorAll('.stock-counter');
stockCounters.forEach(counter => {
const cardId = counter.getAttribute('data-card-id');
fetch(`/store/api/stock/${cardId}/`)
.then(response => response.json())
.then(data => {
if (data.total_stock > 0) {
counter.textContent = `${data.total_stock} in stock`;
counter.style.color = '#10b981'; // green
} else {
counter.textContent = 'Out of Stock';
counter.style.color = '#ef4444'; // red
}
})
.catch(err => {
counter.textContent = 'Stock unknown';
});
});
});
</script>
{% endblock %}

80
templates/store/cart.html Normal file
View File

@@ -0,0 +1,80 @@
{% extends 'base/layout.html' %}
{% block content %}
<div style="max-width: 800px; margin: 0 auto;">
<h1 style="margin-bottom: 2rem;">Shopping Cart</h1>
{% if cart and cart.items.count > 0 %}
<div
style="background: var(--card-bg); border-radius: 0.5rem; border: 1px solid var(--border-color); overflow: hidden;">
{% for item in cart.items.all %}
<div
style="display: flex; justify-content: space-between; align-items: center; padding: 1.5rem; border-bottom: 1px solid var(--border-color);">
<div style="display: flex; gap: 1.5rem; align-items: center;">
{% if item.listing %}
{% if item.listing.card.image_url %}
<img src="{{ item.listing.card.image_url }}"
style="width: 50px; height: 70px; object-fit: cover; border-radius: 4px;">
{% endif %}
<div>
<h3 style="margin: 0; font-size: 1.125rem;">{{ item.listing.card.name }}</h3>
<p style="margin: 0.25rem 0 0; color: #94a3b8; font-size: 0.875rem;">
{{ item.listing.get_condition_display }}
{% if item.listing.is_foil %}&bull; Foil{% endif %}
</p>
</div>
{% else %}
{% if item.pack_listing.image_url %}
<img src="{{ item.pack_listing.image_url }}" style="width: 50px; height: 70px; object-fit: cover; border-radius: 4px;">
{% else %}
<div style="width: 50px; height: 70px; background: #6366f1; border-radius: 4px; display: flex; align-items: center; justify-content: center; font-size: 1.5rem;">📦</div>
{% endif %}
<div>
<h3 style="margin: 0; font-size: 1.125rem;">{{ item.pack_listing.name }}</h3>
<p style="margin: 0.25rem 0 0; color: #94a3b8; font-size: 0.875rem;">Booster Pack</p>
</div>
{% endif %}
</div>
<div style="display: flex; align-items: center; gap: 2rem;">
<div style="text-align: right;">
<div style="font-weight: 600;">{{ item.quantity }} x ${% if item.listing %}{{ item.listing.price }}{% else %}{{ item.pack_listing.price }}{% endif %}</div>
</div>
<div style="font-weight: 700; font-size: 1.25rem;">
${{ item.total_price }}
</div>
<a href="{% url 'store:remove_from_cart' item.id %}"
style="color: #ef4444; text-decoration: none; font-size: 1.25rem;">&times;</a>
</div>
</div>
{% endfor %}
<div style="padding: 1rem 1.5rem; background: rgba(0,0,0,0.1); border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center;">
<div style="display: flex; align-items: center; gap: 0.75rem;">
<a href="{% url 'store:toggle_insurance' %}" style="text-decoration: none; display: flex; align-items: center; gap: 0.5rem; color: var(--text-color);">
<div style="width: 20px; height: 20px; border: 2px solid var(--border-color); border-radius: 4px; display: flex; align-items: center; justify-content: center; background: {% if cart.insurance %}var(--primary-color, #3b82f6){% else %}transparent{% endif %};">
{% if cart.insurance %}<span style="color: white; font-size: 14px;"></span>{% endif %}
</div>
<span><strong>Add Shipping Insurance</strong> (Protects against damage/loss)</span>
</a>
</div>
<span>+$5.00</span>
</div>
<div
style="padding: 1.5rem; background: rgba(0,0,0,0.2); display: flex; justify-content: space-between; align-items: center;">
<span style="font-size: 1.25rem; font-weight: 600;">Total</span>
<span style="font-size: 1.5rem; font-weight: 800;">${{ cart.total_price }}</span>
</div>
</div>
<div style="margin-top: 2rem; text-align: right;">
<a href="{% url 'store:checkout' %}" class="btn" style="padding: 1rem 2rem; font-size: 1.125rem;">Proceed to Checkout</a>
</div>
{% else %}
<div style="text-align: center; padding: 4rem; background: var(--card-bg); border-radius: 0.5rem; color: #94a3b8;">
<p style="font-size: 1.25rem; margin-bottom: 1.5rem;">Your cart is empty.</p>
<a href="{% url 'store:card_list' %}" class="btn">Browse Cards</a>
</div>
{% endif %}
</div>
{% endblock %}

View File

@@ -0,0 +1,60 @@
{% extends 'base/layout.html' %}
{% block content %}
<div style="max-width: 800px; margin: 0 auto;">
<h2 style="margin-bottom: 1.5rem;">Mass Deck Buyer</h2>
<p style="margin-bottom: 2rem; color: #94a3b8;">Paste your deck list below to instantly find the cheapest printings for every card.</p>
{% if preview %}
<div style="background: var(--card-bg); padding: 1.5rem; border-radius: 0.5rem; border: 1px solid var(--border-color); margin-bottom: 2rem;">
<h3 style="margin-top: 0;">Review Purchase</h3>
<p style="font-size: 1.25rem; font-weight: bold; margin-bottom: 1rem;">Total Estimated Cost: <span style="color: #10b981;">${{ total_cost }}</span></p>
<h4 style="border-bottom: 1px solid var(--border-color); padding-bottom: 0.5rem;">Found Items</h4>
<ul style="list-style: none; padding: 0; margin-bottom: 1.5rem;">
{% for item in found_items %}
<li style="display: flex; justify-content: space-between; padding: 0.5rem 0; border-bottom: 1px solid #334155;">
<span>{{ item.quantity }}x <strong>{{ item.card_name }}</strong> <small style="color: #94a3b8;">({{ item.listing.get_condition_display }})</small></span>
<span>${{ item.total }}</span>
</li>
{% endfor %}
</ul>
{% if missing_items %}
<h4 style="border-bottom: 1px solid var(--border-color); padding-bottom: 0.5rem; color: #ef4444;">Missing / Out of Stock</h4>
<ul style="list-style: none; padding: 0; margin-bottom: 1.5rem;">
{% for item in missing_items %}
<li style="padding: 0.5rem 0; color: #f87171;">
{{ item.quantity }}x {{ item.name }}
</li>
{% endfor %}
</ul>
{% endif %}
<form method="post" style="display: flex; gap: 1rem;">
{% csrf_token %}
<input type="hidden" name="action" value="add_to_cart">
<input type="hidden" name="deck_text" value="{{ deck_text }}">
<button type="submit" class="btn" style="flex: 1;">Confirm & Add to Cart</button>
<a href="{% url 'store:deck_buyer' %}" class="btn" style="background: var(--bg-color); border: 1px solid var(--border-color); color: var(--text-color); flex: 1; text-align: center; text-decoration: none;">Cancel</a>
</form>
</div>
{% else %}
<form method="post" style="display: flex; flex-direction: column; gap: 1rem;">
{% csrf_token %}
<input type="hidden" name="action" value="preview">
<div style="background: var(--bg-color); padding: 1rem; border: 1px solid var(--border-color); border-radius: 0.5rem;">
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
<input type="checkbox" name="ignore_owned" id="ignore_owned">
<span><strong>Complete My Deck:</strong> Exclude cards I already own from my purchase.</span>
</label>
</div>
<label for="deck_text" style="font-weight: bold;">Deck List</label>
<textarea name="deck_text" id="deck_text" rows="15" placeholder="4 Lightning Bolt&#10;2 Counterspell&#10;1 Sol Ring" style="padding: 1rem; border-radius: 0.5rem; background: var(--card-bg); color: var(--text-color); border: 1px solid var(--border-color); font-family: monospace;"></textarea>
<button type="submit" class="btn">Find Cards</button>
</form>
{% endif %}
</div>
{% endblock %}

View File

@@ -0,0 +1,30 @@
{% extends 'base/layout.html' %}
{% block content %}
<div style="max-width: 1200px; margin: 0 auto;">
<h1 style="margin-bottom: 2rem;">My Packs</h1>
{% if packs %}
<div class="card-grid">
{% for pack in packs %}
<div class="tcg-card">
<div style="aspect-ratio: 2.5/3.5; background: linear-gradient(135deg, #4f46e5, #0ea5e9); position: relative; display: flex; align-items: center; justify-content: center;">
<div style="text-align: center; color: white;">
<div style="font-size: 3rem;">🎁</div>
<div>{{ pack.listing.name }}</div>
</div>
</div>
<div class="tcg-card-body" style="text-align: center;">
<h4 style="margin: 0 0 0.5rem; font-size: 1rem;">{{ pack.listing.name }}</h4>
<a href="{% url 'store:open_pack' pack.id %}" class="btn" style="width: 100%; display: block; margin-top: 1rem;">Open Pack</a>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div style="text-align: center; padding: 4rem; background: var(--card-bg); border-radius: 0.5rem;">
<p>You don't have any sealed packs.</p>
<a href="{% url 'store:pack_list' %}" class="btn">Buy Packs</a>
</div>
{% endif %}
</div>
{% endblock %}

View File

@@ -0,0 +1,125 @@
{% extends 'base/layout.html' %}
{% load static %}
{% block content %}
<div style="max-width: 1000px; margin: 0 auto; text-align: center; min-height: 600px;">
<h1 id="pack-title">{{ pack.listing.name }}</h1>
<div id="pack-container" style="margin: 4rem auto; perspective: 1000px; width: 300px; height: 420px; cursor: pointer;">
<div id="pack-wrapper" style="width: 100%; height: 100%; position: relative; transform-style: preserve-3d; transition: transform 0.6s;">
<div class="pack-face" style="position: absolute; width: 100%; height: 100%; backface-visibility: hidden; background: linear-gradient(135deg, #4f46e5, #0ea5e9); border-radius: 1rem; display: flex; align-items: center; justify-content: center; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);">
<div style="font-size: 5rem;">🎁</div>
<div style="position: absolute; bottom: 2rem; color: white; font-weight: bold;">Click to Open</div>
</div>
</div>
</div>
<div id="cards-container" style="display: none; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-top: 2rem;">
<!-- Cards injected here -->
</div>
<div id="actions" style="margin-top: 2rem; display: none;">
<a href="{% url 'store:my_packs' %}" class="btn">Back to My Packs</a>
</div>
</div>
<style>
@keyframes shake {
0% { transform: translate(1px, 1px) rotate(0deg); }
10% { transform: translate(-1px, -2px) rotate(-1deg); }
20% { transform: translate(-3px, 0px) rotate(1deg); }
30% { transform: translate(3px, 2px) rotate(0deg); }
40% { transform: translate(1px, -1px) rotate(1deg); }
50% { transform: translate(-1px, 2px) rotate(-1deg); }
60% { transform: translate(-3px, 1px) rotate(0deg); }
70% { transform: translate(3px, 1px) rotate(-1deg); }
80% { transform: translate(-1px, -1px) rotate(1deg); }
90% { transform: translate(1px, 2px) rotate(0deg); }
100% { transform: translate(1px, -2px) rotate(-1deg); }
}
.shaking {
animation: shake 0.5s;
animation-iteration-count: infinite;
}
.card-reveal {
opacity: 0;
transform: translateY(20px);
animation: fadeInUp 0.5s forwards;
}
@keyframes fadeInUp {
to { opacity: 1; transform: translateY(0); }
}
</style>
<script>
const packWrapper = document.getElementById('pack-wrapper');
const packContainer = document.getElementById('pack-container');
const cardsContainer = document.getElementById('cards-container');
const actions = document.getElementById('actions');
const packTitle = document.getElementById('pack-title');
packContainer.addEventListener('click', function() {
if (packContainer.classList.contains('opened')) return;
packWrapper.classList.add('shaking');
// Use fetch with CSRF token
const csrftoken = document.cookie.split('; ').find(row => row.startsWith('csrftoken='))?.split('=')[1];
fetch(window.location.href, {
method: 'POST',
headers: {
'X-CSRFToken': '{{ csrf_token }}',
'Content-Type': 'application/json'
},
body: JSON.stringify({})
})
.then(response => response.json())
.then(data => {
setTimeout(() => {
packWrapper.classList.remove('shaking');
packWrapper.style.transform = 'scale(0) rotate(720deg)';
packWrapper.style.opacity = '0';
setTimeout(() => {
packContainer.style.display = 'none';
cardsContainer.style.display = 'grid';
actions.style.display = 'block';
packTitle.textContent = "New Cards!";
data.cards.forEach((card, index) => {
const cardEl = document.createElement('div');
cardEl.className = 'tcg-card card-reveal';
cardEl.style.animationDelay = `${index * 0.2}s`;
let imgHtml = '';
if (card.image_url) {
imgHtml = `<img src="${card.image_url}" style="width: 100%; height: auto;">`;
} else {
imgHtml = `<div style="aspect-ratio: 2.5/3.5; background: #000; display: flex; align-items: center; justify-content: center; color: white;">${card.name}</div>`;
}
cardEl.innerHTML = `
<div style="aspect-ratio: 2.5/3.5; background: #000; position: relative;">
${imgHtml}
</div>
<div class="tcg-card-body">
<h4>${card.name}</h4>
<p>${card.set} - ${card.rarity}</p>
</div>
`;
cardsContainer.appendChild(cardEl);
});
}, 500);
}, 1000);
})
.catch(err => {
console.error(err);
packWrapper.classList.remove('shaking');
alert('Error opening pack');
});
packContainer.classList.add('opened');
});
</script>
{% endblock %}

View File

@@ -0,0 +1,74 @@
{% extends 'base/layout.html' %}
{% block content %}
<div class="container" style="max-width: 800px; margin: 0 auto; padding: 2rem;">
<div style="margin-bottom: 2rem; display: flex; justify-content: space-between; align-items: center;">
<h1 style="margin: 0;">Order #{{ order.id }}</h1>
<a href="{% url 'users:profile' %}" class="btn" style="background-color: var(--card-bg); border: 1px solid var(--border-color); color: var(--text-color);">Back to Profile</a>
</div>
<div style="background: var(--card-bg); padding: 2rem; border-radius: 0.5rem; border: 1px solid var(--border-color); margin-bottom: 2rem;">
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 2rem;">
<div>
<p style="color: #94a3b8; font-size: 0.875rem; margin-bottom: 0.5rem;">Date Placed</p>
<p style="font-weight: 600;">{{ order.created_at|date:"F j, Y, P" }}</p>
</div>
<div>
<p style="color: #94a3b8; font-size: 0.875rem; margin-bottom: 0.5rem;">Status</p>
<span style="display: inline-block; padding: 0.25rem 0.75rem; border-radius: 999px; background: #334155; font-size: 0.875rem; font-weight: 600;">
{{ order.get_status_display }}
</span>
</div>
<div>
<p style="color: #94a3b8; font-size: 0.875rem; margin-bottom: 0.5rem;">Total Amount</p>
<p style="font-weight: 600; font-size: 1.25rem;">${{ order.total_price }}</p>
</div>
</div>
{% if order.insurance_purchased %}
<div style="margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px solid var(--border-color);">
<p style="display: flex; align-items: center; gap: 0.5rem; color: #10b981;">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
Shipping Insurance Included
</p>
</div>
{% endif %}
</div>
<h3 style="margin-bottom: 1.5rem;">Order Items</h3>
<div style="display: grid; gap: 1rem;">
{% for item in order.items.all %}
<div style="background: var(--card-bg); padding: 1.5rem; border-radius: 0.5rem; border: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center;">
<div style="display: flex; align-items: center; gap: 1rem;">
{% if item.listing and item.listing.card.image_url %}
<img src="{{ item.listing.card.image_url }}" alt="{{ item.listing.card.name }}" style="width: 50px; border-radius: 4px;">
{% elif item.pack_listing and item.pack_listing.image_url %}
<img src="{{ item.pack_listing.image_url }}" alt="{{ item.pack_listing.name }}" style="width: 50px; border-radius: 4px;">
{% else %}
<div style="width: 50px; height: 70px; background: #334155; border-radius: 4px;"></div>
{% endif %}
<div>
{% if item.listing %}
<h4 style="margin: 0; font-size: 1rem;">{{ item.listing.card.name }}</h4>
<p style="margin: 0.25rem 0 0; color: #94a3b8; font-size: 0.875rem;">
{{ item.listing.card.set.name }} • {{ item.listing.get_condition_display }} • {% if item.listing.is_foil %}Foil{% else %}Non-Foil{% endif %}
</p>
{% elif item.pack_listing %}
<h4 style="margin: 0; font-size: 1rem;">{{ item.pack_listing.name }}</h4>
<p style="margin: 0.25rem 0 0; color: #94a3b8; font-size: 0.875rem;">Booster Pack</p>
{% else %}
<h4 style="margin: 0; font-size: 1rem;">Unknown Item</h4>
{% endif %}
</div>
</div>
<div style="text-align: right;">
<p style="margin: 0; font-weight: 600;">${{ item.price_at_purchase }}</p>
<p style="margin: 0.25rem 0 0; color: #94a3b8; font-size: 0.875rem;">Qty: {{ item.quantity }}</p>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,30 @@
{% extends 'base/layout.html' %}
{% block content %}
<div style="max-width: 1200px; margin: 0 auto;">
<h1 style="margin-bottom: 2rem;">Booster Packs</h1>
<div class="card-grid">
{% for pack in packs %}
<div class="tcg-card">
<div style="aspect-ratio: 2.5/3.5; background: #334155; position: relative; display: flex; align-items: center; justify-content: center;">
{% if pack.image_url %}
<img src="{{ pack.image_url }}" alt="{{ pack.name }}" style="width: 100%; height: 100%; object-fit: cover;">
{% else %}
<div style="text-align: center; padding: 1rem;">
<div style="font-size: 3rem;">📦</div>
<div>{{ pack.game.name }}</div>
</div>
{% endif %}
</div>
<div class="tcg-card-body">
<h4 style="margin: 0 0 0.5rem; font-size: 1rem;">{{ pack.name }}</h4>
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 1rem;">
<span style="font-weight: 700;">${{ pack.price }}</span>
<a href="{% url 'store:add_pack_to_cart' pack.id %}" class="btn" style="padding: 0.25rem 0.75rem; font-size: 0.875rem;">Add to Cart</a>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,27 @@
{% extends 'base/layout.html' %}
{% block content %}
<div class="container" style="max-width: 600px; margin: 0 auto; padding-top: 2rem;">
<div style="background: var(--card-bg); padding: 2rem; border-radius: 0.5rem; border: 1px solid var(--border-color);">
<h2 style="margin-top: 0;">Add Address</h2>
<form method="post">
{% csrf_token %}
<div style="display: grid; gap: 1rem;">
{% for field in form %}
<div style="display: flex; flex-direction: column; gap: 0.5rem;">
<label for="{{ field.id_for_label }}" style="font-weight: 500;">{{ field.label }}</label>
{{ field }}
{% if field.errors %}
<span style="color: #ef4444; font-size: 0.875rem;">{{ field.errors.0 }}</span>
{% endif %}
</div>
{% endfor %}
</div>
<div style="margin-top: 2rem; display: flex; gap: 1rem;">
<button type="submit" class="btn">Save Address</button>
<a href="{% url 'users:profile' %}" class="btn" style="background: transparent; border: 1px solid var(--border-color);">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,30 @@
{% extends 'base/layout.html' %}
{% block content %}
<div class="container" style="max-width: 600px; margin: 0 auto; padding-top: 2rem;">
<div style="background: var(--card-bg); padding: 2rem; border-radius: 0.5rem; border: 1px solid var(--border-color);">
<h2 style="margin-top: 0;">Add Payment Method</h2>
<div style="background: #3b82f620; border: 1px solid #3b82f6; padding: 1rem; border-radius: 0.5rem; margin-bottom: 1.5rem;">
<p style="margin: 0; color: #60a5fa; font-size: 0.875rem;"><strong>Note:</strong> This is a demo. Do not enter real credit card information.</p>
</div>
<form method="post">
{% csrf_token %}
<div style="display: grid; gap: 1rem;">
{% for field in form %}
<div style="display: flex; flex-direction: column; gap: 0.5rem;">
<label for="{{ field.id_for_label }}" style="font-weight: 500;">{{ field.label }}</label>
{{ field }}
{% if field.errors %}
<span style="color: #ef4444; font-size: 0.875rem;">{{ field.errors.0 }}</span>
{% endif %}
</div>
{% endfor %}
</div>
<div style="margin-top: 2rem; display: flex; gap: 1rem;">
<button type="submit" class="btn">Save Payment Method</button>
<a href="{% url 'users:profile' %}" class="btn" style="background: transparent; border: 1px solid var(--border-color);">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,141 @@
{% extends 'base/layout.html' %}
{% block content %}
<div class="card-grid" style="grid-template-columns: 1fr 2fr; gap: 2rem; align-items: start;">
<div style="display: flex; flex-direction: column; gap: 2rem;">
<!-- User Info Card -->
<div style="background: var(--card-bg); padding: 2rem; border-radius: 0.5rem; border: 1px solid var(--border-color);">
<h2>{{ user.username }}</h2>
<span
style="background: {% if user.profile.is_pro %}{{ 'linear-gradient(to right, #f59e0b, #d97706)' }}{% else %}#475569{% endif %};
color: white; padding: 0.25rem 0.75rem; border-radius: 999px; font-size: 0.875rem; font-weight: 600;">
{% if user.profile.is_pro %}PRO MEMBER{% else %}BASIC MEMBER{% endif %}
</span>
<div style="margin-top: 2rem;">
<p><strong>Email:</strong> {{ user.email }}</p>
</div>
<div style="margin-top: 2rem; border-top: 1px solid var(--border-color); padding-top: 1rem;">
<form method="post">
{% csrf_token %}
<p><strong>Theme Preference:</strong></p>
<div style="display: flex; gap: 0.5rem;">
{{ form.theme_preference }}
<button type="submit" class="btn" style="padding: 0.25rem 0.75rem;">Save</button>
</div>
</form>
</div>
{% if not user.profile.is_pro %}
<div style="margin-top: 2rem; padding-top: 2rem; border-top: 1px solid var(--border-color);">
<h3>Upgrade to Pro</h3>
<p>Get access to Deck Builder and exclusive analytics.</p>
<form action="{% url 'users:upgrade_account' %}" method="post">
{% csrf_token %}
<button type="submit" class="btn" style="width: 100%;">Upgrade Now ($5/mo)</button>
</form>
</div>
{% endif %}
</div>
<!-- Addresses Card -->
<div style="background: var(--card-bg); padding: 2rem; border-radius: 0.5rem; border: 1px solid var(--border-color);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<h3 style="margin: 0;">Addresses</h3>
<a href="{% url 'users:add_address' %}" class="btn" style="font-size: 0.75rem; padding: 0.25rem 0.5rem;">+ Add</a>
</div>
{% if addresses %}
<div style="display: flex; flex-direction: column; gap: 1rem;">
{% for address in addresses %}
<div style="border: 1px solid var(--border-color); padding: 0.75rem; border-radius: 0.375rem; position: relative;">
<div style="display: flex; justify-content: space-between;">
<span style="font-weight: 600; font-size: 0.875rem;">{{ address.get_address_type_display }} {% if address.is_default %}(Default){% endif %}</span>
<form action="{% url 'users:delete_address' address.pk %}" method="post" onsubmit="return confirm('Are you sure?');">
{% csrf_token %}
<button type="submit" style="background: none; border: none; color: #ef4444; cursor: pointer;">&times;</button>
</form>
</div>
<p style="margin: 0.25rem 0; font-size: 0.875rem;">{{ address.street }}<br>{{ address.city }}, {{ address.state }} {{ address.zip_code }}</p>
</div>
{% endfor %}
</div>
{% else %}
<p style="color: #94a3b8; font-size: 0.875rem;">No addresses saved.</p>
{% endif %}
</div>
<!-- Payment Methods Card -->
<div style="background: var(--card-bg); padding: 2rem; border-radius: 0.5rem; border: 1px solid var(--border-color);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<h3 style="margin: 0;">Payment Methods</h3>
<a href="{% url 'users:add_payment_method' %}" class="btn" style="font-size: 0.75rem; padding: 0.25rem 0.5rem;">+ Add</a>
</div>
{% if payment_methods %}
<div style="display: flex; flex-direction: column; gap: 1rem;">
{% for pm in payment_methods %}
<div style="border: 1px solid var(--border-color); padding: 0.75rem; border-radius: 0.375rem; position: relative;">
<div style="display: flex; justify-content: space-between;">
<span style="font-weight: 600; font-size: 0.875rem;">{{ pm.brand }} •••• {{ pm.last4 }}</span>
<form action="{% url 'users:delete_payment_method' pm.pk %}" method="post" onsubmit="return confirm('Are you sure?');">
{% csrf_token %}
<button type="submit" style="background: none; border: none; color: #ef4444; cursor: pointer;">&times;</button>
</form>
</div>
<p style="margin: 0.25rem 0; font-size: 0.875rem; color: #94a3b8;">Expires {{ pm.exp_month }}/{{ pm.exp_year }}</p>
{% if pm.is_default %}<span style="font-size: 0.75rem; color: var(--primary-color);">Default</span>{% endif %}
</div>
{% endfor %}
</div>
{% else %}
<p style="color: #94a3b8; font-size: 0.875rem;">No payment methods saved.</p>
{% endif %}
</div>
</div>
<div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<h2 style="margin: 0;">Recent Orders</h2>
<form method="get" style="display: flex; gap: 0.5rem;">
<input type="date" name="date" value="{{ date_query }}" style="background: var(--input-bg); border: 1px solid var(--border-color); color: var(--text-color); padding: 0.25rem 0.5rem; border-radius: 0.25rem;">
<button type="submit" class="btn" style="padding: 0.25rem 0.75rem;">Filter</button>
{% if date_query %}
<a href="{% url 'users:profile' %}" class="btn" style="background: var(--card-bg); border: 1px solid var(--border-color); padding: 0.25rem 0.75rem;">Clear</a>
{% endif %}
</form>
</div>
{% if orders %}
<div style="display: grid; gap: 1rem;">
{% for order in orders %}
<a href="{% url 'store:order_detail' order.id %}" style="text-decoration: none; color: inherit;">
<div
style="background: var(--card-bg); padding: 1.5rem; border-radius: 0.5rem; border: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; transition: transform 0.2s;"
onmouseover="this.style.transform='translateY(-2px)'; this.style.borderColor='var(--accent-color)'"
onmouseout="this.style.transform='translateY(0)'; this.style.borderColor='var(--border-color)'">
<div>
<h4 style="margin: 0;">Order #{{ order.id }}</h4>
<p style="margin: 0.5rem 0 0; color: #94a3b8; font-size: 0.875rem;">{{ order.created_at|date }}</p>
</div>
<div style="text-align: right;">
<span style="display: block; font-weight: 600; font-size: 1.125rem;">${{ order.total_price }}</span>
<span
style="display: inline-block; padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-size: 0.75rem; background: #334155; margin-top: 0.5rem;">
{{ order.get_status_display }}
</span>
</div>
</div>
</a>
{% endfor %}
</div>
{% else %}
<div
style="text-align: center; padding: 4rem; background: var(--card-bg); border-radius: 0.5rem; color: #94a3b8;">
<p>No orders found.</p>
<a href="{% url 'store:card_list' %}" class="btn" style="margin-top: 1rem;">Start Shopping</a>
</div>
{% endif %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,16 @@
{% extends 'base/layout.html' %}
{% block content %}
<div
style="max-width: 400px; margin: 4rem auto; background: var(--card-bg); padding: 2rem; border-radius: 0.5rem; border: 1px solid var(--border-color);">
<h2 style="margin-top: 0;">Create Account</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn" style="width: 100%;">Register</button>
</form>
<p style="margin-top: 1rem; text-align: center;">
Already have an account? <a href="{% url 'login' %}" style="color: var(--primary-color);">Login here</a>.
</p>
</div>
{% endblock %}

View File

@@ -0,0 +1,72 @@
{% extends 'base/layout.html' %}
{% block content %}
<div class="container" style="max-width: 1000px; margin: 0 auto; padding-top: 2rem;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
<h1>My Card Vault</h1>
<div style="background: var(--card-bg); padding: 0.5rem 1rem; border-radius: 0.5rem; border: 1px solid var(--border-color);">
Total Cards: <strong>{{ total_cards }}</strong>
</div>
</div>
<!-- Filters -->
<div style="margin-bottom: 2rem; background: var(--card-bg); padding: 1rem; border-radius: 0.5rem; border: 1px solid var(--border-color);">
<form method="get" style="display: flex; gap: 1rem; flex-wrap: wrap; align-items: center;">
<div>
<label for="set" style="margin-right: 0.5rem; font-size: 0.875rem;">Set:</label>
<select name="set" id="set" style="padding: 0.25rem 0.5rem; border-radius: 0.25rem; background: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color);">
<option value="">All Sets</option>
{% for set in available_sets %}
<option value="{{ set.id }}" {% if current_set == set.id %}selected{% endif %}>{{ set.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="rarity" style="margin-right: 0.5rem; font-size: 0.875rem;">Rarity:</label>
<select name="rarity" id="rarity" style="padding: 0.25rem 0.5rem; border-radius: 0.25rem; background: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color);">
<option value="">All Rarities</option>
{% for rarity in available_rarities %}
<option value="{{ rarity }}" {% if current_rarity == rarity %}selected{% endif %}>{{ rarity|title }}</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn" style="padding: 0.25rem 0.75rem;">Filter</button>
<a href="{% url 'users:vault' %}" class="btn" style="background: transparent; border: 1px solid var(--border-color); padding: 0.25rem 0.75rem;">Clear</a>
</form>
</div>
{% if vault_items %}
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1.5rem;">
{% for item in vault_items %}
<div style="background: var(--card-bg); border: 1px solid var(--border-color); border-radius: 0.5rem; overflow: hidden; display: flex; flex-direction: column;">
<div style="position: relative; aspect-ratio: 2.5/3.5;">
{% if item.card.image_url %}
<img src="{{ item.card.image_url }}" alt="{{ item.card.name }}" style="width: 100%; height: 100%; object-fit: cover;">
{% else %}
<div style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: #1e293b; color: #94a3b8;">No Image</div>
{% endif %}
<div style="position: absolute; bottom: 0; right: 0; background: rgba(0,0,0,0.8); color: white; padding: 0.25rem 0.5rem; border-top-left-radius: 0.5rem;">
x{{ item.quantity }}
</div>
</div>
<div style="padding: 1rem; flex-grow: 1; display: flex; flex-direction: column; justify-content: space-between;">
<div>
<h3 style="margin: 0; font-size: 1rem;">{{ item.card.name }}</h3>
<p style="color: #94a3b8; font-size: 0.875rem; margin: 0.25rem 0;">{{ item.card.set.name }}</p>
<p style="color: #94a3b8; font-size: 0.75rem; margin: 0;">{{ item.card.rarity|title }}</p>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div style="text-align: center; padding: 4rem; background: var(--card-bg); border-radius: 0.5rem; border: 1px solid var(--border-color);">
<p style="font-size: 1.25rem; color: #94a3b8; margin-bottom: 1rem;">No cards found.</p>
<div style="display: flex; gap: 1rem; justify-content: center; margin-top: 2rem;">
<a href="{% url 'store:card_list' %}" class="btn">Browse Cards</a>
<a href="{% url 'users:vault' %}" class="btn" style="background: transparent; border: 1px solid var(--border-color);">Clear Filters</a>
</div>
</div>
{% endif %}
</div>
{% endblock %}