Add woodland save/load modal and picker
This commit is contained in:
parent
a58c4c290c
commit
a6938f390b
109
assets/app.js
109
assets/app.js
@ -2,14 +2,24 @@
|
|||||||
let projects = JSON.parse(localStorage.getItem('crochetCounters')) || [];
|
let projects = JSON.parse(localStorage.getItem('crochetCounters')) || [];
|
||||||
// New Earthy/Woodland Palette extracted from image vibes
|
// New Earthy/Woodland Palette extracted from image vibes
|
||||||
const colors = [
|
const colors = [
|
||||||
'#8a6b52', // Oak
|
'#a17d63', // Soft oak
|
||||||
'#6f8b5f', // Moss
|
'#7a8c6a', // Moss sage
|
||||||
'#c28a5c', // Burnished amber
|
'#c7a272', // Warm amber
|
||||||
'#b27c7a', // Rose clay
|
'#b88b8a', // Rose clay
|
||||||
'#708a84', // Sagey teal
|
'#7b9189', // Sage teal
|
||||||
'#9d8b6f', // Wheat
|
'#aa9a7a', // Wheat linen
|
||||||
'#5b6d4f', // Deep forest
|
'#5f6d57', // Forest dusk
|
||||||
'#a36a5f' // Terra
|
'#b07d6f' // Terra blush
|
||||||
|
];
|
||||||
|
const oldColors = [
|
||||||
|
'#b56b54', // Rust/Mushroom
|
||||||
|
'#7a8b4f', // Olive Green
|
||||||
|
'#cba052', // Mustard/Daisy center
|
||||||
|
'#5f8a8b', // Muted Teal
|
||||||
|
'#8c6246', // Warm Wood tone
|
||||||
|
'#a87b8c', // Dusty Rose
|
||||||
|
'#4a5d43', // Deep Forest
|
||||||
|
'#9c7e63' // Taupe
|
||||||
];
|
];
|
||||||
|
|
||||||
// --- State Variables ---
|
// --- State Variables ---
|
||||||
@ -22,6 +32,11 @@ const hapticTick = () => { if ('vibrate' in navigator) navigator.vibrate(12); };
|
|||||||
const installBtn = document.getElementById('installBtn');
|
const installBtn = document.getElementById('installBtn');
|
||||||
const importInput = document.getElementById('importFile');
|
const importInput = document.getElementById('importFile');
|
||||||
const motionBtn = document.getElementById('motionBtn');
|
const motionBtn = document.getElementById('motionBtn');
|
||||||
|
const colorOverlay = document.getElementById('colorOverlay');
|
||||||
|
const colorGrid = document.getElementById('colorGrid');
|
||||||
|
const saveOverlay = document.getElementById('saveOverlay');
|
||||||
|
const saveList = document.getElementById('saveList');
|
||||||
|
let pendingSaveSelection = [];
|
||||||
let lastCountPulse = null;
|
let lastCountPulse = null;
|
||||||
let lastFinishedId = null;
|
let lastFinishedId = null;
|
||||||
let fireflyTimer = null;
|
let fireflyTimer = null;
|
||||||
@ -184,17 +199,39 @@ function toggleAnimations() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function exportData() {
|
function openColorPicker(pId, partId) {
|
||||||
|
if (!colorOverlay || !colorGrid) return;
|
||||||
|
const project = projects.find(p => p.id === pId);
|
||||||
|
const part = project.parts.find(pt => pt.id === partId);
|
||||||
|
colorGrid.innerHTML = colors.map(c => `
|
||||||
|
<button class="color-swatch" style="background:${c}" onclick="setPartColor(${pId}, ${partId}, '${c}'); closeColorPicker();"></button>
|
||||||
|
`).join('');
|
||||||
|
colorOverlay.classList.add('active');
|
||||||
|
colorOverlay.dataset.projectId = pId;
|
||||||
|
colorOverlay.dataset.partId = partId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeColorPicker() {
|
||||||
|
if (!colorOverlay) return;
|
||||||
|
colorOverlay.classList.remove('active');
|
||||||
|
colorGrid.innerHTML = '';
|
||||||
|
colorOverlay.dataset.projectId = '';
|
||||||
|
colorOverlay.dataset.partId = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportData(selectedProjects = projects) {
|
||||||
const payload = {
|
const payload = {
|
||||||
projects,
|
projects: selectedProjects,
|
||||||
isDarkMode,
|
isDarkMode,
|
||||||
animationsEnabled
|
animationsEnabled
|
||||||
};
|
};
|
||||||
|
const names = selectedProjects.map(p => p.name || 'Project').join('_').replace(/\s+/g, '-').slice(0, 50) || 'projects';
|
||||||
|
const filename = `toadstool_${names}.json`;
|
||||||
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
|
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = 'toadstool-counter-backup.json';
|
a.download = filename;
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
@ -236,6 +273,38 @@ if (importInput) {
|
|||||||
importInput.addEventListener('change', handleImport);
|
importInput.addEventListener('change', handleImport);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openSaveModal() {
|
||||||
|
if (!saveOverlay || !saveList) return;
|
||||||
|
saveList.innerHTML = '';
|
||||||
|
pendingSaveSelection = projects.map(p => p.id);
|
||||||
|
projects.forEach(p => {
|
||||||
|
const item = document.createElement('label');
|
||||||
|
item.className = 'save-item';
|
||||||
|
item.innerHTML = `
|
||||||
|
<input type="checkbox" checked data-id="${p.id}">
|
||||||
|
<span>${p.name}</span>
|
||||||
|
`;
|
||||||
|
saveList.appendChild(item);
|
||||||
|
});
|
||||||
|
saveOverlay.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeSaveModal() {
|
||||||
|
if (!saveOverlay) return;
|
||||||
|
saveOverlay.classList.remove('active');
|
||||||
|
saveList.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportSelected() {
|
||||||
|
if (!saveOverlay) return;
|
||||||
|
const inputs = saveList.querySelectorAll('input[type="checkbox"]');
|
||||||
|
const selectedIds = Array.from(inputs).filter(i => i.checked).map(i => Number(i.dataset.id));
|
||||||
|
if (selectedIds.length === 0) { closeSaveModal(); return; }
|
||||||
|
const selectedProjects = projects.filter(p => selectedIds.includes(p.id));
|
||||||
|
exportData(selectedProjects);
|
||||||
|
closeSaveModal();
|
||||||
|
}
|
||||||
|
|
||||||
// --- Firefly Animation ---
|
// --- Firefly Animation ---
|
||||||
function spawnFirefly({ markActive = false, source = 'ambient' } = {}) {
|
function spawnFirefly({ markActive = false, source = 'ambient' } = {}) {
|
||||||
const wrap = document.createElement('div');
|
const wrap = document.createElement('div');
|
||||||
@ -336,6 +405,16 @@ if (logoIcon) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (colorOverlay) {
|
||||||
|
colorOverlay.addEventListener('click', (e) => {
|
||||||
|
if (e.target === colorOverlay) closeColorPicker();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape' && colorOverlay && colorOverlay.classList.contains('active')) {
|
||||||
|
closeColorPicker();
|
||||||
|
}
|
||||||
|
});
|
||||||
const importBtn = document.getElementById('importBtn');
|
const importBtn = document.getElementById('importBtn');
|
||||||
if (importBtn) {
|
if (importBtn) {
|
||||||
importBtn.addEventListener('click', triggerImport);
|
importBtn.addEventListener('click', triggerImport);
|
||||||
@ -379,11 +458,15 @@ if (projects.length > 0) {
|
|||||||
projects.forEach((p, index) => {
|
projects.forEach((p, index) => {
|
||||||
if (!p.parts) { p.parts = []; changed = true; }
|
if (!p.parts) { p.parts = []; changed = true; }
|
||||||
if (!p.color) { p.color = colors[index % colors.length]; changed = true; }
|
if (!p.color) { p.color = colors[index % colors.length]; changed = true; }
|
||||||
|
const oldIdx = oldColors.indexOf(p.color);
|
||||||
|
if (oldIdx !== -1) { p.color = colors[oldIdx % colors.length]; changed = true; }
|
||||||
if (p.note === undefined) { p.note = ''; changed = true; }
|
if (p.note === undefined) { p.note = ''; changed = true; }
|
||||||
p.parts.forEach(pt => {
|
p.parts.forEach(pt => {
|
||||||
if (pt.max === undefined) { pt.max = null; changed = true; }
|
if (pt.max === undefined) { pt.max = null; changed = true; }
|
||||||
if (pt.note === undefined) { pt.note = ''; changed = true; }
|
if (pt.note === undefined) { pt.note = ''; changed = true; }
|
||||||
if (!pt.color) { pt.color = p.color; changed = true; }
|
if (!pt.color) { pt.color = p.color; changed = true; }
|
||||||
|
const oldPartIdx = oldColors.indexOf(pt.color);
|
||||||
|
if (oldPartIdx !== -1) { pt.color = colors[oldPartIdx % colors.length]; changed = true; }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (changed) { localStorage.setItem('crochetCounters', JSON.stringify(projects)); }
|
if (changed) { localStorage.setItem('crochetCounters', JSON.stringify(projects)); }
|
||||||
@ -628,7 +711,6 @@ function render() {
|
|||||||
const showSetMax = part.minimized ? 'hidden' : '';
|
const showSetMax = part.minimized ? 'hidden' : '';
|
||||||
const partNoteId = `part-note-${project.id}-${part.id}`;
|
const partNoteId = `part-note-${project.id}-${part.id}`;
|
||||||
const countId = `count-${part.id}`;
|
const countId = `count-${part.id}`;
|
||||||
const colorInputId = `color-${project.id}-${part.id}`;
|
|
||||||
const pulseClass = lastCountPulse && lastCountPulse.partId === part.id
|
const pulseClass = lastCountPulse && lastCountPulse.partId === part.id
|
||||||
? (lastCountPulse.dir === 'up' ? 'count-bump-up' : 'count-bump-down')
|
? (lastCountPulse.dir === 'up' ? 'count-bump-up' : 'count-bump-down')
|
||||||
: '';
|
: '';
|
||||||
@ -639,8 +721,7 @@ function render() {
|
|||||||
const actionsHtml = part.minimized
|
const actionsHtml = part.minimized
|
||||||
? `<div class="part-actions"><button class="icon-btn btn-toggle-part" onclick="togglePartMinimize(${project.id}, ${part.id})" title="Expand"><i class="fa-solid fa-chevron-down"></i></button></div>`
|
? `<div class="part-actions"><button class="icon-btn btn-toggle-part" onclick="togglePartMinimize(${project.id}, ${part.id})" title="Expand"><i class="fa-solid fa-chevron-down"></i></button></div>`
|
||||||
: `<div class="part-actions">
|
: `<div class="part-actions">
|
||||||
<button class="btn-color" style="--project-color: ${accent}" onclick="document.getElementById('${colorInputId}').click()" title="Set color"></button>
|
<button class="btn-color" style="--project-color: ${accent}" onclick="openColorPicker(${project.id}, ${part.id})" title="Set color"></button>
|
||||||
<input type="color" id="${colorInputId}" class="color-input" value="${accent}" onchange="setPartColor(${project.id}, ${part.id}, this.value)">
|
|
||||||
<button class="icon-btn btn-reset-part" onclick="resetCount(${project.id}, ${part.id})" ${isFinished || part.locked ? 'disabled' : ''}><i class="fa-solid fa-rotate-left"></i></button>
|
<button class="icon-btn btn-reset-part" onclick="resetCount(${project.id}, ${part.id})" ${isFinished || part.locked ? 'disabled' : ''}><i class="fa-solid fa-rotate-left"></i></button>
|
||||||
<button class="icon-btn btn-delete-part" onclick="deletePart(${project.id}, ${part.id})" ${lockDisabled}><i class="fa-solid fa-trash"></i></button>
|
<button class="icon-btn btn-delete-part" onclick="deletePart(${project.id}, ${part.id})" ${lockDisabled}><i class="fa-solid fa-trash"></i></button>
|
||||||
<button class="icon-btn btn-toggle-part" onclick="togglePartMinimize(${project.id}, ${part.id})" title="Minimize"><i class="fa-solid fa-chevron-down"></i></button>
|
<button class="icon-btn btn-toggle-part" onclick="togglePartMinimize(${project.id}, ${part.id})" title="Minimize"><i class="fa-solid fa-chevron-down"></i></button>
|
||||||
|
|||||||
@ -135,6 +135,80 @@ h1 {
|
|||||||
.header-btn.is-active { background: var(--header-text); color: var(--header-bg); }
|
.header-btn.is-active { background: var(--header-text); color: var(--header-bg); }
|
||||||
.hidden { display: none !important; }
|
.hidden { display: none !important; }
|
||||||
.hidden-input { display: none; }
|
.hidden-input { display: none; }
|
||||||
|
.color-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(44, 35, 25, 0.55);
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 210;
|
||||||
|
padding: 20px;
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
}
|
||||||
|
.color-overlay.active { display: flex; }
|
||||||
|
.color-modal {
|
||||||
|
background: var(--card-bg);
|
||||||
|
color: var(--text);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 18px 18px 14px;
|
||||||
|
width: min(420px, 90vw);
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.25);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.color-title { margin: 0 0 12px; font-family: 'Cormorant Garamond', Georgia, serif; }
|
||||||
|
.color-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(60px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.color-swatch {
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 2px solid var(--border);
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: inset 0 0 0 2px rgba(255,255,255,0.55);
|
||||||
|
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||||
|
}
|
||||||
|
.color-swatch:hover { transform: translateY(-1px) scale(1.02); box-shadow: inset 0 0 0 2px rgba(255,255,255,0.8); }
|
||||||
|
.color-swatch:active { transform: scale(0.98); }
|
||||||
|
|
||||||
|
.save-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(44, 35, 25, 0.55);
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 210;
|
||||||
|
padding: 20px;
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
}
|
||||||
|
.save-overlay.active { display: flex; }
|
||||||
|
.save-modal {
|
||||||
|
background: var(--card-bg);
|
||||||
|
color: var(--text);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 18px 18px 14px;
|
||||||
|
width: min(420px, 90vw);
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.25);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.save-subtext { margin: 0 0 8px; color: var(--text-muted); }
|
||||||
|
.save-list { display: grid; gap: 8px; max-height: 220px; overflow-y: auto; margin-bottom: 10px; }
|
||||||
|
.save-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--input-bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.save-item input { width: 18px; height: 18px; }
|
||||||
|
.save-actions { display: flex; justify-content: flex-end; gap: 8px; }
|
||||||
|
.icon-woodland { width: 22px; height: 22px; }
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
@ -226,7 +300,6 @@ h1 {
|
|||||||
box-shadow: inset 0 0 0 2px var(--card-bg);
|
box-shadow: inset 0 0 0 2px var(--card-bg);
|
||||||
}
|
}
|
||||||
.btn-color:hover { box-shadow: inset 0 0 0 2px var(--project-color); }
|
.btn-color:hover { box-shadow: inset 0 0 0 2px var(--project-color); }
|
||||||
.color-input { display: none; }
|
|
||||||
|
|
||||||
/* --- PART CARD --- */
|
/* --- PART CARD --- */
|
||||||
.part-card {
|
.part-card {
|
||||||
|
|||||||
33
index.html
33
index.html
@ -7,7 +7,7 @@
|
|||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@500;700&family=Quicksand:wght@400;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@500;700&family=Quicksand:wght@400;600;700&display=swap" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" integrity="sha512-4LrZ0Yqhgm/Vdyg0LJ5Rzc5x8g6f5seRTwOAyq1DmiIYQnqak44fUgWuxlHfwDfC0BNp8f7C0R60VNZ9sFlQmA==" crossorigin="anonymous" referrerpolicy="no-referrer">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer">
|
||||||
<link rel="icon" href="assets/icons/favicon.ico">
|
<link rel="icon" href="assets/icons/favicon.ico">
|
||||||
<link rel="icon" type="image/png" sizes="96x96" href="assets/icons/favicon-96x96.png">
|
<link rel="icon" type="image/png" sizes="96x96" href="assets/icons/favicon-96x96.png">
|
||||||
<link rel="icon" type="image/svg+xml" href="assets/icons/favicon.svg">
|
<link rel="icon" type="image/svg+xml" href="assets/icons/favicon.svg">
|
||||||
@ -28,8 +28,14 @@
|
|||||||
<button class="header-btn" id="motionBtn" onclick="toggleAnimations()" title="Toggle Animations"><i class="fa-solid fa-wand-magic-sparkles"></i></button>
|
<button class="header-btn" id="motionBtn" onclick="toggleAnimations()" title="Toggle Animations"><i class="fa-solid fa-wand-magic-sparkles"></i></button>
|
||||||
<button class="header-btn" id="themeBtn" onclick="toggleTheme()" title="Toggle Dark Mode"><i class="fa-solid fa-moon"></i></button>
|
<button class="header-btn" id="themeBtn" onclick="toggleTheme()" title="Toggle Dark Mode"><i class="fa-solid fa-moon"></i></button>
|
||||||
<button class="header-btn" id="focusBtn" onclick="toggleFocusMode()" title="Focus Mode (Keeps Screen On)"><i class="fa-solid fa-eye"></i></button>
|
<button class="header-btn" id="focusBtn" onclick="toggleFocusMode()" title="Focus Mode (Keeps Screen On)"><i class="fa-solid fa-eye"></i></button>
|
||||||
<button class="header-btn" id="exportBtn" onclick="exportData()" title="Export data"><i class="fa-solid fa-file-export"></i></button>
|
<button class="header-btn" id="saveLoadBtn" onclick="openSaveModal()" title="Save/Load">
|
||||||
<button class="header-btn" id="importBtn" title="Import data"><i class="fa-solid fa-file-import"></i></button>
|
<svg class="icon-woodland" viewBox="0 0 32 32" aria-hidden="true">
|
||||||
|
<path d="M8 18c0-5.5 4-11 8-12.5 3.5 1.7 8 7 8 12.5 0 4.5-3 7.5-8 7.5S8 22.5 8 18Z" fill="currentColor" opacity="0.92"/>
|
||||||
|
<path d="M16 9c1.8 1.5 3 4.2 3 6.8 0 3-1.4 5.7-3 6.7-1.6-1-3-3.7-3-6.7 0-2.6 1.2-5.3 3-6.8Z" fill="var(--card-bg)"/>
|
||||||
|
<path d="M18.5 7.5c-1.2.7-3.8 1-5.8.5-2-.6-2.8-1.9-2.8-1.9 2.5-1.4 4.9-1.9 8.6-1.4z" fill="currentColor" opacity="0.85"/>
|
||||||
|
<path d="M15 7l2 2M16 5v3" stroke="var(--card-bg)" stroke-width="1.4" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<input type="file" id="importFile" accept="application/json" class="hidden-input" />
|
<input type="file" id="importFile" accept="application/json" class="hidden-input" />
|
||||||
@ -49,6 +55,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="color-overlay" id="colorOverlay">
|
||||||
|
<div class="color-modal">
|
||||||
|
<h3 class="color-title">Pick a color</h3>
|
||||||
|
<div class="color-grid" id="colorGrid"></div>
|
||||||
|
<button class="modal-btn btn-cancel" onclick="closeColorPicker()">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="save-overlay" id="saveOverlay">
|
||||||
|
<div class="save-modal">
|
||||||
|
<h3 class="color-title">Save or Load</h3>
|
||||||
|
<p class="save-subtext">Choose projects to include:</p>
|
||||||
|
<div class="save-list" id="saveList"></div>
|
||||||
|
<div class="save-actions">
|
||||||
|
<button class="modal-btn btn-cancel" onclick="closeSaveModal()">Cancel</button>
|
||||||
|
<button class="modal-btn btn-save" onclick="exportSelected()">Save</button>
|
||||||
|
<button class="modal-btn btn-save" onclick="triggerImport()">Load</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="assets/app.js"></script>
|
<script src="assets/app.js"></script>
|
||||||
<footer class="footer-bg" aria-hidden="true"></footer>
|
<footer class="footer-bg" aria-hidden="true"></footer>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user