MASSIVE UPDATE:

bounty board feature

buyers to see bounty boards

seller profile page (like have theme chooser)

Have the game and set name be filters.

Add cards to vault manually

update card inventory add to have the autocomplete for the card  -

store analytics, clicks, views, link to store (url/QR code)

bulk item inventory creation --

Make the banner feature flag driven so I can have a beta site setup like the primary site

don't use primary key values in urls - update to use uuid4 values

site analytics. tianji is being sent

item potent on the mtg and lorcana populate scripts

Card item images for specific listings

check that when you buy a card it is in the vault

Buys should be able to search on store inventories

More pie charts for the seller!

post bounty board is slow to load

seller reviews/ratings - show a historgram - need a way for someone to rate

Report a seller feature for buyer to report

Make sure the stlying is consistent based on the theme choosen

smart minimum order quantity and shipping amounts (defined by the store itself)

put virtual packs behind a feature flag like bounty board

proxy service feature flag

Terms of Service

new description for TCGKof

store SSN, ITIN, and EIN

optomize for SEO
This commit is contained in:
2026-01-23 12:28:20 -06:00
parent c43603bfb5
commit 9040021d1b
80 changed files with 6938 additions and 592 deletions

View File

@@ -4,246 +4,98 @@
<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>
<title>{% block title %}TCGKof - Premium TCG Store{% endblock %}</title>
<meta name="description" content="{% block meta_description %}TCGKof is the premier marketplace for trading card games. Buy and sell Magic: The Gathering, Lorcana, and more.{% endblock %}">
<meta name="keywords" content="{% block meta_keywords %}TCG, Magic: The Gathering, Lorcana, cards, marketplace, buy cards, sell cards{% endblock %}">
<link rel="canonical" href="{% block canonical_url %}{{ request.build_absolute_uri }}{% endblock %}">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="{% block og_type %}website{% endblock %}">
<meta property="og:url" content="{{ request.build_absolute_uri }}">
<meta property="og:title" content="{% block og_title %}{{ self.title }}{% endblock %}">
<meta property="og:description" content="{% block og_description %}TCGKof is the premier marketplace for trading card games. Buy and sell Magic: The Gathering, Lorcana, and more.{% endblock %}">
<meta property="og:image" content="{% block og_image %}{{ request.scheme }}://{{ request.get_host }}{% static 'img/og-default.jpg' %}{% endblock %}">
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:url" content="{{ request.build_absolute_uri }}">
<meta property="twitter:title" content="{% block twitter_title %}{{ self.title }}{% endblock %}">
<meta property="twitter:description" content="{% block twitter_description %}TCGKof is the premier marketplace for trading card games. Buy and sell Magic: The Gathering, Lorcana, and more.{% endblock %}">
<meta property="twitter:image" content="{% block twitter_image %}{{ request.scheme }}://{{ request.get_host }}{% static 'img/og-default.jpg' %}{% endblock %}">
<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>
{% if FEATURE_DEMO_SITE %}
<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>
{% endif %}
<nav>
<a href="{% url 'home' %}" class="nav-brand">Phantom Card Fam</a>
<div style="display: flex; justify-content: space-between; align-items: center; width: 100%;">
<a href="{% url 'home' %}" class="nav-brand">TCGKof</a>
<button class="mobile-menu-btn" aria-label="Toggle Navigation">
<span class="bar"></span>
<span class="bar"></span>
<span class="bar"></span>
</button>
</div>
<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>
{% if user.seller_profile %}
{# Seller Navigation #}
<a href="{% url 'store:seller_dashboard' %}">Dashboard</a>
<a href="{% url 'store:manage_listings' %}">Store</a>
<a href="{% url 'store:manage_listings' %}">Inventory</a>
{% if debug %}
<a href="{% url 'store:bounty_list' %}">Bounties</a>
{% endif %}
<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>
{% elif user.buyer_profile %}
{# Buyer Navigation #}
<a href="{% url 'store:card_list' %}">Browse</a>
{% if FEATURE_VIRTUAL_PACKS %}
<a href="{% url 'store:pack_list' %}">Packs</a>
{% endif %}
<a href="{% url 'decks:deck_list' %}">Decks</a>
<a href="{% url 'users:vault' %}">Vault</a>
{% if FEATURE_VIRTUAL_PACKS %}
<a href="{% url 'store:my_packs' %}">My Packs</a>
{% endif %}
<a href="{% url 'store:cart' %}">Cart ({{ user.buyer_profile.cart.items.count|default:0 }})</a>
<a href="{% url 'users:profile' %}">Profile</a>
<a href="{% url 'store:bounty_list' %}">Bounties</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 %}
{# Fallback for incomplete profiles #}
<form action="{% url 'logout' %}" method="post" style="display:inline;">
{% csrf_token %}
<button type="submit" class="btn" style="background:none; color:var(--text-color);">Logout (Invalid Account)</button>
</form>
{% endif %}
{% if user.is_staff %}
<a href="{% url 'store:admin_revenue_dashboard' %}" style="color: #f59e0b;">Platform Admin</a>
{% endif %}
{% else %}
{# Public Navigation #}
<a href="{% url 'store:card_list' %}">Browse</a>
{% if FEATURE_VIRTUAL_PACKS %}
<a href="{% url 'store:pack_list' %}">Packs</a>
{% endif %}
{% if debug %}
<a href="{% url 'store:bounty_list' %}">Bounties</a>
{% endif %}
<a href="{% url 'decks:deck_list' %}"
class="auth-required"
data-feature-title="Deck Builder"
@@ -254,13 +106,16 @@
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>
{% if FEATURE_VIRTUAL_PACKS %}
<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>
{% endif %}
<a href="{% url 'login' %}">Login</a>
<a href="{% url 'users:register' %}">Register</a>
<a href="{% url 'store:seller_register' %}">Seller?</a>
{% endif %}
</div>
</nav>
@@ -278,9 +133,27 @@
{% 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 style="background-color: #1e293b; border-top: 1px solid #334155; padding: 3rem 1rem; margin-top: 4rem; color: #94a3b8;">
<div class="container" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 2rem; max-width: 1200px; margin: 0 auto;">
<div style="text-align: left;">
<h3 style="color: #f59e0b; font-weight: 700; font-size: 1.25rem; margin-bottom: 1rem;">TCGKof</h3>
<p style="font-size: 0.875rem; line-height: 1.6;">The premier destination for trading card game buyers and sellers.</p>
<p style="font-size: 0.875rem;">&copy; 2026 TCGKof Store.</p>
<p style="font-size: 0.875rem;">Operated by <a href="https://aimloperations.com" target="_blank" style="color: inherit; text-decoration: underline;">AI ML Operations, LLC</a></p>
</div>
<div style="text-align: left;">
<h4 style="color: #e2e8f0; font-weight: 600; margin-bottom: 1rem;">Sellers</h4>
<ul style="list-style: none; padding: 0;">
<li style="margin-bottom: 0.5rem;"><a href="{% url 'users:sell_on_tcgkof' %}" style="color: inherit; text-decoration: none; transition: color 0.2s;">Sell on TCGKof</a></li>
<li style="margin-bottom: 0.5rem;"><a href="{% url 'store:seller_register' %}" style="color: inherit; text-decoration: none; transition: color 0.2s;">Seller Registration</a></li>
</ul>
</div>
<div style="text-align: left;">
<h4 style="color: #e2e8f0; font-weight: 600; margin-bottom: 1rem;">Legal</h4>
<ul style="list-style: none; padding: 0;">
<li style="margin-bottom: 0.5rem;"><a href="{% url 'store:terms' %}" style="color: inherit; text-decoration: none; transition: color 0.2s;">Terms and Service</a></li>
</ul>
</div>
</footer>
{% if not user.is_authenticated %}
@@ -298,48 +171,59 @@
<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);
// Mobile Menu Toggle
const mobileBtn = document.querySelector('.mobile-menu-btn');
const navLinks = document.querySelector('.nav-links');
if (mobileBtn) {
mobileBtn.addEventListener('click', function() {
navLinks.classList.toggle('active');
});
});
}
closeBtn.addEventListener('click', closeModal);
// Auth Modal Logic
const modal = document.getElementById('authModal');
if (modal) {
const closeBtn = document.querySelector('.close-modal');
const modalTitle = document.getElementById('modalTitle');
const modalDesc = document.getElementById('modalDesc');
const authLinks = document.querySelectorAll('.auth-required');
// Close on outside click
modal.addEventListener('click', function(e) {
if (e.target === modal) {
closeModal();
function openModal(title, desc) {
modalTitle.textContent = title;
modalDesc.textContent = desc;
modal.classList.add('active');
document.body.style.overflow = 'hidden';
}
});
// Close on Escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && modal.classList.contains('active')) {
closeModal();
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);
modal.addEventListener('click', function(e) {
if (e.target === modal) {
closeModal();
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && modal.classList.contains('active')) {
closeModal();
}
});
}
});
</script>
{% endif %}