<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Matts Money Spinner | Live Phone Recycling & Trade-In Hub</title>
<!-- Tailwind CSS CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
brand: {
50: '#f0fdf4',
100: '#dcfce7',
500: '#22c55e',
600: '#16a34a',
700: '#15803d',
},
appleDark: '#0b0b0f',
appleCardDark: '#161622',
appleCardLight: '#ffffff'
},
fontFamily: {
sans: ['-apple-system', 'BlinkMacSystemFont', '"SF Pro Display"', '"SF Pro Text"', '"Helvetica Neue"', 'Helvetica', 'Arial', 'sans-serif'],
}
}
}
}
</script>
<!-- Phosphor Icons -->
<script src="https://unpkg.com/@phosphor-icons/web"></script>
<style>
.glass-panel {
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
}
.hover-lift {
transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
.hover-lift:hover {
transform: translateY(-4px);
}
.modal-backdrop {
background-color: rgba(0, 0, 0, 0.65);
backdrop-filter: blur(6px);
}
</style>
</head>
<body class="bg-gray-50 dark:bg-appleDark text-gray-900 dark:text-gray-100 min-h-screen font-sans antialiased transition-colors duration-300 flex flex-col">
<!-- Navigation / Header -->
<header class="sticky top-0 z-40 border-b border-gray-200 dark:border-gray-800 bg-white/80 dark:bg-appleDark/80 glass-panel">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
<div class="flex items-center space-x-3">
<div class="w-10 h-10 rounded-xl bg-gradient-to-tr from-brand-600 to-emerald-400 flex items-center justify-center text-white shadow-lg shadow-brand-500/20 font-bold text-xl">
<i class="ph ph-currency-circle-dollar text-2xl"></i>
</div>
<div>
<span class="font-bold text-lg tracking-tight text-gray-900 dark:text-white flex items-center">
Matts Money Spinner
<span class="text-brand-500 text-[10px] uppercase font-semibold px-2 py-0.5 rounded-full bg-brand-500/10 border border-brand-500/20 ml-2">UK Hub</span>
</span>
<p class="text-xs text-gray-500 dark:text-gray-400 hidden sm:block">Powered by CompareAndRecycle.co.uk direct lookups</p>
</div>
</div>
<div class="flex items-center space-x-3">
<!-- Add Phone Button (Visible if logged in) -->
<button id="addPhoneHeaderBtn" onclick="openAddPhoneModal()" class="hidden sm:inline-flex items-center space-x-1.5 text-xs font-semibold bg-brand-600 hover:bg-brand-500 text-white px-3.5 py-2 rounded-xl transition-colors shadow-md shadow-brand-600/20">
<i class="ph ph-plus-circle text-base"></i>
<span>Add Phone</span>
</button>
<!-- Auth Button / Avatar -->
<div id="userAuthContainer">
<!-- Populated by JS -->
</div>
<!-- Theme Toggle -->
<button id="themeToggleBtn" onclick="toggleTheme()" class="p-2.5 rounded-full text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors focus:outline-none" title="Toggle Light/Dark Theme">
<i id="themeIcon" class="ph ph-moon text-xl"></i>
</button>
</div>
</div>
</header>
<main class="flex-grow max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Hero Banner -->
<section class="text-center max-w-3xl mx-auto my-6 sm:my-8">
<div class="inline-flex items-center space-x-2 px-3.5 py-1 rounded-full bg-brand-500/10 border border-brand-500/20 text-brand-600 dark:text-brand-400 text-xs font-semibold uppercase tracking-wider mb-4">
<i class="ph ph-sparkle text-sm"></i>
<span>Matts Money Spinner Trade-In Hub</span>
</div>
<h1 class="text-3xl sm:text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white mb-4">
Spin Your Old Phone Into Instant Cash
</h1>
<p class="text-base sm:text-lg text-gray-600 dark:text-gray-300">
Check current mobile recycling values for iPhones & flagship devices. Compare quotes directly with top UK buyers via <strong class="text-gray-900 dark:text-white font-semibold">CompareAndRecycle.co.uk</strong>.
</p>
</section>
<!-- Global Controls / Filters Bar -->
<section class="bg-white dark:bg-appleCardDark rounded-2xl p-5 shadow-sm border border-gray-200 dark:border-gray-800/80 mb-10">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Condition Selector -->
<div>
<label class="block text-xs font-semibold uppercase text-gray-500 dark:text-gray-400 mb-2 flex items-center">
<i class="ph ph-shield-check mr-1 text-sm text-brand-500"></i> Phone Condition
</label>
<div class="grid grid-cols-3 gap-1.5 p-1 bg-gray-100 dark:bg-gray-800/60 rounded-xl">
<button onclick="setCondition('good')" id="cond-good" class="cond-btn py-2 text-xs font-medium rounded-lg transition-all bg-white dark:bg-gray-700 shadow-sm text-gray-900 dark:text-white">Good</button>
<button onclick="setCondition('new')" id="cond-new" class="cond-btn py-2 text-xs font-medium rounded-lg transition-all text-gray-600 dark:text-gray-400 hover:text-gray-900">New / Mint</button>
<button onclick="setCondition('broken')" id="cond-broken" class="cond-btn py-2 text-xs font-medium rounded-lg transition-all text-gray-600 dark:text-gray-400 hover:text-gray-900">Faulty</button>
</div>
</div>
<!-- Network Lock Selector -->
<div>
<label class="block text-xs font-semibold uppercase text-gray-500 dark:text-gray-400 mb-2 flex items-center">
<i class="ph ph-lock-key-open mr-1 text-sm text-brand-500"></i> Network Status
</label>
<div class="grid grid-cols-2 gap-1.5 p-1 bg-gray-100 dark:bg-gray-800/60 rounded-xl">
<button onclick="setNetwork('unlocked')" id="net-unlocked" class="net-btn py-2 text-xs font-medium rounded-lg transition-all bg-white dark:bg-gray-700 shadow-sm text-gray-900 dark:text-white">Unlocked</button>
<button onclick="setNetwork('locked')" id="net-locked" class="net-btn py-2 text-xs font-medium rounded-lg transition-all text-gray-600 dark:text-gray-400 hover:text-gray-900">Network Locked</button>
</div>
</div>
<!-- Preferred Currency Info / Quick Status -->
<div class="sm:col-span-2 lg:col-span-1 flex items-center justify-between bg-emerald-50 dark:bg-emerald-950/30 border border-emerald-200 dark:border-emerald-800/40 rounded-xl px-4 py-3">
<div class="flex items-center space-x-3">
<div class="p-2 rounded-lg bg-emerald-500/10 text-emerald-600 dark:text-emerald-400">
<i class="ph ph-trend-up text-xl"></i>
</div>
<div>
<p class="text-xs font-semibold text-emerald-900 dark:text-emerald-300">Live Price Engine</p>
<p class="text-[11px] text-emerald-700 dark:text-emerald-400">Syncs with CompareAndRecycle</p>
</div>
</div>
<span class="text-xs font-bold text-emerald-700 dark:text-emerald-400 bg-emerald-100 dark:bg-emerald-900/60 px-2.5 py-1 rounded-full">GBP (£)</span>
</div>
</div>
</section>
<!-- Phone Cards Grid -->
<section class="mb-12">
<div class="flex items-center justify-between mb-6">
<h2 class="text-xl font-bold tracking-tight text-gray-900 dark:text-white flex items-center">
<i class="ph ph-device-mobile text-brand-500 mr-2"></i> Choose Phone Model
</h2>
<div class="flex items-center space-x-2">
<span class="text-xs text-gray-500 dark:text-gray-400">Click a phone for full rates</span>
<button id="mobileAddBtn" onclick="openAddPhoneModal()" class="sm:hidden text-xs bg-brand-600 text-white px-2.5 py-1 rounded-lg font-medium">
+ Add Phone
</button>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6" id="modelsGrid">
<!-- Dynamic Cards Inserted Here via JS -->
</div>
</section>
<!-- Detailed Breakdown Table Section -->
<section class="bg-white dark:bg-appleCardDark rounded-3xl p-6 sm:p-8 shadow-sm border border-gray-200 dark:border-gray-800 mb-12">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6">
<div>
<div class="flex items-center space-x-3">
<h2 class="text-2xl font-bold tracking-tight text-gray-900 dark:text-white" id="selectedModelTitle">
iPhone 17 Pro Max
</h2>
<button id="deleteModelBtn" onclick="deleteCurrentModel()" class="hidden text-xs text-red-500 hover:text-red-600 bg-red-500/10 px-2.5 py-1 rounded-lg border border-red-500/20">
<i class="ph ph-trash mr-1"></i>Delete Phone
</button>
</div>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
Storage options & verified recycler links on CompareAndRecycle.co.uk
</p>
</div>
<a id="directCompareBtn" href="#" target="_blank" rel="noopener noreferrer" class="inline-flex items-center justify-center space-x-2 bg-brand-600 hover:bg-brand-500 text-white font-semibold text-sm px-5 py-3 rounded-xl shadow-lg shadow-brand-600/25 transition-all hover:-translate-y-0.5">
<span>Compare Live Quotes</span>
<i class="ph ph-arrow-square-out text-base"></i>
</a>
</div>
<!-- Storage Breakdown Grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-8" id="storageBreakdownCards">
<!-- Storage card options generated dynamically -->
</div>
<!-- Recycler Comparison Table Simulation -->
<div class="overflow-x-auto rounded-2xl border border-gray-200 dark:border-gray-800">
<table class="w-full text-left border-collapse">
<thead>
<tr class="bg-gray-100/70 dark:bg-gray-800/50 text-xs uppercase text-gray-500 dark:text-gray-400 border-b border-gray-200 dark:border-gray-800">
<th class="py-3.5 px-4 font-semibold">UK Recycler Partner</th>
<th class="py-3.5 px-4 font-semibold">Payout Speed</th>
<th class="py-3.5 px-4 font-semibold">Postage</th>
<th class="py-3.5 px-4 font-semibold">Est. Offer (<span id="tableStorageLabel">256GB</span>)</th>
<th class="py-3.5 px-4 font-semibold text-right">Action</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-800 text-sm" id="recyclersTableBody">
<!-- Table rows generated dynamically -->
</tbody>
</table>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-4 flex items-center">
<i class="ph ph-info mr-1 text-base text-gray-400"></i>
Prices shown are approximate market valuations. Real-time final valuation requires inspection on CompareAndRecycle.co.uk.
</p>
</section>
<!-- What is Mobile Eco Recycling & CompareAndRecycle Info Section -->
<section class="bg-gradient-to-br from-emerald-900/10 via-appleCardDark to-appleCardDark rounded-3xl p-6 sm:p-8 border border-emerald-500/20 mb-12">
<div class="flex items-center space-x-3 mb-4">
<div class="w-10 h-10 rounded-xl bg-brand-500/20 text-brand-400 flex items-center justify-center text-2xl">
<i class="ph ph-recycle"></i>
</div>
<div>
<h3 class="text-xl font-bold text-gray-900 dark:text-white">What is Mobile Phone Eco-Recycling?</h3>
<p class="text-xs text-brand-500 font-medium">Understanding the circular tech economy</p>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 text-sm text-gray-600 dark:text-gray-300 leading-relaxed">
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-2 flex items-center">
<i class="ph ph-leaf text-brand-500 mr-1.5"></i> Why Recycle Your Phone?
</h4>
<p>
Mobile phone eco-recycling gives old smartphones a second life. Instead of cluttering drawers or ending up in landfills where hazardous e-waste degrades, devices are refurbished, resold, or broken down into valuable raw components (gold, copper, lithium, cobalt).
</p>
</div>
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-2 flex items-center">
<i class="ph ph-arrows-merge text-brand-500 mr-1.5"></i> How CompareAndRecycle.co.uk Works
</h4>
<p>
<strong class="text-gray-900 dark:text-white">CompareAndRecycle.co.uk</strong> is the UK's leading independent price comparison site for mobile recycling. It aggregates live valuation quotes from dozens of trusted UK trade-in companies (like MusicMagpie, Mazuma, BackMarket), ensuring you get the single highest payout for your model.
</p>
</div>
</div>
</section>
</main>
<footer class="border-t border-gray-200 dark:border-gray-800 bg-white dark:bg-appleDark py-8 mt- auto">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center text-xs text-gray-500 dark:text-gray-400">
<p class="mb-2">Matts Money Spinner is an independent valuation assistant. Live comparison quotes powered by CompareAndRecycle.co.uk.</p>
<p>© 2026 Matts Money Spinner. All rights reserved.</p>
</div>
</footer>
<!-- LOGIN & PASSWORD RESET MODAL -->
<div id="authModal" class="fixed inset-0 z-50 flex items-center justify-center p-4 modal-backdrop hidden">
<div class="bg-white dark:bg-appleCardDark border border-gray-200 dark:border-gray-800 rounded-3xl w-full max-w-md p-6 sm:p-8 shadow-2xl relative">
<button onclick="closeAuthModal()" class="absolute top-5 right-5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200">
<i class="ph ph-x text-2xl"></i>
</button>
<!-- View 1: Login Form -->
<div id="loginView">
<div class="text-center mb-6">
<div class="w-12 h-12 bg-brand-500/10 text-brand-500 rounded-2xl flex items-center justify-center mx-auto mb-3 text-2xl">
<i class="ph ph-lock-key"></i>
</div>
<h3 class="text-2xl font-bold text-gray-900 dark:text-white">Matt's Login</h3>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Sign in to manage custom phone models</p>
</div>
<form id="loginForm" onsubmit="handleLogin(event)" class="space-y-4">
<div>
<label class="block text-xs font-semibold uppercase text-gray-500 dark:text-gray-400 mb-1">Email Address</label>
<input type="email" id="loginEmail" value="matt@thefivecreators.com" required class="w-full px-4 py-2.5 rounded-xl border border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:border-brand-500">
</div>
<div>
<div class="flex justify-between items-center mb-1">
<label class="text-xs font-semibold uppercase text-gray-500 dark:text-gray-400">Password</label>
<button type="button" onclick="switchAuthView('reset')" class="text-xs text-brand-500 hover:underline">Reset Password?</button>
</div>
<input type="password" id="loginPassword" placeholder="••••••••" required class="w-full px-4 py-2.5 rounded-xl border border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:border-brand-500">
</div>
<div id="loginError" class="hidden text-xs text-red-500 bg-red-500/10 p-2.5 rounded-xl border border-red-500/20 text-center">
Invalid email or password.
</div>
<button type="submit" class="w-full py-3 bg-brand-600 hover:bg-brand-500 text-white font-semibold text-sm rounded-xl transition-colors shadow-lg shadow-brand-600/20">
Sign In
</button>
</form>
</div>
<!-- View 2: Reset Password Form -->
<div id="resetView" class="hidden">
<div class="text-center mb-6">
<div class="w-12 h-12 bg-amber-500/10 text-amber-500 rounded-2xl flex items-center justify-center mx-auto mb-3 text-2xl">
<i class="ph ph-key-return"></i>
</div>
<h3 class="text-2xl font-bold text-gray-900 dark:text-white">Reset Password</h3>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Set a new secret password for your account</p>
</div>
<form id="resetForm" onsubmit="handleResetPassword(event)" class="space-y-4">
<div>
<label class="block text-xs font-semibold uppercase text-gray-500 dark:text-gray-400 mb-1">Email</label>
<input type="email" id="resetEmail" value="matt@thefivecreators.com" required readonly class="w-full px-4 py-2.5 rounded-xl border border-gray-300 dark:border-gray-700 bg-gray-100 dark:bg-gray-800/50 text-gray-500 dark:text-gray-400 text-sm cursor-not-allowed">
</div>
<div>
<label class="block text-xs font-semibold uppercase text-gray-500 dark:text-gray-400 mb-1">Current Password</label>
<input type="password" id="resetCurrentPass" placeholder="Enter current password" required class="w-full px-4 py-2.5 rounded-xl border border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:border-brand-500">
</div>
<div>
<label class="block text-xs font-semibold uppercase text-gray-500 dark:text-gray-400 mb-1">New Password</label>
<input type="password" id="resetNewPass" placeholder="Enter new custom password" minlength="4" required class="w-full px-4 py-2.5 rounded-xl border border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:border-brand-500">
</div>
<div id="resetStatus" class="hidden text-xs p-2.5 rounded-xl text-center"></div>
<button type="submit" class="w-full py-3 bg-brand-600 hover:bg-brand-500 text-white font-semibold text-sm rounded-xl transition-colors shadow-lg shadow-brand-600/20">
Update Password
</button>
<button type="button" onclick="switchAuthView('login')" class="w-full text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 text-center block pt-2">
Back to Sign In
</button>
</form>
</div>
</div>
</div>
<!-- ADD PHONE MODEL MODAL -->
<div id="addPhoneModal" class="fixed inset-0 z-50 flex items-center justify-center p-4 modal-backdrop hidden">
<div class="bg-white dark:bg-appleCardDark border border-gray-200 dark:border-gray-800 rounded-3xl w-full max-w-lg p-6 sm:p-8 shadow-2xl relative max-h-[90vh] overflow-y-auto">
<button onclick="closeAddPhoneModal()" class="absolute top-5 right-5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200">
<i class="ph ph-x text-2xl"></i>
</button>
<div class="text-center mb-6">
<div class="w-12 h-12 bg-brand-500/10 text-brand-500 rounded-2xl flex items-center justify-center mx-auto mb-3 text-2xl">
<i class="ph ph-device-mobile-camera"></i>
</div>
<h3 class="text-2xl font-bold text-gray-900 dark:text-white">Add New Phone</h3>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Add custom mobile models to Matts Money Spinner</p>
</div>
<form id="addPhoneForm" onsubmit="handleAddPhoneSubmit(event)" class="space-y-4">
<div>
<label class="block text-xs font-semibold uppercase text-gray-500 dark:text-gray-400 mb-1">Phone Full Name</label>
<input type="text" id="phoneNameInput" placeholder="e.g., Samsung Galaxy S25 Ultra" required class="w-full px-4 py-2.5 rounded-xl border border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:border-brand-500">
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-xs font-semibold uppercase text-gray-500 dark:text-gray-400 mb-1">Badge Tag</label>
<input type="text" id="phoneBadgeInput" placeholder="e.g., Android Flagship" required class="w-full px-4 py-2.5 rounded-xl border border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:border-brand-500">
</div>
<div>
<label class="block text-xs font-semibold uppercase text-gray-500 dark:text-gray-400 mb-1">Release Year / Chip</label>
<input type="text" id="phoneChipInput" placeholder="e.g., Snapdragon 8 Gen 4" required class="w-full px-4 py-2.5 rounded-xl border border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:border-brand-500">
</div>
</div>
<div>
<label class="block text-xs font-semibold uppercase text-gray-500 dark:text-gray-400 mb-1">Base Estimated Value (£ for 256GB / Base)</label>
<input type="number" id="phoneBasePriceInput" placeholder="650" required min="10" max="3000" class="w-full px-4 py-2.5 rounded-xl border border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:border-brand-500">
</div>
<div>
<label class="block text-xs font-semibold uppercase text-gray-500 dark:text-gray-400 mb-1">CompareAndRecycle Link (Optional)</label>
<input type="url" id="phoneUrlInput" placeholder="https://www.compareandrecycle.co.uk/cell-phones/..." class="w-full px-4 py-2.5 rounded-xl border border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:border-brand-500">
</div>
<button type="submit" class="w-full py-3 bg-brand-600 hover:bg-brand-500 text-white font-semibold text-sm rounded-xl transition-colors shadow-lg shadow-brand-600/20 mt-2">
Save Phone Model
</button>
</form>
</div>
</div>
<script>
// --- Initial Default Phone Data ---
const defaultPhoneData = [
{
id: 'iphone-17-pro-max',
name: 'iPhone 17 Pro Max',
badge: 'Latest Flagship',
chip: 'A19 Pro Chip',
year: '2025',
compareUrl: 'https://www.compareandrecycle.co.uk/cell-phones/apple-iphone-17-pro-max',
searchUrl: 'https://www.compareandrecycle.co.uk/search?q=iPhone+17+Pro+Max',
storages: ['256GB', '512GB', '1TB'],
defaultStorage: '256GB',
basePrices: {
'256GB': 780,
'512GB': 890,
'1TB': 1020
},
gradient: 'from-amber-500/20 to-orange-500/5',
accentColor: 'text-amber-500',
isCustom: false
},
{
id: 'iphone-17-pro',
name: 'iPhone 17 Pro',
badge: 'Compact Pro',
chip: 'A19 Pro Chip',
year: '2025',
compareUrl: 'https://www.compareandrecycle.co.uk/cell-phones/apple-iphone-17-pro',
searchUrl: 'https://www.compareandrecycle.co.uk/search?q=iPhone+17+Pro',
storages: ['128GB', '256GB', '512GB', '1TB'],
defaultStorage: '256GB',
basePrices: {
'128GB': 660,
'256GB': 730,
'512GB': 840,
'1TB': 950
},
gradient: 'from-blue-500/20 to-indigo-500/5',
accentColor: 'text-blue-500',
isCustom: false
},
{
id: 'iphone-16-pro-max',
name: 'iPhone 16 Pro Max',
badge: 'High Value',
chip: 'A18 Pro Chip',
year: '2024',
compareUrl: 'https://www.compareandrecycle.co.uk/cell-phones/apple-iphone-16-pro-max',
searchUrl: 'https://www.compareandrecycle.co.uk/search?q=iPhone+16+Pro+Max',
storages: ['256GB', '512GB', '1TB'],
defaultStorage: '256GB',
basePrices: {
'256GB': 610,
'512GB': 710,
'1TB': 820
},
gradient: 'from-emerald-500/20 to-teal-500/5',
accentColor: 'text-emerald-500',
isCustom: false
},
{
id: 'iphone-16-pro',
name: 'iPhone 16 Pro',
badge: 'Top Seller',
chip: 'A18 Pro Chip',
year: '2024',
compareUrl: 'https://www.compareandrecycle.co.uk/cell-phones/apple-iphone-16-pro',
searchUrl: 'https://www.compareandrecycle.co.uk/search?q=iPhone+16+Pro',
storages: ['128GB', '256GB', '512GB', '1TB'],
defaultStorage: '128GB',
basePrices: {
'128GB': 490,
'256GB': 560,
'512GB': 650,
'1TB': 740
},
gradient: 'from-purple-500/20 to-pink-500/5',
accentColor: 'text-purple-500',
isCustom: false
}
];
// Recycler partners list for table simulation
const recyclersList = [
{ name: 'MusicMagpie', rating: '4.8 ★', speed: 'Same Day', postage: 'Free Royal Mail', variance: 1.0 },
{ name: 'Mazuma Mobile', rating: '4.7 ★', speed: '24 Hours', postage: 'Free Tracked Bag', variance: 0.98 },
{ name: 'BackMarket Recyclers', rating: '4.9 ★', speed: '1-2 Days', postage: 'Free Prepaid', variance: 1.02 },
{ name: 'SellMyMobile Direct', rating: '4.6 ★', speed: 'Same Day', postage: 'Free Prepaid', variance: 0.95 },
{ name: 'Giffgaff Recycle', rating: '4.7 ★', speed: '2-3 Days', postage: 'Free Label', variance: 0.96 }
];
// Persistent State Management
function loadPhones() {
const saved = localStorage.getItem('mms_phone_data');
if (saved) {
try { return JSON.parse(saved); } catch(e) {}
}
return defaultPhoneData;
}
function savePhones() {
localStorage.setItem('mms_phone_data', JSON.stringify(phoneData));
}
let phoneData = loadPhones();
// Account Authentication State
function loadUserPass() {
return localStorage.getItem('mms_user_password') || 'password';
}
function saveUserPass(newPass) {
localStorage.setItem('mms_user_password', newPass);
}
let isLoggedIn = localStorage.getItem('mms_is_logged_in') === 'true';
const userEmail = 'matt@thefivecreators.com';
// Application Control Variables
let selectedModelId = 'iphone-17-pro-max';
let currentCondition = 'good'; // 'new', 'good', 'broken'
let currentNetwork = 'unlocked'; // 'unlocked', 'locked'
let selectedStorage = '256GB';
const conditionMultipliers = { 'new': 1.15, 'good': 1.0, 'broken': 0.45 };
const networkMultipliers = { 'unlocked': 1.0, 'locked': 0.88 };
function calculatePrice(basePrice) {
const condMult = conditionMultipliers[currentCondition] || 1.0;
const netMult = networkMultipliers[currentNetwork] || 1.0;
return Math.round(basePrice * condMult * netMult);
}
function toggleTheme() {
const html = document.documentElement;
const icon = document.getElementById('themeIcon');
if (html.classList.contains('dark')) {
html.classList.remove('dark');
icon.className = 'ph ph-sun text-xl';
localStorage.setItem('theme', 'light');
} else {
html.classList.add('dark');
icon.className = 'ph ph-moon text-xl';
localStorage.setItem('theme', 'dark');
}
}
if (localStorage.getItem('theme') === 'light') {
document.documentElement.classList.remove('dark');
const icon = document.getElementById('themeIcon');
if(icon) icon.className = 'ph ph-sun text-xl';
}
// Auth UI Render
function renderAuthUI() {
const container = document.getElementById('userAuthContainer');
const addBtn = document.getElementById('addPhoneHeaderBtn');
const mobileAddBtn = document.getElementById('mobileAddBtn');
if (isLoggedIn) {
if(addBtn) addBtn.classList.remove('hidden');
if(mobileAddBtn) mobileAddBtn.classList.remove('hidden');
container.innerHTML = `
<div class="flex items-center space-x-2">
<button onclick="openAuthModal('reset')" class="flex items-center space-x-2 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 px-3 py-1.5 rounded-xl transition-colors">
<div class="w-6 h-6 rounded-full bg-brand-500 text-white font-bold text-xs flex items-center justify-center">M</div>
<span class="text-xs font-semibold hidden md:inline text-gray-900 dark:text-white">Matt</span>
<i class="ph ph-gear text-xs text-gray-400"></i>
</button>
<button onclick="handleLogout()" class="p-2 text-xs text-red-500 hover:bg-red-500/10 rounded-xl" title="Log Out">
<i class="ph ph-sign-out text-lg"></i>
</button>
</div>
`;
} else {
if(addBtn) addBtn.classList.add('hidden');
if(mobileAddBtn) mobileAddBtn.classList.add('hidden');
container.innerHTML = `
<button onclick="openAuthModal('login')" class="text-xs font-semibold text-gray-700 dark:text-gray-200 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 px-3.5 py-2 rounded-xl transition-colors">
<i class="ph ph-user mr-1"></i> Matt's Sign In
</button>
`;
}
}
function openAuthModal(view = 'login') {
switchAuthView(view);
document.getElementById('authModal').classList.remove('hidden');
}
function closeAuthModal() {
document.getElementById('authModal').classList.add('hidden');
document.getElementById('loginError').classList.add('hidden');
document.getElementById('resetStatus').classList.add('hidden');
}
function switchAuthView(view) {
if (view === 'login') {
document.getElementById('loginView').classList.remove('hidden');
document.getElementById('resetView').classList.add('hidden');
} else {
document.getElementById('loginView').classList.add('hidden');
document.getElementById('resetView').classList.remove('hidden');
}
}
function handleLogin(e) {
e.preventDefault();
const email = document.getElementById('loginEmail').value.trim();
const pass = document.getElementById('loginPassword').value;
const actualPass = loadUserPass();
if (email === userEmail && pass === actualPass) {
isLoggedIn = true;
localStorage.setItem('mms_is_logged_in', 'true');
closeAuthModal();
renderApp();
} else {
document.getElementById('loginError').classList.remove('hidden');
}
}
function handleResetPassword(e) {
e.preventDefault();
const current = document.getElementById('resetCurrentPass').value;
const newPass = document.getElementById('resetNewPass').value;
const actualPass = loadUserPass();
const status = document.getElementById('resetStatus');
if (current !== actualPass) {
status.className = 'text-xs p-2.5 rounded-xl text-center bg-red-500/10 text-red-500 border border-red-500/20';
status.innerText = 'Current password incorrect.';
status.classList.remove('hidden');
return;
}
saveUserPass(newPass);
status.className = 'text-xs p-2.5 rounded-xl text-center bg-emerald-500/10 text-emerald-500 border border-emerald-500/20';
status.innerText = 'Password successfully updated! You can now log in with your new password.';
status.classList.remove('hidden');
setTimeout(() => {
switchAuthView('login');
}, 1500);
}
function handleLogout() {
isLoggedIn = false;
localStorage.setItem('mms_is_logged_in', 'false');
renderApp();
}
function openAddPhoneModal() {
if (!isLoggedIn) {
openAuthModal('login');
return;
}
document.getElementById('addPhoneModal').classList.remove('hidden');
}
function closeAddPhoneModal() {
document.getElementById('addPhoneModal').classList.add('hidden');
}
function handleAddPhoneSubmit(e) {
e.preventDefault();
const name = document.getElementById('phoneNameInput').value.trim();
const badge = document.getElementById('phoneBadgeInput').value.trim() || 'Custom Phone';
const chip = document.getElementById('phoneChipInput').value.trim() || 'Custom Edition';
const basePrice = parseInt(document.getElementById('phoneBasePriceInput').value) || 500;
const url = document.getElementById('phoneUrlInput').value.trim() || `https://www.compareandrecycle.co.uk/search?q=${encodeURIComponent(name)}`;
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-');
const newPhone = {
id: slug,
name: name,
badge: badge,
chip: chip,
year: '2026',
compareUrl: url,
searchUrl: `https://www.compareandrecycle.co.uk/search?q=${encodeURIComponent(name)}`,
storages: ['128GB', '256GB', '512GB', '1TB'],
defaultStorage: '256GB',
basePrices: {
'128GB': Math.round(basePrice * 0.85),
'256GB': basePrice,
'512GB': Math.round(basePrice * 1.18),
'1TB': Math.round(basePrice * 1.35)
},
gradient: 'from-brand-500/20 to-emerald-500/5',
accentColor: 'text-brand-500',
isCustom: true
};
phoneData.push(newPhone);
savePhones();
selectedModelId = newPhone.id;
selectedStorage = '256GB';
closeAddPhoneModal();
document.getElementById('addPhoneForm').reset();
renderApp();
}
function deleteCurrentModel() {
const model = phoneData.find(m => m.id === selectedModelId);
if (!model) return;
if (confirm(`Are you sure you want to remove "${model.name}" from your catalog?`)) {
phoneData = phoneData.filter(m => m.id !== selectedModelId);
savePhones();
selectedModelId = phoneData[0]?.id || 'iphone-17-pro-max';
renderApp();
}
}
// Filter Selectors
function setCondition(cond) {
currentCondition = cond;
document.querySelectorAll('.cond-btn').forEach(btn => {
btn.className = 'cond-btn py-2 text-xs font-medium rounded-lg transition-all text-gray-600 dark:text-gray-400 hover:text-gray-900';
});
const activeBtn = document.getElementById(`cond-${cond}`);
if (activeBtn) {
activeBtn.className = 'cond-btn py-2 text-xs font-medium rounded-lg transition-all bg-white dark:bg-gray-700 shadow-sm text-gray-900 dark:text-white font-semibold';
}
renderApp();
}
function setNetwork(net) {
currentNetwork = net;
document.querySelectorAll('.net-btn').forEach(btn => {
btn.className = 'net-btn py-2 text-xs font-medium rounded-lg transition-all text-gray-600 dark:text-gray-400 hover:text-gray-900';
});
const activeBtn = document.getElementById(`net-${net}`);
if (activeBtn) {
activeBtn.className = 'net-btn py-2 text-xs font-medium rounded-lg transition-all bg-white dark:bg-gray-700 shadow-sm text-gray-900 dark:text-white font-semibold';
}
renderApp();
}
function selectModel(modelId) {
selectedModelId = modelId;
const model = phoneData.find(m => m.id === modelId);
if (model) {
if (!model.storages.includes(selectedStorage)) {
selectedStorage = model.defaultStorage;
}
}
renderApp();
}
function selectStorage(storage) {
selectedStorage = storage;
renderApp();
}
function renderModelCards() {
const grid = document.getElementById('modelsGrid');
grid.innerHTML = phoneData.map(model => {
const isSelected = model.id === selectedModelId;
const topPrice = calculatePrice(model.basePrices[model.defaultStorage] || model.basePrices[model.storages[0]]);
return `
<div onclick="selectModel('${model.id}')" class="hover-lift cursor-pointer rounded-2xl p-5 border transition-all relative ${
isSelected
? 'border-brand-500 ring-2 ring-brand-500/20 bg-white dark:bg-appleCardDark shadow-xl'
: 'border-gray-200 dark:border-gray-800 bg-white dark:bg-appleCardDark/60 hover:border-gray-300 dark:hover:border-gray-700'
}">
<div class="flex items-center justify-between mb-3">
<span class="text-[11px] font-semibold px-2.5 py-1 rounded-full bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300">
${model.badge}
</span>
<span class="text-xs font-medium text-gray-400">${model.year}</span>
</div>
<!-- Device Visual -->
<div class="my-4 flex items-center justify-center py-3 rounded-xl bg-gradient-to-b ${model.gradient}">
<div class="relative w-16 h-28 border-2 border-gray-400 dark:border-gray-600 rounded-[1.2rem] bg-gray-900 p-1 flex flex-col justify-between shadow-inner">
<div class="w-6 h-1 bg-gray-700 rounded-full mx-auto mt-0.5"></div>
<i class="ph ph-device-mobile text-gray-500 text-2xl mx-auto opacity-50"></i>
<div class="text-[8px] text-gray-400 text-center mb-1 truncate px-1">${model.chip}</div>
</div>
</div>
<h3 class="font-bold text-gray-900 dark:text-white text-base text-center">${model.name}</h3>
<div class="mt-3 text-center">
<span class="text-xs text-gray-500 dark:text-gray-400">Up to approx.</span>
<div class="text-2xl font-black text-brand-600 dark:text-brand-400">
£${topPrice}
</div>
<span class="text-[10px] text-gray-400 block mt-0.5">for ${model.defaultStorage} (${currentCondition})</span>
</div>
<div class="mt-4 pt-3 border-t border-gray-100 dark:border-gray-800/80 flex items-center justify-between text-xs">
<span class="text-gray-500">${model.storages.length} Capacities</span>
<span class="${model.accentColor} font-semibold flex items-center">
View Rates <i class="ph ph-caret-right ml-0.5"></i>
</span>
</div>
</div>
`;
}).join('');
}
function renderDetailBreakdown() {
const model = phoneData.find(m => m.id === selectedModelId);
if (!model) return;
document.getElementById('selectedModelTitle').innerText = `${model.name} Rates`;
document.getElementById('tableStorageLabel').innerText = selectedStorage;
const directCompareBtn = document.getElementById('directCompareBtn');
directCompareBtn.href = model.compareUrl;
// Toggle Delete Button for Logged in user
const deleteBtn = document.getElementById('deleteModelBtn');
if (isLoggedIn) {
deleteBtn.classList.remove('hidden');
} else {
deleteBtn.classList.add('hidden');
}
// Storage Options
const storageCardsContainer = document.getElementById('storageBreakdownCards');
storageCardsContainer.innerHTML = model.storages.map(st => {
const isSelected = st === selectedStorage;
const estVal = calculatePrice(model.basePrices[st] || Math.round(model.basePrices[model.defaultStorage] * 1.1));
return `
<div onclick="selectStorage('${st}')" class="cursor-pointer p-4 rounded-xl border transition-all ${
isSelected
? 'bg-brand-500/10 border-brand-500 text-brand-700 dark:text-brand-300 font-semibold shadow-sm'
: 'bg-gray-50 dark:bg-gray-800/40 border-gray-200 dark:border-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800'
}">
<div class="flex items-center justify-between text-xs mb-1">
<span>Capacity</span>
${isSelected ? '<span class="text-[10px] bg-brand-500 text-white px-1.5 py-0.5 rounded">Selected</span>' : ''}
</div>
<div class="text-lg font-bold">${st}</div>
<div class="text-xl font-black mt-2 text-gray-900 dark:text-white">£${estVal}</div>
<div class="text-[11px] text-gray-500 dark:text-gray-400 mt-1">Approx recycling payout</div>
</div>
`;
}).join('');
// Recycler Comparison Table
const baseVal = calculatePrice(model.basePrices[selectedStorage] || model.basePrices[model.defaultStorage] || 500);
const tableBody = document.getElementById('recyclersTableBody');
tableBody.innerHTML = recyclersList.map(rec => {
const finalPrice = Math.round(baseVal * rec.variance);
return `
<tr class="hover:bg-gray-50/50 dark:hover:bg-gray-800/30 transition-colors">
<td class="py-4 px-4 font-semibold text-gray-900 dark:text-white">
<div class="flex items-center space-x-2">
<span>${rec.name}</span>
<span class="text-[10px] font-normal text-amber-500 bg-amber-500/10 px-1.5 py-0.5 rounded">${rec.rating}</span>
</div>
</td>
<td class="py-4 px-4 text-gray-600 dark:text-gray-300">${rec.speed}</td>
<td class="py-4 px-4 text-gray-600 dark:text-gray-300">${rec.postage}</td>
<td class="py-4 px-4 font-black text-base text-brand-600 dark:text-brand-400">£${finalPrice}</td>
<td class="py-4 px-4 text-right">
<a href="${model.searchUrl}" target="_blank" rel="noopener noreferrer" class="inline-flex items-center text-xs font-semibold text-brand-600 dark:text-brand-400 hover:text-brand-700 bg-brand-50 dark:bg-brand-950/50 hover:bg-brand-100 dark:hover:bg-brand-900/60 px-3 py-1.5 rounded-lg border border-brand-200 dark:border-brand-800/50 transition-colors">
<span>Get Offer</span>
<i class="ph ph-arrow-right ml-1"></i>
</a>
</td>
</tr>
`;
}).join('');
}
function renderApp() {
renderAuthUI();
renderModelCards();
renderDetailBreakdown();
}
window.onload = function() {
renderApp();
};
</script>
</body>
</html>