toadstoolTally/index.html
chris 84909ff4e0 Revamp Pattern Composer UI, fix Backup/Restore, and improve Image Uploads
- Refactored Pattern Composer UI with new tabs (Specs, Draft, Read, Shelf).
- Added support for multiple Yarns and Hooks with integrated color pickers.
- Improved Step drafting UX: reordered list/editor, added inline actions.
- Fixed Database Backup/Restore: switched to SQL dump/restore for robustness.
- Improved Image Uploads: added WebP optimization (with fallback) and preview display.
- Updated local dev setup: added live-server proxy config and concurrently script.
2025-12-15 21:53:04 -05:00

324 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Toadstool Cottage Counter</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600;700&family=Nunito: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" crossorigin="anonymous" referrerpolicy="no-referrer">
<link rel="icon" type="image/png" sizes="32x32" href="assets/icons/appicon-32x32.png">
<link rel="icon" type="image/png" sizes="128x128" href="assets/icons/appicon-128x128.png">
<link rel="apple-touch-icon" href="assets/icons/appicon-256x256.png">
<link rel="manifest" href="assets/site.webmanifest">
<meta name="theme-color" content="#e4e8d5">
<link rel="stylesheet" href="assets/style.css">
</head>
<body>
<header>
<div class="brand">
<img class="brand-icon" src="assets/icons/appicon-128x128.png" alt="Toadstool Cottage Counter icon">
<h1 id="appTitle">Toadstool Cottage Counter</h1>
</div>
<div class="header-controls">
<button class="header-btn hidden" id="installBtn" title="Install app"><i class="fa-solid fa-download"></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="focusBtn" onclick="toggleFocusMode()" title="Focus Mode (Keeps Screen On)"><i class="fa-solid fa-eye"></i></button>
<button class="header-btn" id="saveLoadBtn" onclick="openSaveModal()" title="Save/Load"><i class="fa-solid fa-floppy-disk"></i></button>
<button class="header-btn" id="authBtn" onclick="openAuthModal()" title="Sign in to sync"><i class="fa-solid fa-user"></i></button>
</div>
</header>
<input type="file" id="importFile" accept="application/json" class="hidden-input" />
<div class="container" id="app"></div>
<button class="fab" onclick="openModal('addProject')">+</button>
<button class="fab fab-pattern" onclick="openPatternComposer()" title="Open Pattern Composer"><i class="fa-solid fa-swatchbook"></i></button>
<div class="modal-overlay" id="modalOverlay">
<div class="modal-content">
<h3 class="modal-title" id="modalTitle">Title</h3>
<input type="text" class="modal-input" id="modalInput" autocomplete="off">
<div class="pattern-picker" id="patternPicker">
<label for="patternSelect">Pattern (optional)</label>
<select id="patternSelect">
<option value="">No pattern</option>
</select>
</div>
<div class="modal-actions">
<button class="modal-btn btn-cancel" onclick="closeModal()">Cancel</button>
<button class="modal-btn btn-save" onclick="saveModal()">Save</button>
</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>
<div class="color-custom">
<label for="customColorInput">Custom:</label>
<input type="color" id="customColorInput" />
</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 class="import-selection hidden" id="importSelection">
<p class="save-subtext">Imported file projects:</p>
<div class="save-list" id="importList"></div>
<div class="save-actions">
<button class="modal-btn btn-cancel" onclick="cancelImportSelection()">Cancel</button>
<button class="modal-btn btn-save" onclick="applyImportSelection()">Add Selected</button>
</div>
</div>
</div>
</div>
<div class="auth-overlay" id="authOverlay">
<div class="auth-modal">
<div class="auth-modal-head">
<div>
<h3 class="color-title">Welcome Back!</h3>
<p class="auth-subtext">Stay free forever. Sign in only if you want cloud backups and device switching.</p>
</div>
<button class="pattern-close" onclick="closeAuthModal()">&times;</button>
</div>
<div class="auth-status">
<span id="authStatusBadge" class="status-pill">Signed out</span>
<span id="authLastSync" class="status-subtext">Last sync: never</span>
</div>
<div class="auth-tabs">
<button class="auth-tab active" data-mode="login" onclick="setAuthMode('login')">Login</button>
<button class="auth-tab" data-mode="signup" onclick="setAuthMode('signup')">Sign up</button>
<button class="auth-tab" id="profileTabBtn" data-mode="profile" onclick="setAuthMode('profile')" style="display:none;">Profile</button>
<button class="auth-tab" id="adminTabBtn" data-mode="admin" onclick="setAuthMode('admin')" style="display:none;">Admin</button>
</div>
<div class="auth-content">
<div id="loginContent" class="auth-tab-content active">
<form class="auth-form" onsubmit="return submitAuth(event, 'login')">
<label class="field-label" for="loginEmail">Email</label>
<input id="loginEmail" type="email" placeholder="you@example.com" autocomplete="email" required>
<label class="field-label" for="loginPassword">Password</label>
<input id="loginPassword" type="password" placeholder="••••••••" autocomplete="current-password" required>
<p class="auth-hint">Cloud sync is not required. Offline data stays on this device.</p>
<div class="auth-actions">
<button type="button" class="modal-btn btn-cancel" onclick="closeAuthModal()">Cancel</button>
<button type="submit" class="modal-btn btn-save">Login</button>
</div>
</form>
</div>
<div id="signupContent" class="auth-tab-content">
<form class="auth-form" onsubmit="return submitAuth(event, 'signup')">
<label class="field-label" for="signupEmail">Email</label>
<input id="signupEmail" type="email" placeholder="you@example.com" autocomplete="email" required>
<label class="field-label" for="signupPassword">Password</label>
<input id="signupPassword" type="password" placeholder="••••••••" autocomplete="new-password" required>
<label class="field-label" for="signupConfirmPassword">Confirm Password</label>
<input id="signupConfirmPassword" type="password" placeholder="••••••••" autocomplete="new-password" required>
<p class="auth-hint">When enabled, well sync projects/patterns securely.</p>
<div class="auth-actions">
<button type="button" class="modal-btn btn-cancel" onclick="closeAuthModal()">Cancel</button>
<button type="submit" class="modal-btn btn-save">Sign Up</button>
</div>
</form>
</div>
<div id="adminContent" class="auth-tab-content">
<div id="adminPanel" class="admin-panel">
<h4>Pending Approvals</h4>
<div class="admin-actions">
<button type="button" class="modal-btn btn-secondary" onclick="fetchPendingUsers()">Refresh Pending</button>
</div>
<div id="pendingList" class="admin-list"></div>
<h4 style="margin-top:16px;">User Management</h4>
<div class="admin-actions">
<button type="button" class="modal-btn btn-secondary" onclick="fetchAllUsers()">Load All Users</button>
</div>
<div id="allUsersList" class="admin-list"></div>
<h4 style="margin-top:16px;">System Data</h4>
<div class="admin-actions">
<button type="button" class="modal-btn btn-secondary" onclick="downloadBackup()">Backup Data</button>
<label class="modal-btn btn-secondary">
Restore
<input type="file" id="restoreInput" accept="application/json,application/sql,.sql" style="display:none;" onchange="uploadRestore(event)">
</label>
</div>
</div>
</div>
</div>
<form class="auth-profile" onsubmit="return saveProfile(event)">
<h4 class="auth-section-title">Profile Settings</h4>
<div class="auth-profile-body">
<label class="field-label" for="authDisplayName">Display Name</label>
<input id="authDisplayName" type="text" placeholder="Your name">
<label class="field-label" for="authNote">Profile Note</label>
<textarea id="authNote" rows="2" placeholder="Add a note for your patterns..."></textarea>
</div>
<div class="auth-sync-section">
<p class="auth-hint">Sync your projects to the cloud to access them anywhere.</p>
<button type="button" class="modal-btn btn-secondary" onclick="autoSync()"><i class="fa-solid fa-rotate"></i> Sync Now</button>
</div>
<div class="auth-actions modal-footer">
<button type="button" class="modal-btn btn-cancel danger-text" onclick="logoutAuth()">Log Out</button>
<button type="submit" class="modal-btn btn-save">Save Changes</button>
</div>
</form>
</div>
</div>
<div class="pattern-overlay" id="patternOverlay">
<div class="pattern-sheet">
<div class="pattern-sheet-header">
<div class="header-main">
<h2 class="pattern-sheet-title">Pattern Composer</h2>
<div class="pattern-modes">
<button class="pattern-mode" data-mode="crochet" onclick="setPatternMode('crochet')">Crochet</button>
<button class="pattern-mode" data-mode="knit" onclick="setPatternMode('knit')">Knit</button>
</div>
</div>
<div class="header-actions">
<span class="pattern-save-indicator" id="patternSaveIndicator">Saved</span>
<button class="icon-action" onclick="savePatternDraft()" title="Save to Basket"><i class="fa-solid fa-bookmark"></i></button>
<button class="icon-action" onclick="exportPatternPDF()" title="Export PDF"><i class="fa-solid fa-file-pdf"></i></button>
<button class="icon-action" onclick="sharePattern()" title="Share Link"><i class="fa-solid fa-link"></i></button>
<button class="icon-action danger" onclick="clearPatternOutput()" title="Clear Draft"><i class="fa-solid fa-eraser"></i></button>
<button class="pattern-close" onclick="closePatternComposer()">&times;</button>
</div>
</div>
<div class="pattern-nav">
<button class="nav-item active" data-tab="info" onclick="showPatternTab('info')">Specs</button>
<button class="nav-item" data-tab="steps" onclick="showPatternTab('steps')">Draft</button>
<button class="nav-item" data-tab="view" onclick="showPatternTab('view')">Read</button>
<button class="nav-item" data-tab="library" onclick="showPatternTab('library')">Shelf</button>
</div>
<div class="pattern-body">
<div class="pattern-section active" data-section="info">
<div class="card-grid">
<div class="input-card">
<h4 class="card-head">Basic Details</h4>
<div class="form-row">
<label class="field-label" for="patternTitle">Title</label>
<input id="patternTitle" type="text" placeholder="e.g., Baby Fox Plush">
</div>
<div class="form-row">
<label class="field-label" for="patternDesigner">Designer</label>
<input id="patternDesigner" type="text" placeholder="Your name or shop">
</div>
</div>
<div class="input-card">
<h4 class="card-head">Yarn & Tools</h4>
<label class="field-label">Yarns</label>
<div id="yarnList" class="yarn-list-container">
<!-- Populated by JS -->
</div>
<label class="field-label" style="margin-top: 10px;">Hooks / Needles</label>
<div id="hookList" class="hook-list-container">
<!-- Populated by JS -->
</div>
<div class="field-group-inline" style="margin-top: 10px;">
<div>
<label class="field-label" for="patternGaugeSts">Sts / 4in</label>
<input id="patternGaugeSts" type="text" placeholder="e.g., 16 sc">
</div>
<div>
<label class="field-label" for="patternGaugeRows">Rows / 4in</label>
<input id="patternGaugeRows" type="text" placeholder="e.g., 18 rows">
</div>
</div>
<label class="field-label" for="patternGauge">Gauge Notes</label>
<textarea id="patternGauge" rows="2" placeholder="Magic ring start; or any extra gauge notes"></textarea>
</div>
<div class="input-card full-width">
<h4 class="card-head">Materials & Notes</h4>
<label class="field-label" for="patternMaterials">Materials (one per line)</label>
<textarea id="patternMaterials" rows="3" placeholder="• 1 skein Worsted Weight Yarn (Main Color)&#10;• 4.0mm Hook&#10;• Tapestry Needle&#10;• Polyester Fiberfill"></textarea>
<label class="field-label" for="patternNotes">Notes / Finishing</label>
<textarea id="patternNotes" rows="2" placeholder="Assembly, finishing, safety warnings, credits..."></textarea>
</div>
<div class="input-card full-width">
<div class="abbrev-head">
<h4 class="card-head" style="margin:0;">Abbreviations</h4>
<button class="secondary small" onclick="openAbbrevModal()">Edit / Add</button>
</div>
<div id="abbrevSummary" class="selected-abbrev">
<!-- Selected pills will appear here -->
<span class="text-muted" style="font-size: 0.9rem;">No stitches selected.</span>
</div>
<textarea id="patternAbbrev" rows="3" placeholder="Selected abbreviations will appear here..." readonly></textarea>
<label class="field-label" for="patternStitches">Special Stitches / Notes</label>
<textarea id="patternStitches" rows="2" placeholder="Magic ring: ...&#10;Invisible decrease: ..."></textarea>
</div>
</div>
<div class="pattern-footer-actions">
<button class="secondary" onclick="importPatternJSON()">Import JSON</button>
</div>
</div>
<div class="pattern-section" data-section="steps">
<div class="pattern-steps-head">
<h4>Pattern Steps</h4>
</div>
<div id="patternSteps"></div>
</div>
<div class="pattern-section" data-section="library">
<div class="pattern-steps-head">
<h4>Saved Patterns</h4>
<button class="primary" onclick="savePatternDraft()">Save Current Draft</button>
</div>
<div id="patternLibrary" class="pattern-library"></div>
</div>
<div class="pattern-section" data-section="view">
<div id="patternView" class="pattern-view"></div>
</div>
</div>
</div>
</div>
<div class="modal-overlay" id="abbrevModal">
<div class="modal-content" style="width: min(800px, 94vw); max-height: 85vh; display: flex; flex-direction: column;">
<h3 class="modal-title">Select Abbreviations</h3>
<div class="abbrev-controls" style="margin-bottom: 12px;">
<input type="text" id="abbrevSearch" placeholder="Search stitches..." oninput="filterAbbrev()">
<button class="secondary small" onclick="loadDefaultAbbrev()">Reset to Defaults</button>
</div>
<div id="patternAbbrevList" class="abbrev-grid" style="overflow-y: auto; flex: 1;"></div>
<div class="modal-actions" style="margin-top: 12px; border-top: 1px solid var(--border); padding-top: 12px;">
<button class="modal-btn btn-save" onclick="closeAbbrevModal()">Done</button>
</div>
</div>
</div>
<script src="assets/app.js"></script>
<footer class="footer-bg" aria-hidden="true"></footer>
</body>
</html>