This commit is contained in:
Caleb Fultz 2024-08-21 18:03:33 -04:00
parent 1aed7fb673
commit 6d93ad6203
18 changed files with 762 additions and 24 deletions

BIN
assets/cash-money.mp3 Normal file

Binary file not shown.

BIN
assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

89
css/base.css Normal file
View File

@ -0,0 +1,89 @@
/* base.css */
:root {
--background-color: #f0f0f0;
--text-color: #333;
--card-background: #ffffff;
--button-background: #007acc;
--button-hover: #005fa3;
}
body {
font-family: 'Sorts Mill Goudy', serif;
background-color: var(--background-color);
color: var(--text-color);
margin: 0;
padding: 20px;
}
.container {
max-width: 600px;
margin: 0 auto;
background-color: var(--card-background);
padding: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
h1 {
text-align: center;
margin-bottom: 20px;
}
form div {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
}
input, select, textarea {
width: 100%;
padding: 8px;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 4px;
background-color: var(--card-background);
color: var(--text-color);
}
button {
display: block;
width: 100%;
padding: 10px;
background-color: var(--button-background);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-top: 10px;
}
button:hover {
background-color: var(--button-hover);
}
#csv-output {
margin-top: 20px;
height: 200px;
background-color: var(--card-background);
color: var(--text-color);
}
footer {
text-align: center;
font-size: 0.5rem;
color: #666;
margin-top: 0px;
padding: 1px;
}
footer a {
color: #007acc;
text-decoration: none;
}
footer a:hover {
text-decoration: underline;
}

106
css/components.css Normal file
View File

@ -0,0 +1,106 @@
/* components.css */
.mana-icon {
font-family: 'mana', sans-serif;
font-size: 24px;
cursor: pointer;
padding: 5px;
margin-right: 10px;
}
.mana-icon:hover {
opacity: 0.8;
}
.menu {
position: relative;
display: inline-block;
}
.hamburger-menu {
cursor: pointer;
font-size: 30px;
color: var(--text-color);
}
.menu-content {
display: none; /* Ensure it's hidden by default */
position: absolute;
top: 40px;
left: 0;
background-color: var(--background-color);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
padding: 10px;
z-index: 1;
min-width: 150px;
border-radius: 4px;
}
.menu-content.show {
display: block; /* Display menu when toggled */
}
.zoom-modal {
display: none; /* Ensure modal is hidden by default */
position: fixed;
z-index: 1000;
padding-top: 100px;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.8);
}
.zoomed-image {
margin: auto;
display: block;
width: 80%;
max-width: 700px;
}
/* Dark mode styles */
/* Dark mode styles */
body.dark-mode .menu-content {
background-color: #333;
}
body.dark-mode .hamburger-menu,
body.dark-mode .menu-content label,
body.dark-mode .menu-content input {
color: white;
}
.card-popup {
position: fixed;
bottom: 20px;
right: 20px;
max-width: 20%;
z-index: 1000;
background-color: rgba(0, 0, 0, 0.7);
padding: 10px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
opacity: 0;
transition: opacity 0.5s ease-in-out;
display: none;
}
.card-popup img {
width: 100%;
height: auto;
border-radius: 4px;
}
@media (max-width: 600px) {
.card-popup {
max-width: 40%;
}
}
@media (max-width: 400px) {
.card-popup {
max-width: 60%;
}
}

102
css/themes.css Normal file
View File

@ -0,0 +1,102 @@
/* Light and Dark mode base theme */
:root {
--background-color: #f0f0f0;
--text-color: #333;
--card-background: #ffffff;
--button-background: #007acc;
--button-hover: #005fa3;
}
@media (prefers-color-scheme: dark) {
:root {
--background-color: #1e1e1e;
--text-color: #ffffff;
--card-background: #2e2e2e;
--button-background: #1f6fb2;
--button-hover: #145b8c;
}
}
/* Mana Color Themes */
.theme-white {
--background-color: #f9f8f6;
--text-color: #1e1e1e;
--card-background: #ffffff;
--button-background: #e5e5e5;
--button-hover: #cccccc;
}
.theme-blue {
--background-color: #d7e7f9;
--text-color: #002d72;
--card-background: #e0f2ff;
--button-background: #007acc;
--button-hover: #005fa3;
}
.theme-black {
--background-color: #333333;
--text-color: #ffffff;
--card-background: #444444;
--button-background: #1f1f1f;
--button-hover: #000000;
}
.theme-red {
--background-color: #fbe4e4;
--text-color: #8b0000;
--card-background: #ffe5e5;
--button-background: #d32f2f;
--button-hover: #b71c1c;
}
.theme-green {
--background-color: #e7f4e7;
--text-color: #004d00;
--card-background: #e0ffe0;
--button-background: #388e3c;
--button-hover: #2e7d32;
}
/* Dark Mode Variants for Mana Themes */
@media (prefers-color-scheme: dark) {
.theme-white {
--background-color: #1e1e1e;
--text-color: #ffffff;
--card-background: #333333;
--button-background: #555555;
--button-hover: #777777;
}
.theme-blue {
--background-color: #001f3f;
--text-color: #ffffff;
--card-background: #002d72;
--button-background: #005fa3;
--button-hover: #007acc;
}
.theme-black {
--background-color: #000000;
--text-color: #e5e5e5;
--card-background: #1c1c1c;
--button-background: #333333;
--button-hover: #555555;
}
.theme-red {
--background-color: #330000;
--text-color: #ffaaaa;
--card-background: #8b0000;
--button-background: #b71c1c;
--button-hover: #d32f2f;
}
.theme-green {
--background-color: #002200;
--text-color: #ccffcc;
--card-background: #004d00;
--button-background: #2e7d32;
--button-hover: #388e3c;
}
}

View File

@ -1,28 +1,33 @@
<!-- index.html -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MTG Card CSV Generator</title> <title>MTG Card CSV Generator</title>
<link rel="stylesheet" href="styles.css">
<!-- Stylesheets -->
<link rel="stylesheet" href="css/base.css">
<link rel="stylesheet" href="css/themes.css">
<link rel="stylesheet" href="css/components.css">
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
<link href="https://fonts.googleapis.com/css2?family=Sorts+Mill+Goudy&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Sorts+Mill+Goudy&display=swap" rel="stylesheet">
<link rel="icon" href="icon.png" type="image/png"> <link rel="icon" href="assets/icon.png" type="image/png">
<link href="//cdn.jsdelivr.net/npm/mana-font@latest/css/mana.css" rel="stylesheet" type="text/css" /> <link href="https://cdn.jsdelivr.net/npm/mana-font@latest/css/mana.css" rel="stylesheet" type="text/css" />
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<div class="menu"> <div class="menu">
<div class="hamburger-menu" onclick="toggleMenu()"></div> <div class="hamburger-menu" id="hamburger-menu"></div>
<div id="menu-content" class="menu-content"> <div id="menu-content" class="menu-content">
<label>Select Theme:</label> <label>Select Theme:</label>
<div id="theme-selector"> <div id="theme-selector">
<span class="mana-icon" onclick="setTheme('')">&#xe623;</span> <!-- Colorless --> <span class="mana-icon" data-theme="" onclick="setTheme('')">&#xe623;</span> <!-- Colorless -->
<span class="mana-icon" onclick="setTheme('theme-white')">&#xe600;</span> <!-- White --> <span class="mana-icon" data-theme="theme-white" onclick="setTheme('theme-white')">&#xe600;</span> <!-- White -->
<span class="mana-icon" onclick="setTheme('theme-blue')">&#xe601;</span> <!-- Blue --> <span class="mana-icon" data-theme="theme-blue" onclick="setTheme('theme-blue')">&#xe601;</span> <!-- Blue -->
<span class="mana-icon" onclick="setTheme('theme-black')">&#xe602;</span> <!-- Black --> <span class="mana-icon" data-theme="theme-black" onclick="setTheme('theme-black')">&#xe602;</span> <!-- Black -->
<span class="mana-icon" onclick="setTheme('theme-red')">&#xe603;</span> <!-- Red --> <span class="mana-icon" data-theme="theme-red" onclick="setTheme('theme-red')">&#xe603;</span> <!-- Red -->
<span class="mana-icon" onclick="setTheme('theme-green')">&#xe604;</span> <!-- Green --> <span class="mana-icon" data-theme="theme-green" onclick="setTheme('theme-green')">&#xe604;</span> <!-- Green -->
</div> </div>
<label for="sound-threshold">Sound Threshold (USD):</label> <label for="sound-threshold">Sound Threshold (USD):</label>
<input type="number" id="sound-threshold" min="0.01" step="0.01" value="1.00"> <input type="number" id="sound-threshold" min="0.01" step="0.01" value="1.00">
@ -30,6 +35,7 @@
</div> </div>
<h1>MTG Card CSV Generator</h1> <h1>MTG Card CSV Generator</h1>
<form id="card-form"> <form id="card-form">
<div> <div>
<label for="card-name">Card Name:</label> <label for="card-name">Card Name:</label>
@ -57,9 +63,23 @@
</div> </div>
<button type="button" id="add-card-button">Add Card</button> <button type="button" id="add-card-button">Add Card</button>
</form> </form>
<button id="download-csv-button">Download CSV</button>
<div class="action-buttons">
<button id="download-csv-button">Download CSV</button>
<button id="save-collection-button">Save Collection</button>
<button id="clear-collection-button">Clear Collection</button>
</div>
<textarea id="csv-output" readonly></textarea> <textarea id="csv-output" readonly></textarea>
<div id="card-popup" class="card-popup"></div>
<div id="card-popup" class="card-popup">
<img id="card-popup-image" src="" alt="Card Image">
</div>
<div id="zoom-modal" class="zoom-modal">
<span id="close-modal" class="close-modal">&times;</span>
<img id="zoomed-image" class="zoomed-image" src="" alt="">
</div>
</div> </div>
<footer> <footer>
@ -69,11 +89,22 @@
</p> </p>
</footer> </footer>
<script type="module" src="app.js"></script> <!-- Scripts -->
<script type="module" src="js/theme.js"></script>
<script type="module" src="js/main.js"></script>
<script type="module" src="js/card.js"></script>
<script type="module" src="js/storage.js"></script>
<script type="module" src="js/csv.js"></script>
<script type="module" src="js/popup.js"></script>
<script> <script>
if (typeof navigator.serviceWorker !== 'undefined') { if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js') navigator.serviceWorker.register('js/sw.js').then(function(registration) {
console.log('Service Worker registered with scope:', registration.scope);
}).catch(function(error) {
console.error('Service Worker registration failed:', error);
});
} }
</script> </script>
</body> </body>
</html> </html>

37
js/api.js Normal file
View File

@ -0,0 +1,37 @@
// api.js
export async function fetchCardSuggestions(query) {
try {
const response = await fetch(`https://api.scryfall.com/cards/autocomplete?q=${encodeURIComponent(query)}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
return data.data;
} catch (error) {
console.error('Fetch error:', error);
return [];
}
}
export async function fetchCardDetails(cardName, setCode) {
const response = await fetch(`https://api.scryfall.com/cards/named?exact=${encodeURIComponent(cardName)}&set=${encodeURIComponent(setCode)}`);
const data = await response.json();
if (data.object === 'error') {
return null;
}
return {
set: data.set.toUpperCase(),
card_id: data.collector_number,
mana_cost: data.mana_cost || 'N/A',
power: data.power || 'N/A',
toughness: data.toughness || 'N/A',
type: data.type_line.split('—')[0].trim(),
rarity: data.rarity || 'N/A',
price: data.prices.usd || '0.00',
imageUrl: data.image_uris ? data.image_uris.small : null,
name: data.name
};
}

68
js/card.js Normal file
View File

@ -0,0 +1,68 @@
// card.js
import { updateCardDetails, loadCollection } from './storage.js';
import { showCardPopup } from './popup.js';
export async function addCard(cardList) {
const cardName = document.getElementById('card-name').value;
const setCodeInput = document.getElementById('set-code');
const cardIdInput = document.getElementById('card-id');
let setCode = setCodeInput.value;
let cardId = cardIdInput.value;
const cardDetails = await fetchCardDetails(cardName, setCode);
if (cardDetails) {
if (!setCode) {
setCode = cardDetails.set;
setCodeInput.value = setCode;
}
if (!cardId) {
cardId = cardDetails.card_id;
cardIdInput.value = cardId;
}
const cardData = {
name: cardName,
set: setCode,
card_id: cardId,
quantity: document.getElementById('quantity').value,
foil: document.getElementById('foil').value,
mana_cost: cardDetails.mana_cost,
power: cardDetails.power,
toughness: cardDetails.toughness,
type: cardDetails.type,
rarity: cardDetails.rarity,
price: cardDetails.price
};
updateCardDetails(cardData);
cardList.push(cardData); // Add the card to the list
showCardPopup(cardData.name, cardDetails.imageUrl, cardDetails.price);
} else {
alert("Card not found with the given name.");
}
}
export async function fetchCardDetails(cardName, setCode) {
const response = await fetch(`https://api.scryfall.com/cards/named?exact=${encodeURIComponent(cardName)}&set=${encodeURIComponent(setCode)}`);
const data = await response.json();
if (data.object === 'error') {
return null;
}
return {
set: data.set.toUpperCase(),
card_id: data.collector_number,
mana_cost: data.mana_cost || 'N/A',
power: data.power || 'N/A',
toughness: data.toughness || 'N/A',
type: data.type_line.split('—')[0].trim(),
rarity: data.rarity || 'N/A',
price: data.prices.usd || '0.00',
imageUrl: data.image_uris ? data.image_uris.small : null,
name: data.name
};
}

44
js/csv.js Normal file
View File

@ -0,0 +1,44 @@
// csv.js
import { getRandomWordsFromFile } from './randomWords.js';
export function generateCSVContent(cardList) {
let csvContent = "Card Name,Set,Card ID,Quantity,Foil,Mana Type,Power,Toughness,Type,Rarity\n";
cardList.forEach(card => {
const escapedValues = [
card.name,
card.set,
card.card_id,
card.quantity,
card.foil,
card.mana_cost,
card.power,
card.toughness,
card.type,
card.rarity
].map(value => `"${value.replace(/"/g, '""')}"`); // Escape any existing quotes by doubling them
csvContent += escapedValues.join(",") + "\n";
});
return csvContent;
}
export async function downloadCSV(cardList) {
const csvContent = generateCSVContent(cardList);
const randomFileName = getRandomWordsFromFile();
// Create a blob with the CSV content
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
// Create a temporary link element
const link = document.createElement("a");
link.setAttribute("href", url);
link.setAttribute("download", `${randomFileName}.csv`);
document.body.appendChild(link);
// Trigger the download
link.click();
// Clean up and remove the link
document.body.removeChild(link);
}

93
js/main.js Normal file
View File

@ -0,0 +1,93 @@
// main.js
import { addCard } from './card.js';
import { downloadCSV } from './csv.js';
import { saveCollection, loadCollection, clearCollection } from './storage.js';
import { setTheme, applySavedTheme } from './theme.js';
import { fetchCardSuggestions } from './api.js';
document.addEventListener('DOMContentLoaded', () => {
applySavedTheme();
let cardList = loadCollection();
document.getElementById('add-card-button').addEventListener('click', () => {
addCard(cardList);
updateCSVContent(cardList);
});
document.getElementById('download-csv-button').addEventListener('click', () => downloadCSV(cardList));
document.getElementById('save-collection-button').addEventListener('click', () => saveCollection(cardList));
document.getElementById('clear-collection-button').addEventListener('click', () => {
if (confirm("Are you sure you want to clear your collection? This action cannot be undone.")) {
cardList = []; // Reset the card list
clearCollection(cardList);
updateCSVContent(cardList);
}
});
document.getElementById('card-name').addEventListener('input', async function() {
const query = this.value.trim();
if (query.length < 2) {
document.getElementById('suggestions').innerHTML = '';
return;
}
const suggestions = await fetchCardSuggestions(query);
const suggestionsDiv = document.getElementById('suggestions');
suggestionsDiv.innerHTML = '';
suggestions.forEach(suggestion => {
const div = document.createElement('div');
div.textContent = suggestion;
div.onclick = () => {
document.getElementById('card-name').value = suggestion;
suggestionsDiv.innerHTML = '';
};
suggestionsDiv.appendChild(div);
});
});
document.getElementById('sound-threshold').addEventListener('input', (event) => {
const soundThreshold = parseFloat(event.target.value);
localStorage.setItem('soundThreshold', soundThreshold);
});
document.getElementById('hamburger-menu').addEventListener('click', () => {
const menuContent = document.getElementById('menu-content');
menuContent.classList.toggle('show');
});
const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
if (isDarkMode) {
document.body.classList.add('dark-mode');
}
});
// Update the CSV content in the textarea
function updateCSVContent(cardList) {
// Sort the cardList by set and then by card ID
cardList.sort((a, b) => {
if (a.set < b.set) return -1;
if (a.set > b.set) return 1;
return parseInt(a.card_id) - parseInt(b.card_id);
});
let csvContent = "Card Name,Set,Card ID,Quantity,Foil,Mana Type,Power,Toughness,Type,Rarity\n";
cardList.forEach(card => {
const escapedValues = [
card.name || '',
card.set || '',
card.card_id || '',
card.quantity || '',
card.foil || '',
card.mana_type || '',
card.power || '',
card.toughness || '',
card.type || '',
card.rarity || ''
].map(value => `"${(value !== undefined ? value : '').replace(/"/g, '""')}"`);
csvContent += escapedValues.join(",") + "\n";
});
document.getElementById('csv-output').value = csvContent;
}

4
js/menu.js Normal file
View File

@ -0,0 +1,4 @@
export function toggleMenu() {
const menuContent = document.getElementById('menu-content');
menuContent.classList.toggle('show');
}

40
js/popup.js Normal file
View File

@ -0,0 +1,40 @@
// popup.js
export async function showCardPopup(cardName, imageUrl, price) {
const soundThreshold = parseFloat(localStorage.getItem('soundThreshold')) || 1.00;
const cardPopup = document.getElementById('card-popup');
cardPopup.innerHTML = `
<div style="font-family: 'Sorts Mill Goudy', serif; text-align: center;">
<strong>${cardName}</strong>
</div>
<img src="${imageUrl}" alt="${cardName}">
<div style="font-family: 'Sorts Mill Goudy', serif; text-align: center; margin-top: 10px;">
Price: $${price}
</div>
`;
cardPopup.style.display = 'block';
cardPopup.style.opacity = 1;
// Check if the price exceeds the sound threshold and play sound
if (parseFloat(price) >= soundThreshold) {
playSound(); // Play the sound if the price exceeds the threshold
}
// Dissolve the popup after a few seconds
setTimeout(() => {
cardPopup.style.opacity = 0;
setTimeout(() => {
cardPopup.style.display = 'none';
}, 500);
}, 3000);
}
function playSound() {
const audio = new Audio('assets/cash-money.mp3');
audio.play().then(() => {
console.log('Sound played successfully');
}).catch(error => {
console.error('Error playing sound:', error);
});
}

32
js/randomWords.js Normal file
View File

@ -0,0 +1,32 @@
// randomWords.js
export const randomWords = [
"nonstop", "detailed", "aspiring", "shock", "play", "bashful", "long", "quarter",
"six", "charge", "dock", "crabby", "dime", "wry", "story", "wiry", "rampant",
"return", "dare", "open", "shame", "many", "brush", "babies", "stem", "squeeze",
"judge", "heavenly", "defeated", "optimal", "invention", "object", "change",
"sink", "verdant", "jaded", "adjoining", "muddled", "switch", "helpless",
"motionless", "skirt", "shrug", "trip", "haircut", "oven", "lip", "habitual",
"yielding", "bag", "wheel", "attach", "ticket", "visit", "reflect", "suppose",
"present", "wound", "voyage", "real", "aunt", "religion", "redundant", "necessary",
"fail", "flower", "unpack", "join", "gamy", "tired", "welcome", "rightful", "jeans",
"obscene", "spring", "basket", "battle", "utter", "descriptive", "caring", "fry",
"resonant", "supply", "geese", "pets", "impulse", "scintillating", "tame", "release",
"tail", "depend", "lively", "nondescript", "punishment", "meek", "crooked",
"representative", "twist", "manage", "bored", "grotesque", "demonic", "camp",
"temporary", "coil", "passenger", "appliance", "clam", "smoggy", "tasteless", "guess",
"verse", "drab", "peep", "business", "paper", "female", "admire", "way", "moor",
"breezy", "opposite", "comparison", "tank", "suit", "ludicrous", "minister", "stiff",
"whine", "request", "camera", "internal", "improve", "unnatural", "decisive", "exist",
"grip", "electric", "bathe", "scandalous", "steer", "humdrum", "action", "rot",
"roll", "quartz", "amused", "sidewalk", "roll", "curve"
];
export function getRandomWordsFromFile() {
let randomWordsSelected = [];
for (let i = 0; i < 3; i++) {
const randomIndex = Math.floor(Math.random() * randomWords.length);
randomWordsSelected.push(randomWords[randomIndex]);
}
return randomWordsSelected.join('-');
}

24
js/storage.js Normal file
View File

@ -0,0 +1,24 @@
// storage.js
export function saveCollection(cardList) {
localStorage.setItem('cardList', JSON.stringify(cardList));
}
export function loadCollection() {
const savedCollection = localStorage.getItem('cardList');
if (savedCollection) {
return JSON.parse(savedCollection);
}
return [];
}
export function updateCardDetails(cardData) {
const cardList = loadCollection();
cardList.push(cardData);
saveCollection(cardList);
}
export function clearCollection(cardList) {
cardList.length = 0; // Clear the cardList array
saveCollection(cardList); // Save the empty list to localStorage
}

54
js/sw.js Normal file
View File

@ -0,0 +1,54 @@
// This is the service worker with the combined offline experience (Offline page + Offline copy of pages)
const CACHE = "pwabuilder-offline-page";
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js');
// TODO: replace the following with the correct offline fallback page i.e.: const offlineFallbackPage = "offline.html";
const offlineFallbackPage = "ToDo-replace-this-name.html";
self.addEventListener("message", (event) => {
if (event.data && event.data.type === "SKIP_WAITING") {
self.skipWaiting();
}
});
self.addEventListener('install', async (event) => {
event.waitUntil(
caches.open(CACHE)
.then((cache) => cache.add(offlineFallbackPage))
);
});
if (workbox.navigationPreload.isSupported()) {
workbox.navigationPreload.enable();
}
workbox.routing.registerRoute(
new RegExp('/*'),
new workbox.strategies.StaleWhileRevalidate({
cacheName: CACHE
})
);
self.addEventListener('fetch', (event) => {
if (event.request.mode === 'navigate') {
event.respondWith((async () => {
try {
const preloadResp = await event.preloadResponse;
if (preloadResp) {
return preloadResp;
}
const networkResp = await fetch(event.request);
return networkResp;
} catch (error) {
const cache = await caches.open(CACHE);
const cachedResp = await cache.match(offlineFallbackPage);
return cachedResp;
}
})());
}
});

19
js/theme.js Normal file
View File

@ -0,0 +1,19 @@
export function setTheme(theme) {
// Remove all theme classes first
document.body.classList.remove('theme-white', 'theme-blue', 'theme-black', 'theme-red', 'theme-green');
// Add the selected theme class if it exists
if (theme) {
document.body.classList.add(theme);
}
// Save the selected theme to localStorage
localStorage.setItem('selectedTheme', theme);
}
export function applySavedTheme() {
const savedTheme = localStorage.getItem('selectedTheme');
if (savedTheme) {
setTheme(savedTheme);
}
}

View File

@ -8,12 +8,12 @@
"description": "A PWA for generating CSV files with Magic: The Gathering card details", "description": "A PWA for generating CSV files with Magic: The Gathering card details",
"icons": [ "icons": [
{ {
"src": "icon.png", "src": "assets/icon.png",
"sizes": "192x192", "sizes": "192x192",
"type": "image/png" "type": "image/png"
}, },
{ {
"src": "icon.png", "src": "assets/icon.png",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png" "type": "image/png"
} }

View File

@ -8,8 +8,7 @@
<link href="https://fonts.googleapis.com/css2?family=Sorts+Mill+Goudy&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Sorts+Mill+Goudy&display=swap" rel="stylesheet">
<link rel="icon" href="icon.png" type="image/png"> <link rel="icon" href="icon.png" type="image/png">
<link href="//cdn.jsdelivr.net/npm/mana-font@latest/css/mana.css" rel="stylesheet" type="text/css" /> <link href="//cdn.jsdelivr.net/npm/mana-font@latest/css/mana.css" rel="stylesheet" type="text/css" />
<link rel="stylesheet" href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css">
</head> </head>
<body> <body>
<div class="container"> <div class="container">
@ -27,9 +26,5 @@
This app is not affiliated with Wizards of the Coast or Hasbro. All rights and trademarks are owned by their respective owners. This app is not affiliated with Wizards of the Coast or Hasbro. All rights and trademarks are owned by their respective owners.
</p> </p>
</footer> </footer>
<script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script>
<script>
mdc.autoInit(); // Initialize all MDC components
</script>
</body> </body>
</html> </html>