toadstoolTally/index.html

385 lines
22 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="sync-banner" id="syncBanner" onclick="openAuthModal()" title="Sign in to sync">Local-only mode</button>
<button class="header-btn hidden" id="installBtn" title="Install app"><i class="fa-solid fa-download"></i></button>
<button class="header-btn" id="settingsBtn" onclick="openSettingsModal()" title="Settings"><i class="fa-solid fa-gear"></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" id="patternFab" 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="settings-overlay" id="settingsOverlay">
<div class="settings-modal">
<div class="settings-head">
<h3 class="color-title">Settings</h3>
<button class="pattern-close" onclick="closeSettingsModal()">&times;</button>
</div>
<div class="settings-body">
<label class="settings-toggle">
<span>Dark mode</span>
<input type="checkbox" id="settingDarkMode" onchange="setDarkMode(this.checked)">
</label>
<p class="settings-note">Use a darker theme that is easier on the eyes at night.</p>
<label class="settings-toggle">
<span>Animations</span>
<input type="checkbox" id="settingAnimations" onchange="setAnimations(this.checked)">
</label>
<p class="settings-note">Toggle ambient motion like fireflies and micro-animations.</p>
</div>
</div>
</div>
<div class="auth-overlay" id="authOverlay">
<div class="auth-modal">
<div class="auth-modal-head">
<div>
<h3 class="color-title">Account</h3>
<p class="auth-subtext">Sign in to sync across devices.</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">Status: unknown</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" data-mode="reset" onclick="setAuthMode('reset')">Reset</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>
<div class="auth-actions">
<button type="button" class="modal-btn btn-secondary" onclick="setAuthMode('reset')">Forgot password?</button>
<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>
<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="resetContent" class="auth-tab-content">
<form class="auth-form" onsubmit="return requestPasswordReset(event)">
<label class="field-label" for="resetEmail">Email</label>
<input id="resetEmail" type="email" placeholder="you@example.com" autocomplete="email" required>
<div class="auth-actions">
<button type="button" class="modal-btn btn-cancel" onclick="setAuthMode('login')">Back to login</button>
<button type="submit" class="modal-btn btn-save">Send reset</button>
</div>
</form>
<hr class="auth-divider">
<form id="resetConfirmForm" class="auth-form" onsubmit="return confirmPasswordReset(event)">
<label class="field-label" for="resetToken">Reset Token</label>
<input id="resetToken" type="text" placeholder="Paste your reset token" autocomplete="one-time-code" required>
<label class="field-label" for="resetPassword">New Password</label>
<input id="resetPassword" type="password" placeholder="••••••••" autocomplete="new-password" required>
<label class="field-label" for="resetConfirmPassword">Confirm New Password</label>
<input id="resetConfirmPassword" type="password" placeholder="••••••••" autocomplete="new-password" required>
<div class="auth-actions">
<button type="submit" class="modal-btn btn-save">Update password</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="savePatternAndStartProject()" title="Save & Start Project"><i class="fa-solid fa-play"></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>
<label class="field-label" style="margin-top: 10px;">Finished Photos</label>
<div id="finishedPhotoList" class="finished-photo-list"></div>
<button class="secondary small" type="button" onclick="addFinishedPhoto()">Add Photo</button>
</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">
<div>
<h4>Pattern Steps</h4>
<p class="muted small">Build steps and rows in order. Collapse steps to stay focused.</p>
</div>
</div>
<div id="patternSteps"></div>
</div>
<div class="pattern-section" data-section="library">
<div class="pattern-steps-head">
<div>
<h4>Saved Patterns</h4>
<p class="muted small">Search, load, or start a project.</p>
</div>
<button class="primary" onclick="savePatternDraft()">Save Current Draft</button>
</div>
<div class="pattern-library-controls">
<input id="patternSearch" type="text" placeholder="Search patterns..." oninput="renderPatternLibrary()">
<select id="patternSort" onchange="renderPatternLibrary()">
<option value="recent">Most recent</option>
<option value="name">Name AZ</option>
</select>
</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?v=3"></script>
<footer class="footer-bg" aria-hidden="true"></footer>
</body>
</html>