mirror of
https://gitlab.com/Zer0interrupt/recipe-manager.git
synced 2026-11-01 23:24:11 -05:00
Mobile Optimizations
This commit is contained in:
parent
02f0ecb587
commit
b9016991eb
4 changed files with 693 additions and 12 deletions
117
public/app.js
117
public/app.js
|
|
@ -117,7 +117,22 @@ const elements = {
|
|||
loadingOverlay: document.getElementById('loading-overlay'),
|
||||
loaderStepFetch: document.getElementById('loader-step-fetch'),
|
||||
loaderStepParse: document.getElementById('loader-step-parse'),
|
||||
loaderStepImages: document.getElementById('loader-step-images')
|
||||
loaderStepImages: document.getElementById('loader-step-images'),
|
||||
|
||||
// Mobile Components
|
||||
fabAddRecipe: document.getElementById('mobile-header-btn-add'),
|
||||
mobileTabCookbook: document.getElementById('mobile-header-tab-cookbook'),
|
||||
mobileTabCommunity: document.getElementById('mobile-header-tab-community'),
|
||||
mobileTabProfile: document.getElementById('mobile-header-btn-profile'),
|
||||
mobileProfileModal: document.getElementById('mobile-profile-modal'),
|
||||
mobileProfileUsername: document.getElementById('mobile-profile-username'),
|
||||
mobileProfileRole: document.getElementById('mobile-profile-role'),
|
||||
mobileBtnThemeToggle: document.getElementById('mobile-btn-theme-toggle'),
|
||||
mobileBtnSettings: document.getElementById('mobile-btn-settings'),
|
||||
mobileBtnAdmin: document.getElementById('mobile-btn-admin'),
|
||||
mobileBtnLogout: document.getElementById('mobile-btn-logout'),
|
||||
headerLogo: document.getElementById('header-logo'),
|
||||
libraryViewTitle: document.getElementById('library-view-title')
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------
|
||||
|
|
@ -211,6 +226,26 @@ function registerEvents() {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Mobile UI Events
|
||||
elements.mobileTabCookbook.addEventListener('click', () => switchTab('my-cookbook'));
|
||||
elements.mobileTabCommunity.addEventListener('click', () => switchTab('community-vault'));
|
||||
elements.mobileTabProfile.addEventListener('click', openMobileProfile);
|
||||
elements.headerLogo.addEventListener('click', () => switchTab('my-cookbook'));
|
||||
elements.fabAddRecipe.addEventListener('click', handleFabClick);
|
||||
elements.mobileBtnThemeToggle.addEventListener('click', toggleTheme);
|
||||
elements.mobileBtnSettings.addEventListener('click', () => {
|
||||
closeModal('mobile-profile-modal');
|
||||
openSettingsModal();
|
||||
});
|
||||
elements.mobileBtnAdmin.addEventListener('click', () => {
|
||||
closeModal('mobile-profile-modal');
|
||||
switchTab('user-admin');
|
||||
});
|
||||
elements.mobileBtnLogout.addEventListener('click', () => {
|
||||
closeModal('mobile-profile-modal');
|
||||
logout();
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
|
|
@ -472,7 +507,7 @@ async function deleteActiveRecipe() {
|
|||
}
|
||||
}
|
||||
|
||||
// Clone a Community Vault recipe to current user's collection
|
||||
// Clone a Shared Cookbook recipe to current user's collection
|
||||
async function cloneActiveRecipe() {
|
||||
if (!activeRecipe) return;
|
||||
|
||||
|
|
@ -646,7 +681,13 @@ function renderLibrary() {
|
|||
|
||||
let imgBlock = '';
|
||||
if (recipe.image) {
|
||||
imgBlock = `<img src="${recipe.image}" class="recipe-card-img" alt="${recipe.title}" loading="lazy">`;
|
||||
imgBlock = `
|
||||
<img src="${recipe.image}" class="recipe-card-img" alt="${recipe.title}" loading="lazy" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
|
||||
<div class="recipe-card-img-placeholder" style="display: none;">
|
||||
<i data-lucide="cooking-pot"></i>
|
||||
<span>No dish photo</span>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
imgBlock = `
|
||||
<div class="recipe-card-img-placeholder">
|
||||
|
|
@ -1027,8 +1068,10 @@ function initAuth() {
|
|||
// Toggle Admin visibility
|
||||
if (currentUser.isAdmin) {
|
||||
elements.tabUserAdmin.style.display = 'flex';
|
||||
elements.mobileBtnAdmin.style.display = 'block';
|
||||
} else {
|
||||
elements.tabUserAdmin.style.display = 'none';
|
||||
elements.mobileBtnAdmin.style.display = 'none';
|
||||
}
|
||||
|
||||
// Reset view to My Cookbook on init
|
||||
|
|
@ -1073,8 +1116,10 @@ async function handleLogin(e) {
|
|||
// Toggle Admin visibility
|
||||
if (currentUser.isAdmin) {
|
||||
elements.tabUserAdmin.style.display = 'flex';
|
||||
elements.mobileBtnAdmin.style.display = 'block';
|
||||
} else {
|
||||
elements.tabUserAdmin.style.display = 'none';
|
||||
elements.mobileBtnAdmin.style.display = 'none';
|
||||
}
|
||||
|
||||
elements.loginForm.reset();
|
||||
|
|
@ -1094,7 +1139,7 @@ function logout() {
|
|||
elements.authModal.classList.add('active');
|
||||
elements.authModal.style.display = 'flex';
|
||||
elements.userBadgeContainer.style.display = 'none';
|
||||
elements.tabUserAdmin.style.display = 'none';
|
||||
elements.mobileBtnAdmin.style.display = 'none';
|
||||
allRecipes = [];
|
||||
|
||||
// Reset tabs visual state
|
||||
|
|
@ -1119,6 +1164,11 @@ function switchTab(tabName) {
|
|||
elements.tabUserAdmin.classList.remove('btn-primary');
|
||||
elements.tabUserAdmin.classList.add('btn-outline');
|
||||
|
||||
// Mobile Tab Bar sync
|
||||
elements.mobileTabCookbook.classList.remove('active');
|
||||
elements.mobileTabCommunity.classList.remove('active');
|
||||
elements.mobileTabProfile.classList.remove('active');
|
||||
|
||||
elements.recipesGrid.style.display = 'none';
|
||||
elements.emptyState.style.display = 'none';
|
||||
elements.userAdminPanel.style.display = 'none';
|
||||
|
|
@ -1126,19 +1176,33 @@ function switchTab(tabName) {
|
|||
const filters = elements.librarySearch.closest('.filter-controls');
|
||||
if (filters) filters.style.display = 'flex';
|
||||
|
||||
// Update Dynamic Mobile Header Title
|
||||
if (elements.libraryViewTitle) {
|
||||
if (tabName === 'my-cookbook') {
|
||||
elements.libraryViewTitle.textContent = 'My Cookbook';
|
||||
} else if (tabName === 'community-vault') {
|
||||
elements.libraryViewTitle.textContent = 'Shared Cookbook';
|
||||
} else if (tabName === 'user-admin') {
|
||||
elements.libraryViewTitle.textContent = 'User Administration';
|
||||
}
|
||||
}
|
||||
|
||||
if (tabName === 'my-cookbook') {
|
||||
elements.tabMyCookbook.classList.add('active');
|
||||
elements.mobileTabCookbook.classList.add('active');
|
||||
elements.recipesGrid.style.display = 'grid';
|
||||
activeTab = 'my-cookbook';
|
||||
fetchRecipes();
|
||||
} else if (tabName === 'community-vault') {
|
||||
elements.tabCommunityVault.classList.add('active');
|
||||
elements.mobileTabCommunity.classList.add('active');
|
||||
elements.recipesGrid.style.display = 'grid';
|
||||
activeTab = 'community-vault';
|
||||
fetchRecipes();
|
||||
} else if (tabName === 'user-admin') {
|
||||
elements.tabUserAdmin.classList.add('btn-primary');
|
||||
elements.tabUserAdmin.classList.remove('btn-outline');
|
||||
elements.mobileTabProfile.classList.add('active');
|
||||
elements.userAdminPanel.style.display = 'block';
|
||||
if (filters) filters.style.display = 'none';
|
||||
activeTab = 'user-admin';
|
||||
|
|
@ -1654,3 +1718,48 @@ async function scanRecipeFile(file) {
|
|||
alert(`Scanning Failed: ${error.message}\n\nPlease verify your internet connection and check that your Gemini API Key is correct.`);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// MOBILE PORTAL CONTROLS & GESTURES
|
||||
// -------------------------------------------------------------
|
||||
|
||||
function openMobileProfile() {
|
||||
if (!currentUser) return;
|
||||
|
||||
elements.mobileProfileUsername.textContent = `@${currentUser.username}`;
|
||||
|
||||
// Set role text
|
||||
let roleText = 'Chef Account';
|
||||
if (currentUser.isAdmin) {
|
||||
roleText = 'Administrator';
|
||||
} else if (currentUser.isRecipeManager) {
|
||||
roleText = 'Recipe Manager';
|
||||
}
|
||||
elements.mobileProfileRole.textContent = roleText;
|
||||
|
||||
// Handle theme icon inside profile drawer
|
||||
const isDark = document.body.classList.contains('dark-theme');
|
||||
const sunIcon = elements.mobileBtnThemeToggle.querySelector('.sun-icon');
|
||||
const moonIcon = elements.mobileBtnThemeToggle.querySelector('.moon-icon');
|
||||
if (sunIcon && moonIcon) {
|
||||
if (isDark) {
|
||||
sunIcon.style.display = 'block';
|
||||
moonIcon.style.display = 'none';
|
||||
} else {
|
||||
sunIcon.style.display = 'none';
|
||||
moonIcon.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
openModal('mobile-profile-modal');
|
||||
}
|
||||
|
||||
function handleFabClick() {
|
||||
// If not on cookbook tab, switch back to list first
|
||||
if (activeTab !== 'my-cookbook') {
|
||||
switchTab('my-cookbook');
|
||||
}
|
||||
// Open the manual recipe creator
|
||||
openManualAdd();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,12 +16,13 @@
|
|||
<body class="dark-theme">
|
||||
<!-- Top Navigation -->
|
||||
<header class="app-header">
|
||||
<div class="header-logo" style="cursor: pointer;" onclick="switchTab('my-cookbook')">
|
||||
<div class="header-logo" style="cursor: pointer;" id="header-logo">
|
||||
<div class="logo-icon"><i data-lucide="cooking-pot"></i></div>
|
||||
<span class="logo-text">Fork<span>Folio</span></span>
|
||||
</div>
|
||||
|
||||
<div class="header-actions">
|
||||
<!-- Desktop Actions -->
|
||||
<div class="user-badge-container" id="user-badge-container" style="display: none;">
|
||||
<span class="user-display"><i data-lucide="user"></i> <span id="user-display-name">@chef</span></span>
|
||||
<button class="btn btn-icon-round" id="btn-settings" title="Application Settings"><i data-lucide="settings"></i></button>
|
||||
|
|
@ -37,6 +38,22 @@
|
|||
<i data-lucide="sun" class="sun-icon"></i>
|
||||
<i data-lucide="moon" class="moon-icon"></i>
|
||||
</button>
|
||||
|
||||
<!-- Mobile Consolidated Header Group -->
|
||||
<div class="mobile-header-group" id="mobile-header-group" style="display: none;">
|
||||
<button class="mobile-header-tab active" id="mobile-header-tab-cookbook" title="My Cookbook">
|
||||
<i data-lucide="book-open"></i>
|
||||
</button>
|
||||
<button class="mobile-header-tab" id="mobile-header-tab-community" title="Shared Cookbook">
|
||||
<i data-lucide="globe"></i>
|
||||
</button>
|
||||
<button class="mobile-header-btn-add" id="mobile-header-btn-add" title="Add Recipe">
|
||||
<i data-lucide="plus"></i>
|
||||
</button>
|
||||
<button class="mobile-header-tab" id="mobile-header-btn-profile" title="Chef Profile">
|
||||
<i data-lucide="user"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
|
@ -74,7 +91,7 @@
|
|||
<i data-lucide="book-open"></i> <span>My Cookbook</span>
|
||||
</button>
|
||||
<button class="dashboard-tab-btn" id="tab-community-vault">
|
||||
<i data-lucide="globe"></i> <span>Community Vault</span>
|
||||
<i data-lucide="globe"></i> <span>Shared Cookbook</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -87,6 +104,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dynamic Mobile Section Title -->
|
||||
<h2 class="library-view-title" id="library-view-title" style="display: none;">My Cookbook</h2>
|
||||
|
||||
<!-- Recipes Grid -->
|
||||
<div class="recipes-grid" id="recipes-grid">
|
||||
<!-- Recipe cards will be loaded here dynamically -->
|
||||
|
|
@ -95,7 +115,7 @@
|
|||
<!-- Empty State -->
|
||||
<div class="empty-state" id="empty-state">
|
||||
<div class="empty-icon"><i data-lucide="folder-open"></i></div>
|
||||
<h3>Your vault is empty</h3>
|
||||
<h3>Your cookbook is empty</h3>
|
||||
<p>Insert a cooking web link above or create one manually to build your digital recipe collections.</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -193,7 +213,7 @@
|
|||
<input type="checkbox" id="edit-is-public" checked>
|
||||
<span class="checkbox-label"><i data-lucide="globe" style="width: 14px; height: 14px; display: inline-block; vertical-align: middle; margin-right: 4px;"></i> Share with ForkFolio Community (Public)</span>
|
||||
</label>
|
||||
<p class="form-help text-sm opacity-80" style="margin-top: 4px; font-size: 0.8rem; color: var(--text-muted);">If enabled, this recipe will be shared to the global Community Vault feed for everyone to view and clone.</p>
|
||||
<p class="form-help text-sm opacity-80" style="margin-top: 4px; font-size: 0.8rem; color: var(--text-muted);">If enabled, this recipe will be shared to the global Shared Cookbook feed for everyone to view and clone.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-grid-2">
|
||||
|
|
@ -450,11 +470,11 @@
|
|||
<label class="checkbox-container" style="align-items: flex-start;">
|
||||
<input type="checkbox" id="delete-user-preserve-recipes" checked>
|
||||
<span class="checkbox-label" style="font-size: 0.9rem; line-height: 1.4; color: var(--text-main); font-weight: 500;">
|
||||
<i data-lucide="globe" style="width: 14px; height: 14px; display: inline-block; vertical-align: middle; margin-right: 4px;"></i> Keep recipes in Community Vault?
|
||||
<i data-lucide="globe" style="width: 14px; height: 14px; display: inline-block; vertical-align: middle; margin-right: 4px;"></i> Keep recipes in Shared Cookbook?
|
||||
</span>
|
||||
</label>
|
||||
<p style="font-size: 0.75rem; color: var(--text-muted); margin-top: 6px; padding-left: 30px; margin-bottom: 0;">
|
||||
If enabled, this chef's private recipes will be transferred to a public archive in the Community Vault. If disabled, all recipes owned by this chef will be purged.
|
||||
If enabled, this chef's private recipes will be transferred to a public archive in the Shared Cookbook. If disabled, all recipes owned by this chef will be purged.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -542,6 +562,50 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- MOBILE SYSTEM COMPONENTS -->
|
||||
|
||||
<!-- Mobile Profile Drawer Sheet -->
|
||||
<div class="modal-overlay" id="mobile-profile-modal">
|
||||
<div class="modal-box max-width-xs mobile-profile-sheet">
|
||||
<div class="bottom-sheet-handle"></div>
|
||||
<div class="modal-header">
|
||||
<h3>Chef Profile</h3>
|
||||
<button class="close-btn" onclick="closeModal('mobile-profile-modal')"><i data-lucide="x"></i></button>
|
||||
</div>
|
||||
<div class="modal-body text-center" style="display: flex; flex-direction: column; align-items: center; gap: 16px;">
|
||||
<div class="profile-avatar-large">
|
||||
<i data-lucide="user"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="profile-username" id="mobile-profile-username" style="font-size: 1.6rem; color: var(--text-main); margin-bottom: 4px;">@chef</h2>
|
||||
<span class="admin-role-badge chef" id="mobile-profile-role" style="font-size: 0.8rem; padding: 4px 10px; border-radius: var(--border-radius-full);">Chef Account</span>
|
||||
</div>
|
||||
|
||||
<div class="mobile-profile-actions" style="width: 100%; display: flex; flex-direction: column; gap: 12px; margin-top: 8px;">
|
||||
<button class="btn btn-outline btn-full-width" id="mobile-btn-theme-toggle" style="justify-content: flex-start; padding-left: 20px;">
|
||||
<i data-lucide="sun" class="sun-icon"></i>
|
||||
<i data-lucide="moon" class="moon-icon" style="display: none;"></i>
|
||||
<span style="margin-left: 8px;">Toggle Theme</span>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-outline btn-full-width" id="mobile-btn-settings" style="justify-content: flex-start; padding-left: 20px;">
|
||||
<i data-lucide="settings"></i>
|
||||
<span style="margin-left: 8px;">Application Settings</span>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-outline btn-full-width" id="mobile-btn-admin" style="display: none; justify-content: flex-start; padding-left: 20px; border-color: var(--primary); color: var(--primary);">
|
||||
<i data-lucide="shield"></i>
|
||||
<span style="margin-left: 8px;">User Admin Panel</span>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-danger btn-full-width" id="mobile-btn-logout" style="justify-content: flex-start; padding-left: 20px; background-color: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.2); color: var(--danger);">
|
||||
<i data-lucide="log-out"></i>
|
||||
<span style="margin-left: 8px;">Log Out</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Initialize Lucide Icons initially
|
||||
|
|
|
|||
508
public/style.css
508
public/style.css
|
|
@ -1884,3 +1884,511 @@ h1, h2, h3, h4, h5, h6 {
|
|||
border: 1px dashed rgba(239, 68, 68, 0.35);
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
/* =============================================================
|
||||
MOBILE DESIGN AND NAVIGATION UPGRADES (HIGH FIDELITY UX)
|
||||
============================================================= */
|
||||
|
||||
/* Layout adaptation variables */
|
||||
:root {
|
||||
--bottom-nav-height: 68px;
|
||||
--fab-size: 56px;
|
||||
}
|
||||
|
||||
/* Default Mobile Elements Hidden on Desktop */
|
||||
.mobile-header-group,
|
||||
.bottom-sheet-handle,
|
||||
.profile-avatar-large {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Mobile responsive media queries */
|
||||
@media (max-width: 768px) {
|
||||
/* Define layout safe offsets */
|
||||
body {
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* Compact Header */
|
||||
.app-header {
|
||||
padding: 12px 16px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
/* Hide desktop action buttons in header */
|
||||
.app-header #btn-manual-add,
|
||||
.app-header .user-badge-container,
|
||||
.app-header #tab-user-admin {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Compact Hero */
|
||||
.app-container {
|
||||
padding: 16px 16px 40px 16px;
|
||||
}
|
||||
|
||||
.scraping-hero {
|
||||
margin-bottom: 24px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.hero-content h1 {
|
||||
font-size: 1.8rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.hero-content p {
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.url-input-form {
|
||||
flex-direction: column;
|
||||
padding: 6px;
|
||||
border-radius: var(--border-radius-md);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.url-input-form input {
|
||||
padding: 10px 10px 10px 42px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.hero-buttons-group {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hero-buttons-group button {
|
||||
flex: 1;
|
||||
height: 44px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Hide desktop dashboard tab bar */
|
||||
.library-header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.dashboard-tabs {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.search-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-wrapper input {
|
||||
width: 100% !important;
|
||||
height: 44px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
/* fluid grids */
|
||||
.recipes-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.recipe-card {
|
||||
border-radius: var(--border-radius-md);
|
||||
}
|
||||
|
||||
.recipe-card-content {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.recipe-card-origin {
|
||||
font-size: 0.65rem;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.recipe-card-title {
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.recipe-card-creator {
|
||||
font-size: 0.65rem;
|
||||
padding: 2px 6px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.recipe-card-desc {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.recipe-card-meta {
|
||||
padding-top: 6px;
|
||||
gap: 8px;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
/* Bottom Navigation Styles */
|
||||
.mobile-bottom-nav {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: var(--bottom-nav-height);
|
||||
background-color: var(--glass-bg);
|
||||
border-top: 1px solid var(--border-color);
|
||||
backdrop-filter: var(--glass-blur);
|
||||
-webkit-backdrop-filter: var(--glass-blur);
|
||||
z-index: 990;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.mobile-tab-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-muted);
|
||||
font-family: var(--font-heading);
|
||||
font-weight: 500;
|
||||
font-size: 0.75rem;
|
||||
gap: 4px;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mobile-tab-item i,
|
||||
.mobile-tab-item svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transition: transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.mobile-tab-item:active i,
|
||||
.mobile-tab-item:active svg {
|
||||
transform: scale(0.88);
|
||||
}
|
||||
|
||||
.mobile-tab-item.active {
|
||||
color: var(--primary);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Floating Action Button */
|
||||
.fab-add-recipe {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
bottom: calc(var(--bottom-nav-height) + 16px + env(safe-area-inset-bottom));
|
||||
right: 16px;
|
||||
width: var(--fab-size);
|
||||
height: var(--fab-size);
|
||||
border-radius: var(--border-radius-full);
|
||||
background: linear-gradient(135deg, var(--primary), var(--secondary));
|
||||
box-shadow: 0 6px 20px var(--primary-glow);
|
||||
border: none;
|
||||
color: var(--text-white);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
z-index: 995;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.fab-add-recipe i,
|
||||
.fab-add-recipe svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
transition: transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.fab-add-recipe:active {
|
||||
transform: scale(0.92);
|
||||
box-shadow: 0 4px 12px var(--primary-glow);
|
||||
}
|
||||
|
||||
/* Adaptive Bottom Sheet Modals */
|
||||
.modal-overlay {
|
||||
align-items: flex-end;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.modal-box {
|
||||
max-height: 88vh;
|
||||
border-radius: var(--border-radius-lg) var(--border-radius-lg) 0 0;
|
||||
border-bottom: none;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
transform: translateY(100%);
|
||||
transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
.modal-overlay.active .modal-box {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Bottom Sheet Handle */
|
||||
.bottom-sheet-handle {
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 4px;
|
||||
background-color: var(--border-color);
|
||||
border-radius: var(--border-radius-full);
|
||||
margin: 8px auto 0 auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 12px 20px 16px 20px;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
/* Restructure Detail Layout for Single-Column Bottom Sheet */
|
||||
.modal-box.max-width-xl {
|
||||
max-height: 92vh;
|
||||
}
|
||||
|
||||
.detail-hero-banner {
|
||||
height: 200px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.detail-main-content {
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
.detail-header-info h2 {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.recipe-meta-tags {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.recipe-meta-tags span,
|
||||
.recipe-meta-tags a#tag-source {
|
||||
font-size: 0.8rem;
|
||||
padding: 4px 10px;
|
||||
}
|
||||
|
||||
.servings-scaler-panel {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
background-color: var(--bg-app);
|
||||
border-radius: var(--border-radius-md);
|
||||
}
|
||||
|
||||
.cooking-mode-trigger {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.detail-split-layout {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 24px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
/* Interactive Cooking Mode adjustments */
|
||||
.cooking-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.cooking-header {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.cooking-title-indicator {
|
||||
font-size: 1.1rem;
|
||||
max-width: 160px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.cook-timer-panel {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.timer-display {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.timer-btn {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.cooking-stepper-box {
|
||||
margin: 20px 0;
|
||||
min-height: 240px;
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.cooking-step-text {
|
||||
font-size: 1.35rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.cooking-controls {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.cooking-controls button {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.cooking-controls .step-dots {
|
||||
justify-content: center;
|
||||
order: -1;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* Large Mobile Profile Style sheet */
|
||||
.profile-avatar-large {
|
||||
display: flex;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: var(--border-radius-full);
|
||||
background: linear-gradient(135deg, var(--primary-glow), var(--border-color));
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--primary);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.profile-avatar-large i,
|
||||
.profile-avatar-large svg {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
/* Tap animations */
|
||||
.btn:active,
|
||||
.recipe-card:active,
|
||||
.image-option:active {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
.ingredient-checkbox-item {
|
||||
min-height: 44px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
/* Consolidated header navigation for mobile */
|
||||
.mobile-header-group {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.mobile-header-tab,
|
||||
.mobile-header-btn-add {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: var(--border-radius-full);
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--glass-bg);
|
||||
border: 1px solid var(--glass-border);
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.mobile-header-tab i,
|
||||
.mobile-header-tab svg,
|
||||
.mobile-header-btn-add i,
|
||||
.mobile-header-btn-add svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.mobile-header-tab:active,
|
||||
.mobile-header-btn-add:active {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
.mobile-header-tab.active {
|
||||
color: var(--primary);
|
||||
background-color: var(--primary-glow);
|
||||
border-color: hsla(var(--hue-primary), 95%, 52%, 0.25);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.mobile-header-btn-add {
|
||||
background: linear-gradient(135deg, var(--primary), var(--secondary));
|
||||
color: var(--text-white);
|
||||
border: none;
|
||||
box-shadow: 0 4px 10px var(--primary-glow);
|
||||
}
|
||||
|
||||
/* Library dynamic view header title */
|
||||
.library-view-title {
|
||||
display: block !important;
|
||||
font-size: 1.45rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-main);
|
||||
margin-bottom: 18px;
|
||||
font-family: var(--font-heading);
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
/* Prevent mobile recipe detail header button overlaps */
|
||||
.detail-back-btn span,
|
||||
#btn-detail-clone span {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.detail-back-btn {
|
||||
width: 40px !important;
|
||||
height: 40px !important;
|
||||
padding: 0 !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
border-radius: var(--border-radius-full) !important;
|
||||
top: 16px !important;
|
||||
left: 16px !important;
|
||||
}
|
||||
|
||||
#btn-detail-clone {
|
||||
width: 40px !important;
|
||||
height: 40px !important;
|
||||
padding: 0 !important;
|
||||
border-radius: var(--border-radius-full) !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.detail-actions {
|
||||
top: 16px !important;
|
||||
right: 16px !important;
|
||||
gap: 8px !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -355,7 +355,7 @@ app.delete('/api/admin/users/:id', requireAuth, (req, res) => {
|
|||
let updatedRecipes;
|
||||
|
||||
if (preserveRecipes) {
|
||||
// Keep in Community Vault (reassign to system_admin, set as public)
|
||||
// Keep in Shared Cookbook (reassign to system_admin, set as public)
|
||||
updatedRecipes = recipes.map(r => {
|
||||
if (r.userId === deletedUser.id) {
|
||||
r.userId = 'system_admin';
|
||||
|
|
@ -460,7 +460,7 @@ app.get('/api/recipes', requireAuth, (req, res) => {
|
|||
res.json(userRecipes);
|
||||
});
|
||||
|
||||
// API: List all public recipes for Community Vault
|
||||
// API: List all public recipes for Shared Cookbook
|
||||
app.get('/api/recipes/public', requireAuth, (req, res) => {
|
||||
const recipes = readRecipes();
|
||||
const publicRecipes = recipes.filter(r => r.isPublic !== false);
|
||||
|
|
|
|||
Loading…
Reference in a new issue