{% extends "base.html" %}
{% block title %}Preferences - Cook{% endblock %}
{% block content %}
<div>
<h1 class="text-2xl font-bold mb-6">{{ tr.t("pref-title") }}</h1>
<div class="space-y-6">
<div class="bg-gradient-to-r from-purple-50 to-pink-50 p-6 rounded-2xl border-2 border-purple-200">
<h2 class="text-lg font-semibold mb-4 text-purple-900">{{ tr.t("pref-language") }}</h2>
<div class="flex flex-wrap gap-3">
<button onclick="setLanguage('en-US')"
class="language-btn px-4 py-2 rounded-lg font-medium transition-all duration-200 border-2 {% if tr.lang_string() == "en-US" %}bg-gradient-to-r from-orange-500 to-orange-600 text-white border-orange-600 shadow-lg scale-105{% else %}bg-white text-gray-700 border-gray-300 hover:border-orange-400 hover:bg-orange-50 hover:scale-105{% endif %}">
🇺🇸 English
</button>
<button onclick="setLanguage('de-DE')"
class="language-btn px-4 py-2 rounded-lg font-medium transition-all duration-200 border-2 {% if tr.lang_string() == "de-DE" %}bg-gradient-to-r from-orange-500 to-orange-600 text-white border-orange-600 shadow-lg scale-105{% else %}bg-white text-gray-700 border-gray-300 hover:border-orange-400 hover:bg-orange-50 hover:scale-105{% endif %}">
🇩🇪 Deutsch
</button>
<button onclick="setLanguage('nl-NL')"
class="language-btn px-4 py-2 rounded-lg font-medium transition-all duration-200 border-2 {% if tr.lang_string() == "nl-NL" %}bg-gradient-to-r from-orange-500 to-orange-600 text-white border-orange-600 shadow-lg scale-105{% else %}bg-white text-gray-700 border-gray-300 hover:border-orange-400 hover:bg-orange-50 hover:scale-105{% endif %}">
🇳🇱 Nederlands
</button>
<button onclick="setLanguage('fr-FR')"
class="language-btn px-4 py-2 rounded-lg font-medium transition-all duration-200 border-2 {% if tr.lang_string() == "fr-FR" %}bg-gradient-to-r from-orange-500 to-orange-600 text-white border-orange-600 shadow-lg scale-105{% else %}bg-white text-gray-700 border-gray-300 hover:border-orange-400 hover:bg-orange-50 hover:scale-105{% endif %}">
🇫🇷 Français
</button>
<button onclick="setLanguage('es-ES')"
class="language-btn px-4 py-2 rounded-lg font-medium transition-all duration-200 border-2 {% if tr.lang_string() == "es-ES" %}bg-gradient-to-r from-orange-500 to-orange-600 text-white border-orange-600 shadow-lg scale-105{% else %}bg-white text-gray-700 border-gray-300 hover:border-orange-400 hover:bg-orange-50 hover:scale-105{% endif %}">
🇪🇸 Español
</button>
<button onclick="setLanguage('eu-ES')"
class="language-btn px-4 py-2 rounded-lg font-medium transition-all duration-200 border-2 {% if tr.lang_string() == "eu-ES" %}bg-gradient-to-r from-orange-500 to-orange-600 text-white border-orange-600 shadow-lg scale-105{% else %}bg-white text-gray-700 border-gray-300 hover:border-orange-400 hover:bg-orange-50 hover:scale-105{% endif %}">
Euskara
</button>
<button onclick="setLanguage('sv-SE')"
class="language-btn px-4 py-2 rounded-lg font-medium transition-all duration-200 border-2 {% if tr.lang_string() == "sv-SE" %}bg-gradient-to-r from-orange-500 to-orange-600 text-white border-orange-600 shadow-lg scale-105{% else %}bg-white text-gray-700 border-gray-300 hover:border-orange-400 hover:bg-orange-50 hover:scale-105{% endif %}">
🇸🇪 Svenska
</button>
</div>
<p class="text-sm text-purple-700 mt-3">Your language preference will be saved in a cookie.</p>
</div>
{% if !static_mode %}
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 p-6 rounded-2xl border-2 border-blue-200">
<h2 class="text-lg font-semibold mb-2 text-blue-900">{{ tr.t("pref-features") }}</h2>
<p class="text-sm text-blue-700 mb-4">{{ tr.t("pref-features-desc") }}</p>
<div class="flex flex-wrap gap-3">
<button onclick="toggleFeature('show_shopping_list', {{ features.show_shopping_list }})"
class="px-4 py-2 rounded-lg font-medium transition-all duration-200 border-2 {% if features.show_shopping_list %}bg-gradient-to-r from-orange-500 to-orange-600 text-white border-orange-600 shadow-lg scale-105{% else %}bg-white text-gray-700 border-gray-300 hover:border-orange-400 hover:bg-orange-50 hover:scale-105{% endif %}">
🛒 {{ tr.t("nav-shopping-list") }}
</button>
<button onclick="toggleFeature('show_pantry', {{ features.show_pantry }})"
class="px-4 py-2 rounded-lg font-medium transition-all duration-200 border-2 {% if features.show_pantry %}bg-gradient-to-r from-orange-500 to-orange-600 text-white border-orange-600 shadow-lg scale-105{% else %}bg-white text-gray-700 border-gray-300 hover:border-orange-400 hover:bg-orange-50 hover:scale-105{% endif %}">
🥫 {{ tr.t("nav-pantry") }}
</button>
</div>
</div>
{% endif %}
{% if sync_enabled %}
<div class="bg-gray-50 p-4 rounded">
<h2 class="text-lg font-semibold mb-3">CookCloud Sync</h2>
<div id="sync-section">
{% if sync_logged_in %}
<div class="flex items-center justify-between">
<div>
<p class="text-sm">
Signed in as <span class="font-medium">{{ sync_email.as_deref().unwrap_or("Unknown") }}</span>
</p>
<p class="text-xs text-gray-600 mt-1" id="sync-status-text">{% if sync_syncing %}Syncing recipes...{% else %}Sync idle{% endif %}</p>
</div>
<button onclick="syncLogout()"
class="px-4 py-2 rounded-lg font-medium transition-all duration-200 border-2 bg-white text-red-600 border-red-300 hover:bg-red-50 hover:border-red-400">
Logout
</button>
</div>
{% else %}
<div id="sync-login-section" class="flex items-center justify-between">
<p class="text-sm text-gray-600" id="sync-login-message">Sync your recipes across devices with CookCloud.</p>
<button id="sync-login-btn" onclick="syncLogin()"
class="px-4 py-2 rounded-lg font-medium transition-all duration-200 border-2 bg-orange-700 text-white border-orange-800 hover:bg-orange-800">
Login to CookCloud
</button>
</div>
<div id="sync-login-card" class="hidden mt-4 border border-gray-200 rounded-lg p-6 bg-white">
<h3 class="text-lg font-semibold mb-2">Sign in to CookCloud</h3>
<ol class="mt-3 space-y-2 text-sm text-gray-700">
<li>1. Open <a id="sync-login-link" href="#" target="_blank" rel="noopener" class="underline text-orange-700 hover:text-orange-800">cook.md/device</a> in any browser.</li>
<li>2. Enter this code:</li>
</ol>
<div class="mt-3 flex items-center gap-2">
<span id="sync-login-code" class="text-2xl tracking-widest bg-gray-100 px-4 py-2 rounded font-mono">---- ----</span>
<button id="sync-login-copy" type="button" class="px-3 py-1 border border-gray-300 rounded text-sm hover:bg-gray-50">Copy</button>
</div>
<p id="sync-login-expires" class="mt-3 text-sm text-gray-600"></p>
<div class="mt-4 flex gap-2">
<a id="sync-login-open" href="#" target="_blank" rel="noopener" class="px-4 py-2 rounded-lg font-medium bg-orange-700 text-white border-2 border-orange-800 hover:bg-orange-800">Open cook.md/device</a>
<button id="sync-login-cancel" type="button" class="px-4 py-2 rounded-lg font-medium border-2 border-gray-300 text-gray-700 hover:bg-gray-50">Cancel</button>
</div>
</div>
{% endif %}
</div>
</div>
{% endif %}
<div class="bg-gray-50 p-4 rounded">
<h2 class="text-lg font-semibold mb-3">Configuration Files</h2>
<div class="space-y-2 text-sm">
<div>
<span class="font-medium">{{ tr.t("pref-aisle-path") }}:</span>
<span class="text-gray-600 ml-2">{{ aisle_path }}</span>
</div>
<div>
<span class="font-medium">{{ tr.t("pref-pantry-path") }}:</span>
<span class="text-gray-600 ml-2">{{ pantry_path }}</span>
</div>
</div>
</div>
<div class="bg-gray-50 p-4 rounded">
<h2 class="text-lg font-semibold mb-3">Recipe Directory</h2>
<div class="text-sm">
<span class="font-medium">{{ tr.t("pref-base-path") }}:</span>
<span class="text-gray-600 ml-2">{{ base_path }}</span>
</div>
</div>
<div class="bg-gray-50 p-4 rounded">
<h2 class="text-lg font-semibold mb-3">About</h2>
<div class="space-y-2 text-sm">
<div>
<span class="font-medium">{{ tr.t("pref-version") }}:</span>
<span class="text-gray-600 ml-2">{{ version }}</span>
</div>
<div>
<span class="font-medium">Cooklang CLI:</span>
<span class="text-gray-600 ml-2">A command-line tool for managing Cooklang recipes</span>
</div>
</div>
</div>
<div class="bg-gray-50 p-4 rounded">
<h2 class="text-lg font-semibold mb-3">Documentation & Resources</h2>
<div class="space-y-2 text-sm">
<div>
<a href="https://cooklang.org/cli/" target="_blank" rel="noopener noreferrer"
class="text-orange-700 hover:text-orange-800 hover:underline font-medium">
📚 CLI Documentation
</a>
<span class="text-gray-600 ml-2">- Learn about all CLI commands and features</span>
</div>
<div>
<a href="https://cooklang.org/docs/spec/" target="_blank" rel="noopener noreferrer"
class="text-orange-700 hover:text-orange-800 hover:underline font-medium">
📖 Cooklang Specification
</a>
<span class="text-gray-600 ml-2">- Recipe markup language syntax guide</span>
</div>
<div>
<a href="https://cooklang.org/docs/getting-started/" target="_blank" rel="noopener noreferrer"
class="text-orange-700 hover:text-orange-800 hover:underline font-medium">
🚀 Getting Started
</a>
<span class="text-gray-600 ml-2">- Introduction to Cooklang basics</span>
</div>
<div>
<a href="https://github.com/cooklang/CookCLI" target="_blank" rel="noopener noreferrer"
class="text-orange-700 hover:text-orange-800 hover:underline font-medium">
🔧 GitHub Repository
</a>
<span class="text-gray-600 ml-2">- Source code and issue tracker</span>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function toggleFeature(name, current) {
const val = current ? '0' : '1';
const maxAge = 365 * 24 * 60 * 60;
document.cookie = `${name}=${val}; path={{ prefix }}/; max-age=${maxAge}; SameSite=Lax`;
window.location.reload();
}
function setLanguage(lang) {
const maxAge = 365 * 24 * 60 * 60;
document.cookie = `lang=${lang}; path={{ prefix }}/; max-age=${maxAge}; SameSite=Lax`;
window.location.reload();
}
{% if sync_enabled %}
let pollHandle = null;
let countdownHandle = null;
function renderPending(p) {
document.getElementById('sync-login-section').classList.add('hidden');
const card = document.getElementById('sync-login-card');
card.classList.remove('hidden');
document.getElementById('sync-login-code').textContent = p.user_code;
document.getElementById('sync-login-link').href = p.verification_uri;
document.getElementById('sync-login-link').textContent = p.verification_uri;
document.getElementById('sync-login-open').href = p.verification_uri_complete;
if (countdownHandle) clearInterval(countdownHandle);
const deadline = Date.now() + p.expires_in_secs * 1000;
const expEl = document.getElementById('sync-login-expires');
const tick = () => {
const remaining = Math.max(0, Math.round((deadline - Date.now()) / 1000));
if (remaining <= 0) {
expEl.textContent = 'Code expired.';
stopPolling();
resetLoginUi('Code expired — try again.');
return;
}
const m = Math.floor(remaining / 60);
const s = String(remaining % 60).padStart(2, '0');
expEl.textContent = `Expires in ${m}:${s}`;
};
tick();
countdownHandle = setInterval(tick, 1000);
}
function stopPolling() {
if (pollHandle) { clearInterval(pollHandle); pollHandle = null; }
if (countdownHandle) { clearInterval(countdownHandle); countdownHandle = null; }
}
function startPolling() {
stopPolling();
pollHandle = setInterval(async () => {
const status = await fetch('{{ prefix }}/api/sync/status').then(r => r.json()).catch(() => null);
if (!status) return; if (status.logged_in) {
stopPolling();
window.location.reload();
} else if (!status.pending_login) {
stopPolling();
resetLoginUi('Login was cancelled or expired.');
}
}, 2000);
}
function resetLoginUi(msg) {
document.getElementById('sync-login-card').classList.add('hidden');
document.getElementById('sync-login-section').classList.remove('hidden');
const btn = document.getElementById('sync-login-btn');
if (btn) btn.disabled = false;
document.getElementById('sync-login-message').textContent = msg || '';
}
async function syncLogin() {
const btn = document.getElementById('sync-login-btn');
const msg = document.getElementById('sync-login-message');
try {
btn.disabled = true;
msg.textContent = 'Requesting code...';
const resp = await fetch('{{ prefix }}/api/sync/login', { method: 'POST' });
if (!resp.ok) {
const err = await resp.json().catch(() => ({}));
btn.disabled = false;
msg.textContent = err.error || 'Login failed.';
return;
}
const body = await resp.json();
renderPending(body);
startPolling();
} catch (e) {
btn.disabled = false;
msg.textContent = 'Failed to start login: ' + e.message;
}
}
document.addEventListener('DOMContentLoaded', () => {
const copyBtn = document.getElementById('sync-login-copy');
if (copyBtn) {
copyBtn.addEventListener('click', async () => {
const code = document.getElementById('sync-login-code').textContent;
try {
await navigator.clipboard.writeText(code.replace(/\s+/g, ''));
copyBtn.textContent = 'Copied!';
} catch (e) {
copyBtn.textContent = 'Copy failed';
}
setTimeout(() => { copyBtn.textContent = 'Copy'; }, 1500);
});
}
const cancelBtn = document.getElementById('sync-login-cancel');
if (cancelBtn) {
cancelBtn.addEventListener('click', async () => {
stopPolling();
await fetch('{{ prefix }}/api/sync/cancel_login', { method: 'POST' });
resetLoginUi('Login cancelled.');
});
}
fetch('{{ prefix }}/api/sync/status').then(r => r.json()).then(status => {
if (!status.logged_in && status.pending_login) {
renderPending(status.pending_login);
startPolling();
}
});
});
async function syncLogout() {
try {
await fetch('{{ prefix }}/api/sync/logout', { method: 'POST' });
window.location.reload();
} catch (e) {
alert('Logout failed: ' + e.message);
}
}
{% endif %}
</script>
{% endblock %}