Files
Aissa c5ea8cbb63 Ship War Room UX, CEO inbox, mobile chat, and Agents NAS workspace.
Make A2A togglable, keep reply context while reading, route agent data to //10.4.7.11/share/Agents, and polish themes plus export-intel sync cancel.
2026-08-16 22:40:32 +00:00

660 lines
45 KiB
HTML
Raw Permalink 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="nl">
<script>(function(){try{var t=localStorage.getItem('flk-theme');if(t)document.documentElement.setAttribute('data-theme',t);var s=localStorage.getItem('flk-sidebar-theme');if(s)document.documentElement.setAttribute('data-sidebar',s);var b=localStorage.getItem('flk-page-bg');if(b)document.documentElement.setAttribute('data-bg',b);var ss=localStorage.getItem('flk-sidebar-style');if(ss==='plain')document.documentElement.setAttribute('data-sidebar-style','plain');else document.documentElement.removeAttribute('data-sidebar-style');}catch(e){}})();</script>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, maximum-scale=5" />
<meta name="theme-color" content="#0a0e14" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Foodlinkk" />
<title>{% block title %}{{ page_title }} · Cucina{% endblock %}</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" />
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="manifest" href="/static/manifest.json?v=3" />
<meta name="application-name" content="Foodlinkk" />
<meta name="format-detection" content="telephone=no" />
<link rel="icon" href="/static/icons/icon-192.png" type="image/png" />
<link rel="icon" href="/static/icons/app-icon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/static/icons/apple-touch-icon.png" sizes="180x180" />
<link rel="apple-touch-icon" href="/static/icons/icon-192.png" sizes="192x192" />
<link rel="stylesheet" href="/static/css/palantir-theme.css?v=4" />
<link rel="stylesheet" href="/static/css/flk-themes.css?v=8" />
<link rel="stylesheet" href="/static/css/pulse-theme.css?v=2" />
<link rel="stylesheet" href="/static/css/topnav-neo.css?v=6" />
<link rel="stylesheet" href="/static/css/vertical-tabs.css?v=10" />
<link rel="stylesheet" href="/static/css/herman-dashboard.css?v=34" />
<script defer src="/static/js/herman-assistant.js?v=17"></script>
<script defer src="/static/js/alpine.min.js?v=2"></script>
<script defer src="/static/js/htmx.min.js?v=2"></script>
{% block extra_head %}{% endblock %}
<link rel="stylesheet" href="/static/css/app-sidebar-neo.css?v=13" />
<link rel="stylesheet" href="/static/css/cockpit-typography.css?v=1" />
<link rel="stylesheet" href="/static/css/herman-assistant.css?v=16" />
<!-- mobile last: wins over sidebar/dashboard cascade -->
<link rel="stylesheet" href="/static/css/mobile.css?v=12" />
<link rel="stylesheet" href="/static/css/user-avatar.css?v=3" />
</head>
<body x-data="appShell()" x-init="initApp()" :class="{ 'nav-open': navOpen, 'drawer-open': drawerOpen, 'brand-cucina': brand === 'cucina', 'brand-foodlinkk': brand === 'foodlinkk' }">
<div class="app-shell">
<aside class="app-sidebar" :style="'--sidebar-width:' + sidebarWidth + 'px'" @click="if($event.target.closest('a')) navOpen=false">
<div class="app-sidebar-resizer" title="Sleep om breder te maken"
@mousedown.prevent="startSidebarResize($event)"></div>
<div class="app-sidebar-brand">
<p class="brand-title">Cucina · Foodlinkk</p>
<p class="brand-name" x-text="brand === 'foodlinkk' ? 'Foodlinkk B2B' : 'Cucina B2C'">Cucina B2C</p>
</div>
<div class="persona-switch brand-switch" role="tablist" aria-label="Brand">
<button type="button" role="tab" class="persona-btn brand-btn-cucina"
:class="{ active: brand === 'cucina' }" @click="setBrand('cucina')"
:aria-selected="brand === 'cucina'">Cucina · B2C</button>
<button type="button" role="tab" class="persona-btn brand-btn-foodlinkk"
:class="{ active: brand === 'foodlinkk' }" @click="setBrand('foodlinkk')"
:aria-selected="brand === 'foodlinkk'">Foodlinkk · B2B</button>
</div>
<div class="persona-nav">
<div class="app-sidebar-group" x-show="brand === 'foodlinkk'">
<div class="app-sidebar-label">Foodlinkk B2B</div>
<a href="/export-intel" data-perm="export" class="nav-pill nav-pill-wereldexport {% if request.url.path.startswith('/export-intel') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg></span><span class="sidebar-nav-label">Wereldexport</span></a>
<a href="/clients" data-perm="clients" class="nav-pill nav-pill-clients {% if request.url.path.startswith('/clients') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.clients">Klanten</span></a>
<a href="/deals" data-perm="deals" class="nav-pill nav-pill-deals {% if request.url.path.startswith('/deals') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="m11 17 2 2a1 1 0 1 0 3-3"/><path d="m14 14 2.5 2.5a1 1 0 1 0 3-3l-3.88-3.88a3 3 0 0 0-4.24 0l-.88.88a1 1 0 1 1-3-3l2.81-2.81a5.79 5.79 0 0 1 7.06-.87l.47.28a2 2 0 0 0 1.42.25L21 6"/><path d="m3 13 1.68-1.68a2.41 2.41 0 0 1 3.41 0L12 16"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.deals">Deals</span></a>
<a href="/products" data-perm="products" class="nav-pill nav-pill-products {% if request.url.path.startswith('/products') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z"/><path d="M12 22V12"/><path d="m3.3 7 7.7 4.7L18.7 7"/><path d="m7.5 4.3 9 5.2"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.products">Producten</span></a>
<a href="/suppliers" data-perm="suppliers" class="nav-pill nav-pill-suppliers {% if request.url.path.startswith('/suppliers') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M2 20a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8l-7 5V8l-7 5V4a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"/><path d="M17 18h1"/><path d="M12 18h1"/><path d="M7 18h1"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.suppliers">Leveranciers</span></a>
<a href="/reports" data-perm="reports" class="nav-pill nav-pill-reports {% if request.url.path.startswith('/reports') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18"/><path d="M18 17V9"/><path d="M13 17V5"/><path d="M8 17v-3"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.reports">Rapporten</span></a>
</div>
<div class="app-sidebar-group" x-show="brand === 'cucina'">
<div class="app-sidebar-label">Cucina B2C</div>
<a href="/" data-perm="dashboard" class="nav-pill nav-pill-herman {% if request.url.path == '/' %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.ceo_home">Overzicht</span></a>
<a href="/revenue-cockpit" data-perm="revenue" class="nav-pill nav-pill-deals {% if request.url.path.startswith('/revenue-cockpit') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6"/><path d="M18.09 10.37A6 6 0 1 1 10.34 18"/><path d="M7 6h1v4"/><path d="m16.71 13.88.7.71-2.82 2.82"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.revenue_cockpit">Revenue Cockpit</span></a>
<a href="/retail" data-perm="retail" class="nav-pill nav-pill-retail {% if request.url.path.startswith('/retail') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="21" r="1"/><circle cx="19" cy="21" r="1"/><path d="M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.retail">Retail NL</span></a>
<a href="/marketing" data-perm="marketing" class="nav-pill nav-pill-marketing {% if request.url.path.startswith('/marketing') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="m3 11 18-5v12L3 13v-2z"/><path d="M11.6 16.8a3 3 0 1 1-5.8-1.6"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.marketing">Marketing</span></a>
<a href="/packaging" data-perm="packaging" class="nav-pill nav-pill-studio {% if request.url.path.startswith('/packaging') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z"/><path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"/><path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.packaging">Packaging</span></a>
<a href="/beurs" data-perm="beurs" class="nav-pill nav-pill-beurs {% if request.url.path.startswith('/beurs') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 21 14 3"/><path d="M20.5 21 10 3"/><path d="M15.5 21 12 15l-3.5 6"/><path d="M2 21h20"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.beurs">Beurs Live</span></a>
<a href="/documents" data-perm="documents" class="nav-pill nav-pill-documents {% if request.url.path.startswith('/documents') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.documents">Documenten</span></a>
<a href="/projects" data-perm="projects" class="nav-pill nav-pill-documents {% if request.url.path.startswith('/projects') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.projects">Projecten</span></a>
<a href="/hermes" data-perm="hermes" class="nav-pill nav-pill-telegram {% if request.url.path.startswith('/hermes') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.telegram">Telegram</span></a>
<a href="/analytics" data-perm="analytics" class="nav-pill nav-pill-analytics {% if request.url.path.startswith('/analytics') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 7 13.5 15.5 8.5 10.5 2 17"/><polyline points="16 7 22 7 22 13"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.analytics">Analytics</span></a>
</div>
<div class="app-sidebar-group">
<div class="app-sidebar-label" data-i18n="nav.platform">Platform</div>
<a href="/ops" data-perm="ops" class="nav-pill nav-pill-ops {% if request.url.path.startswith('/ops') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect width="16" height="16" x="4" y="4" rx="2"/><rect width="6" height="6" x="9" y="9" rx="1"/><path d="M15 2v2"/><path d="M15 20v2"/><path d="M2 15h2"/><path d="M2 9h2"/><path d="M20 15h2"/><path d="M20 9h2"/><path d="M9 2v2"/><path d="M9 20v2"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.ops">IT Ops</span></a>
<a href="/agents?tab=warroom" data-perm="agents" class="nav-pill nav-pill-agents {% if request.url.path.startswith('/agents') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.agents">Agents</span></a>
<a href="/browser" data-perm="browser" class="nav-pill nav-pill-browser {% if request.url.path.startswith('/browser') or request.url.path.startswith('/monitor') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.browser">Browser</span></a>
<a href="/studio" data-perm="studio" class="nav-pill nav-pill-studio {% if request.url.path.startswith('/studio') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="13.5" cy="6.5" r=".5" fill="currentColor"/><circle cx="17.5" cy="10.5" r=".5" fill="currentColor"/><circle cx="8.5" cy="7.5" r=".5" fill="currentColor"/><circle cx="6.5" cy="12.5" r=".5" fill="currentColor"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.studio">Studio</span></a>
<a href="/voice" data-perm="voice" class="nav-pill nav-pill-voice {% if request.url.path.startswith('/voice') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" x2="12" y1="19" y2="22"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.voice">Voice</span></a>
<a href="/settings" data-perm="settings" class="nav-pill nav-pill-settings {% if request.url.path.startswith('/settings') %}active{% endif %}"><span class="sidebar-nav-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><line x1="4" x2="4" y1="21" y2="14"/><line x1="4" x2="4" y1="10" y2="3"/><line x1="12" x2="12" y1="21" y2="12"/><line x1="12" x2="12" y1="8" y2="3"/><line x1="20" x2="20" y1="21" y2="16"/><line x1="20" x2="20" y1="12" y2="3"/><line x1="2" x2="6" y1="14" y2="14"/><line x1="10" x2="14" y1="8" y2="8"/><line x1="18" x2="22" y1="16" y2="16"/></svg></span><span class="sidebar-nav-label" data-i18n="nav.settings">Instellingen</span></a>
</div>
</div>
</aside>
<div class="mobile-nav-overlay" x-show="navOpen" @click="navOpen=false" x-cloak></div>
<div class="app-main">
<header class="app-topbar">
<button type="button" class="mobile-menu-btn" @click="navOpen = !navOpen" aria-label="Menu openen">☰</button>
<span class="page-tip">
<span class="persona-badge" :class="brand === 'foodlinkk' ? 'brand-badge-foodlinkk' : 'brand-badge-cucina'" x-text="brand === 'foodlinkk' ? 'B2B' : 'B2C'"></span>
Cucina · Foodlinkk
</span>
<select class="form-input" style="width:auto;min-width:110px;padding:0.2rem 0.5rem;font-size:0.72rem;margin:0 0.5rem"
x-model="locale" @change="setLocale(locale)" :title="$t ? $t('common.language') : 'Taal'">
<option value="nl">NL</option>
<option value="en">EN</option>
</select>
<div class="app-clock"><strong x-text="clockDate">—</strong> · <span x-text="clockTime">—</span></div>
{% set cu = request.state.user if request is defined else none %}
<div class="app-user-chip"{% if not cu %} x-show="authUser" x-cloak{% endif %}
x-data="userChip()" x-init="bootAvatar()">
<button type="button" class="user-avatar-btn" title="Profielfoto wijzigen" @click="pickAvatar()">
{% if cu and cu.avatar_url %}
<img class="user-avatar-img" src="{{ cu.avatar_url }}"
:src="photoUrl || '{{ cu.avatar_url }}'" alt="" width="36" height="36" />
{% else %}
<img class="user-avatar-img" x-show="photoUrl" :src="photoUrl" alt="" width="36" height="36" />
<span class="user-avatar-fallback" x-show="!photoUrl" x-text="initials">?</span>
{% endif %}
</button>
<input type="file" x-ref="avatarFile" accept="image/png,image/jpeg,image/webp,image/gif"
class="user-avatar-input" style="display:none!important;position:absolute;width:0;height:0;opacity:0" tabindex="-1" aria-hidden="true" @change="onFile($event)" />
<div class="user-avatar-meta">
<span class="user-avatar-name" x-text="name">{% if cu %}{{ cu.display_name or cu.username }}{% endif %}</span>
<span class="user-avatar-role" x-text="role">{% if cu %}{{ cu.role }}{% endif %}</span>
</div>
<a class="btn btn-sm btn-secondary user-logout-btn" href="/logout">Uitloggen</a>
</div>
</header>
<main class="main-topnav">{% block content %}{% endblock %}</main>
</div>
</div>
<div class="pwa-install-bar" x-show="showInstall" x-cloak
:class="{ 'is-ios': isIos, 'is-android': isAndroid }"
style="display:none">
<p>Installeer Foodlinkk op je beginscherm</p>
<p class="pwa-install-hint is-ios-hint">iPhone/iPad (Safari): Deel ↗ → <strong>Zet op beginscherm</strong>. Open via <strong>http://10.4.7.18:8610</strong> — geen certificaatmelding.</p>
<p class="pwa-install-hint is-android-hint">Android (Chrome): menu ⋮ → <strong>Toevoegen aan startscherm</strong>. Open via <strong>http://10.4.7.18:8610</strong>.</p>
<button type="button" class="btn btn-sm btn-pulse-green" @click="installPwa()">Uitleg</button>
<button type="button" class="btn btn-sm" @click="dismissPwa()">Later</button>
</div>
<!-- Herman chat — altijd vast onderin zichtbaar -->
<div id="ha-dock" class="ha-dock" x-data="hermanBrowserWidget()" x-init="initWidget()" :class="{ 'ha-dock-hidden': hidden }">
<button type="button" class="ha-fab"
:class="{ 'is-open': open, 'is-busy': busy, 'is-dragging': fabDragging, 'has-custom-pos': !!fabPos }"
:style="fabStyle()"
@pointerdown.prevent="onFabPointerDown($event)"
@pointermove="onFabPointerMove($event)"
@pointerup="onFabPointerUp($event)"
@pointercancel="onFabPointerUp($event)"
@click="onFabClick($event)"
@dblclick="onFabDblClick($event)"
title="Slepen om te verplaatsen · klik om te openen · dubbelklik = reset"
aria-label="Open Herman chat">
<span class="ha-fab-icon" aria-hidden="true">💬</span>
<span class="ha-fab-label" x-text="open ? 'Sluiten' : 'Herman'"></span>
</button>
<div class="ha-panel" :class="{ 'has-custom-pos': !!fabPos }" :style="panelStyle()" x-show="open" x-cloak x-transition.opacity.duration.150ms @keydown.escape.window="open = false">
<header class="ha-head">
<div class="ha-head-title">
<h2>Herman</h2>
<p class="ha-conn" :class="{ 'is-ok': connOk, 'is-err': connOk === false }" x-text="connLabel"></p>
</div>
<div class="ha-head-actions">
<a x-show="!micSecureOk()" class="ha-text-btn" :href="httpsChatUrl()" target="_blank" rel="noopener" title="Audio/microfoon in HTTPS-tab">Audio</a>
<button type="button" class="ha-text-btn" @click="clearChat()" title="Nieuwe chat" :disabled="busy || (!turns.length && !manualText)">Wissen</button>
<button type="button" class="ha-text-btn" @click="openWarRoomPopup()" title="War Room op tweede scherm">War Room</button>
<a href="/agents?tab=warroom">Agents</a>
<a href="/herman">Volledig</a>
<button type="button" @click="open = false" aria-label="Sluiten">×</button>
</div>
</header>
<div class="ha-confirm" x-show="pendingAction">
<span x-text="pendingAction ? (pendingAction.type === 'webbuilder_build' ? ('Agy website: ' + (pendingAction.project || '')) : ('Zoeken: ' + (pendingAction.entity_label || '') + ' in ' + (pendingAction.location_label || ''))) : ''"></span>
<div class="ha-confirm-actions">
<button type="button" class="btn btn-sm btn-pulse" @click="confirmPending()" :disabled="busy">Ja</button>
<button type="button" class="btn btn-sm" @click="cancelPending()" :disabled="busy">Nee</button>
</div>
</div>
<div class="ha-log" x-ref="chatLog">
<template x-for="(t, i) in turns" :key="i">
<div class="ha-bubble" :class="t.role === 'user' ? 'ha-bubble-user' : 'ha-bubble-agent'">
<div class="ha-bubble-meta" x-text="t.at + (t.role === 'user' ? ' · jij' : (' · ' + ((t.meta && t.meta.agent_label) || 'Herman')))"></div>
<p x-text="t.text"></p>
</div>
</template>
<p x-show="!turns.length" class="page-tip" style="font-size:0.75rem;margin:0">Chat met Herman op de achtergrond — bv. “zoek distributeurs in Dubai”</p>
</div>
<div class="ha-preview" :class="{ 'is-interim': liveInterim }" x-show="livePreview || recording" x-text="livePreview || 'Luisteren…'"></div>
<div class="ha-toggles">
<label title="Alleen voorlezen als jij dit aanzet">
<input type="checkbox" x-model="speakReply" @change="persistPrefs()" />
<span>Voorlezen (Maarten)</span>
</label>
<label title="Na antwoord meteen weer luisteren">
<input type="checkbox" x-model="handsFreeMode" @change="onHandsFreeChange()" />
<span>Hands-free praten</span>
</label>
</div>
<p class="ha-status" :class="{ 'is-error': status === 'error' }" x-show="statusDetail || busy" x-text="statusDetail || (busy ? 'Herman denkt…' : '')"></p>
<div class="ha-https-prompt" x-show="httpsAudioPrompt" x-cloak>
<span>Microfoon werkt via HTTPS</span>
<button type="button" class="btn btn-sm btn-pulse" @click="goHttpsAudio()">Doorgaan naar HTTPS →</button>
<button type="button" class="ha-text-btn" @click="httpsAudioPrompt = false" title="Sluiten">×</button>
</div>
<form class="ha-input-row" @submit.prevent="sendText()">
<button type="button" class="ha-mic" :class="micClass()"
@mousedown.prevent="onMicDown()" @mouseup.prevent="onMicUp()"
@touchstart.prevent="onMicDown()" @touchend.prevent="onMicUp()"
@click.prevent="onMicClick()" title="Microfoon">🎙️</button>
<input class="form-input" type="text" name="herman_message" autocomplete="off"
placeholder="Vraag aan Herman…" x-model="manualText"
@keydown.enter.prevent="sendText()" :disabled="busy" />
<button type="submit" class="btn btn-sm btn-pulse" :disabled="busy || !manualText.trim()">→</button>
</form>
</div>
<div class="ei-modal-root ha-results-modal" x-show="resultsOpen" @click.self="closeResultsModal()">
<div class="ei-modal-card ha-results-card" @click.stop>
<h3 x-text="resultsTitle"></h3>
<p><strong x-text="resultsTotal"></strong> resultaten</p>
<div class="voice-results-table-wrap" style="max-height:40vh;overflow:auto">
<table class="voice-results-table" style="width:100%;font-size:0.78rem">
<thead><tr><th>Naam</th><th>Type</th><th>Stad</th></tr></thead>
<tbody>
<template x-for="(e, i) in resultsEntities" :key="i">
<tr>
<td><a :href="'/export-intel?open=' + e.id" x-text="e.name"></a></td>
<td x-text="entityTypeLabel(e.entity_type)"></td>
<td x-text="e.city || '—'"></td>
</tr>
</template>
</tbody>
</table>
</div>
<div class="ei-modal-actions">
<a class="btn btn-sm" :href="resultsOpenUrl">Wereldexport</a>
<button type="button" class="btn btn-sm" @click="closeResultsModal()">Sluiten</button>
</div>
</div>
</div>
<div class="ei-modal-root ha-results-modal" x-show="webbuilderOpen" @click.self="closeWebbuilderModal()">
<div class="ei-modal-card ha-results-card" @click.stop>
<h3 x-text="webbuilderTitle"></h3>
<p><strong x-text="webbuilderProject"></strong> · Agy / Antigravity op Hermes</p>
<p class="page-tip" style="font-size:0.78rem">Preview na afloop: <a :href="webbuilderPreview" target="_blank" rel="noopener" x-text="webbuilderPreview"></a></p>
<p class="page-tip" x-show="webbuilderNas" style="font-size:0.72rem" x-text="'NAS: ' + webbuilderNas"></p>
<div class="ei-modal-actions">
<a class="btn btn-sm btn-pulse" :href="webbuilderAgentsUrl">Agents terminal</a>
<a class="btn btn-sm" :href="webbuilderPreview" target="_blank" rel="noopener">Preview</a>
<button type="button" class="btn btn-sm" @click="closeWebbuilderModal()">Sluiten</button>
</div>
</div>
</div>
</div>
<script src="/static/js/i18n.js?v=5"></script>
<script src="/static/js/cockpit.js?v=3"></script>
<script src="/static/js/viz-engine.js?v=3"></script>
<script src="/static/js/global-viz.js?v=1"></script>
<script src="/static/js/live-pulse.js?v=3"></script>
<script>
window.FoodlinkkBrand = (function () {
var FOODLINKK_PREFIXES = ['/export-intel', '/foodlinkk', '/clients', '/deals', '/products', '/suppliers'];
var CUCINA_PREFIXES = ['/retail', '/marketing', '/packaging', '/revenue-cockpit', '/beurs'];
function isFoodlinkkPath(path) {
return FOODLINKK_PREFIXES.some(function (p) { return path === p || path.indexOf(p + '/') === 0; });
}
function isCucinaPath(path) {
return path === '/' || CUCINA_PREFIXES.some(function (p) { return path === p || path.indexOf(p + '/') === 0; });
}
function detect(path) {
var stored = localStorage.getItem('foodlinkk-brand');
if (stored === 'cucina' || stored === 'foodlinkk') return stored;
if (isFoodlinkkPath(path)) return 'foodlinkk';
return 'cucina';
}
return { isFoodlinkkPath: isFoodlinkkPath, isCucinaPath: isCucinaPath, detect: detect };
})();
/* legacy persona helper → brand */
window.FoodlinkkPersona = window.FoodlinkkBrand;
function userChip() {
return {
photoUrl: {% if cu and cu.avatar_url %}'{{ cu.avatar_url }}'{% else %}null{% endif %},
name: {% if cu %}'{{ (cu.display_name or cu.username)|e }}'{% else %}''{% endif %},
role: {% if cu %}'{{ cu.role }}'{% else %}''{% endif %},
initials: {% if cu %}'{{ ((cu.display_name or cu.username or '?')[0:1])|upper }}'{% else %}'?'{% endif %},
bootAvatar() {
// sync from parent shell when /api/auth/me returns
window.addEventListener('flk-auth-ready', (ev) => {
const u = ev.detail;
if (!u) return;
this.photoUrl = u.avatar_url || null;
this.name = u.display_name || u.username || '';
this.role = u.role || '';
const n = (this.name || '?').trim();
const parts = n.split(/\s+/).filter(Boolean);
this.initials = parts.length >= 2 ? (parts[0][0] + parts[1][0]).toUpperCase() : n.slice(0, 1).toUpperCase();
});
},
pickAvatar() { this.$refs.avatarFile && this.$refs.avatarFile.click(); },
async onFile(ev) {
const file = ev.target.files && ev.target.files[0];
ev.target.value = '';
if (!file) return;
const fd = new FormData();
fd.append('file', file);
try {
const res = await fetch('/api/auth/me/avatar', { method: 'POST', body: fd, credentials: 'same-origin' });
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.detail || 'Upload mislukt');
this.photoUrl = (data.avatar_url || (data.user && data.user.avatar_url)) + '?t=' + Date.now();
if (window.Cockpit && Cockpit.toast) Cockpit.toast('Profielfoto bijgewerkt', 'success');
window.dispatchEvent(new CustomEvent('flk-auth-ready', { detail: data.user }));
} catch (e) {
if (window.Cockpit && Cockpit.toast) Cockpit.toast(e.message || 'Upload mislukt', 'error');
else alert(e.message || 'Upload mislukt');
}
}
};
}
function appShell() {
return {
locale: 'nl',
brand: 'cucina',
drawerOpen: false,
navOpen: false,
sidebarWidth: 280,
showInstall: false,
authUser: null,
authPages: [],
_deferredPrompt: null,
clockDate: '—',
clockTime: '—',
_offset: 0,
isIos: false,
async initApp() {
this.brand = window.FoodlinkkBrand.detect(location.pathname);
this.isIos = /iPhone|iPad|iPod/i.test(navigator.userAgent) ||
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
// One-time migration off legacy no-op SW
if (!localStorage.getItem('flk-pwa-v6')) {
if (window.caches) {
const keys = await caches.keys();
await Promise.all(keys.map((k) => caches.delete(k)));
}
localStorage.setItem('flk-pwa-v6', '1');
localStorage.setItem('flk-cache-v4', '1');
}
if (window.I18n) await window.I18n.init();
this.locale = window.I18n ? window.I18n.locale() : 'nl';
this.initSidebarWidth();
this.initClock();
this.initPwa();
await this.initAuth();
},
async initAuth() {
try {
const r = await fetch('/api/auth/me', { credentials: 'same-origin' });
if (r.status === 401) {
location.href = '/login?next=' + encodeURIComponent(location.pathname + location.search);
return;
}
if (!r.ok) return;
const data = await r.json();
this.authUser = Object.assign({}, data.user || null, { _avTick: Date.now() });
this.authPages = (data.user && data.user.permissions && data.user.permissions.pages) || [];
this.applyNavPermissions();
window.dispatchEvent(new CustomEvent('flk-auth-ready', { detail: this.authUser }));
} catch (e) { /* ignore */ }
},
canPage(key) {
if (!this.authUser) return true;
if (this.authUser.role === 'admin') return true;
return this.authPages.indexOf(key) !== -1;
},
applyNavPermissions() {
if (!this.authUser || this.authUser.role === 'admin') return;
document.querySelectorAll('.app-sidebar a.nav-pill[data-perm]').forEach((a) => {
const key = a.getAttribute('data-perm');
a.style.display = this.canPage(key) ? '' : 'none';
});
},
avatarInitials() {
const u = this.authUser;
if (!u) return '?';
const n = (u.display_name || u.username || '?').trim();
const parts = n.split(/\s+/).filter(Boolean);
if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase();
return n.slice(0, 1).toUpperCase();
},
async uploadMyAvatar(ev) {
const file = ev.target.files && ev.target.files[0];
ev.target.value = '';
if (!file) return;
const fd = new FormData();
fd.append('file', file);
try {
const res = await fetch('/api/auth/me/avatar', { method: 'POST', body: fd, credentials: 'same-origin' });
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.detail || 'Upload mislukt');
this.authUser = Object.assign({}, data.user, { _avTick: Date.now() });
if (window.Cockpit && Cockpit.toast) Cockpit.toast('Profielfoto bijgewerkt', 'success');
} catch (e) {
if (window.Cockpit && Cockpit.toast) Cockpit.toast(e.message || 'Upload mislukt', 'error');
else alert(e.message || 'Upload mislukt');
}
},
initSidebarWidth() {
var saved = parseInt(localStorage.getItem('flk-sidebar-width') || '280', 10);
if (!isFinite(saved)) saved = 280;
this.sidebarWidth = Math.min(420, Math.max(220, saved));
document.documentElement.style.setProperty('--sidebar-width', this.sidebarWidth + 'px');
},
startSidebarResize(e) {
var self = this;
var startX = e.clientX;
var startW = this.sidebarWidth;
var handle = e.currentTarget;
handle.classList.add('is-dragging');
document.body.classList.add('sidebar-resizing');
function onMove(ev) {
var next = Math.min(420, Math.max(220, startW + (ev.clientX - startX)));
self.sidebarWidth = next;
document.documentElement.style.setProperty('--sidebar-width', next + 'px');
}
function onUp() {
handle.classList.remove('is-dragging');
document.body.classList.remove('sidebar-resizing');
localStorage.setItem('flk-sidebar-width', String(self.sidebarWidth));
window.removeEventListener('mousemove', onMove);
window.removeEventListener('mouseup', onUp);
}
window.addEventListener('mousemove', onMove);
window.addEventListener('mouseup', onUp);
},
setBrand(b) {
if (b === this.brand) return;
this.brand = b;
localStorage.setItem('foodlinkk-brand', b);
var path = location.pathname;
if (b === 'foodlinkk' && window.FoodlinkkBrand.isCucinaPath(path) && !window.FoodlinkkBrand.isFoodlinkkPath(path)) {
location.href = '/export-intel';
return;
}
if (b === 'cucina' && window.FoodlinkkBrand.isFoodlinkkPath(path)) {
location.href = '/';
return;
}
},
async setLocale(loc) {
if (window.I18n) await window.I18n.setLocale(loc);
this.locale = loc;
},
async initClock() {
try {
const r = await fetch('/api/server-time').then(x => x.json());
this._offset = new Date(r.utc).getTime() - Date.now();
} catch (e) { this._offset = 0; }
const tick = () => {
const d = new Date(Date.now() + this._offset);
this.clockDate = d.toLocaleDateString('nl-NL', { timeZone: 'Europe/Amsterdam', weekday: 'short', day: 'numeric', month: 'short' });
this.clockTime = d.toLocaleTimeString('nl-NL', { timeZone: 'Europe/Amsterdam', hour: '2-digit', minute: '2-digit', second: '2-digit' });
};
tick();
setInterval(tick, 1000);
},
isAndroid: false,
initPwa() {
const standalone = window.matchMedia('(display-mode: standalone)').matches
|| window.navigator.standalone === true;
if (standalone) return;
this.isAndroid = /Android/i.test(navigator.userAgent);
// SW alleen proberen op secure context (https/localhost). Op http in LAN faalt dit stil — app blijft gewoon werken.
if (window.isSecureContext && 'serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js', { scope: '/' }).catch(function (err) {
console.warn('SW register failed', err);
});
}
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
this._deferredPrompt = e;
if (!localStorage.getItem('pwa-dismiss')) this.showInstall = true;
});
if (!localStorage.getItem('pwa-dismiss') && (this.isIos || this.isAndroid)) {
this.showInstall = true;
}
},
dismissPwa() {
this.showInstall = false;
localStorage.setItem('pwa-dismiss', '1');
},
async installPwa() {
if (this._deferredPrompt) {
this._deferredPrompt.prompt();
await this._deferredPrompt.userChoice;
this._deferredPrompt = null;
this.dismissPwa();
return;
}
if (this.isIos) {
alert('Open http://10.4.7.18:8610 in Safari\n\n1. Tik op Deel (↗)\n2. Kies «Zet op beginscherm»\n3. Bevestig met «Zet erop»\n\nGeen HTTPS nodig — geen certificaatmelding.');
} else {
alert('Open http://10.4.7.18:8610 in Chrome\n\n1. Tik op ⋮ (rechtsboven)\n2. «Toevoegen aan startscherm» of «App installeren»\n3. Bevestig\n\nGeen HTTPS nodig — geen certificaatmelding.');
}
this.dismissPwa();
}
};
}
</script>
<script>
window.addEventListener('load', function () {
setTimeout(function () {
if (window.Alpine) return;
document.querySelectorAll('[x-cloak]').forEach(function (el) { el.removeAttribute('x-cloak'); });
document.body.classList.add('alpine-missing');
console.error('Alpine.js niet geladen — interactieve UI beperkt. Hard refresh (Ctrl+Shift+R).');
}, 2500);
});
</script>
<script>
window.FlkThemes = {
list: [
{ id: 'ocean', name: 'Ocean Cyan', desc: 'Diep navy met fel cyaan' },
{ id: 'aurora', name: 'Aurora', desc: 'Blauw-violet met cyaan highlights' },
{ id: 'neon', name: 'Neon Tide', desc: 'Donker neon cyaan + magenta' },
{ id: 'glacier', name: 'Glacier', desc: 'IJsblauw en helder sky-cyan' },
{ id: 'cobalt', name: 'Cobalt Pulse', desc: 'Kobaltblauw met aqua en amber' },
{ id: 'reef', name: 'Reef', desc: 'Teal-diep met helder mint' },
{ id: 'plasma', name: 'Plasma', desc: 'Paars-nacht met cyaan vonken' },
{ id: 'lagoon', name: 'Lagoon', desc: 'Tropisch blauwgroen water' },
{ id: 'indigo', name: 'Indigo Night', desc: 'Indigo met lavendel accents' },
{ id: 'skyline', name: 'Skyline', desc: 'Stad-nacht blauw + sky' },
{ id: 'mintvolt', name: 'Mint Volt', desc: 'Donker groen met mint-energie' },
{ id: 'sapphire', name: 'Sapphire', desc: 'Klassiek saffierblauw' },
{ id: 'arctic', name: 'Arctic Glow', desc: 'Koud grijsblauw met ice-cyan' },
{ id: 'hyperion', name: 'Hyperion', desc: 'Cyaan met lime-flits' },
{ id: 'meridian', name: 'Meridian', desc: 'Violet-meridiaan met aqua' }
],
sidebars: [
{ id: 'aqua', name: 'Deep Aqua', desc: 'Diep aqua, strak en koel' },
{ id: 'azure', name: 'Royal Azure', desc: 'Diep koningsblauw' },
{ id: 'turquoise', name: 'Abyss Teal', desc: 'Diep teal, premium' },
{ id: 'cerulean', name: 'Ocean Depth', desc: 'Diep oceaanblauw' },
{ id: 'electric', name: 'Electric Cyan', desc: 'Donker elektrisch cyaan' },
{ id: 'violet', name: 'Indigo Night', desc: 'Diep indigo-violet (geen roze)' },
{ id: 'emerald', name: 'Jade Depth', desc: 'Diep jade smaragd' },
{ id: 'midnight', name: 'Midnight Navy', desc: 'Diep marinezwart-blauw' },
{ id: 'steel', name: 'Steel Blue', desc: 'Strak staalblauw' },
{ id: 'nordic', name: 'Nordic Frost', desc: 'Koel nordic blauw' }
],
backgrounds: [
{ id: 'navy', name: 'Deep Navy', desc: 'Diep marineblauw' },
{ id: 'royal', name: 'Royal Blue', desc: 'Koningsblauw' },
{ id: 'azure', name: 'Azure Sea', desc: 'Helder azuur' },
{ id: 'sky', name: 'Sky Blue', desc: 'Heldere luchtblauw' },
{ id: 'cerulean', name: 'Cerulean', desc: 'Ceruleum' },
{ id: 'teal', name: 'Deep Teal', desc: 'Diep teal' },
{ id: 'lagoon', name: 'Lagoon Blue', desc: 'Lagune blauwgroen' },
{ id: 'cyan', name: 'Cyan Depth', desc: 'Cyaan diepte' },
{ id: 'indigo', name: 'Indigo Field', desc: 'Indigo veld' },
{ id: 'violet', name: 'Violet Dusk', desc: 'Violet schemer' },
{ id: 'sapphire', name: 'Sapphire', desc: 'Saffierblauw' },
{ id: 'midnight', name: 'Midnight', desc: 'Nachtblauw (niet zwart)' },
{ id: 'emerald', name: 'Emerald Sea', desc: 'Smaragd zee' },
{ id: 'slate', name: 'Slate Blue', desc: 'Leisteenblauw' },
{ id: 'graphite', name: 'Graphite Blue', desc: 'Grafietblauw' }
],
current: function () {
try { return localStorage.getItem('flk-theme') || 'ocean'; } catch (e) { return 'ocean'; }
},
currentSidebar: function () {
try { return localStorage.getItem('flk-sidebar-theme') || ''; } catch (e) { return ''; }
},
apply: function (id) {
var ok = this.list.some(function (t) { return t.id === id; });
if (!ok) id = 'ocean';
document.documentElement.setAttribute('data-theme', id);
try { localStorage.setItem('flk-theme', id); } catch (e) {}
return id;
},
applySidebar: function (id) {
if (!id) {
document.documentElement.removeAttribute('data-sidebar');
try { localStorage.removeItem('flk-sidebar-theme'); } catch (e) {}
return '';
}
var migrate = { coral: 'midnight', fuchsia: 'nordic', lime: 'steel' };
if (migrate[id]) id = migrate[id];
var ok = this.sidebars.some(function (t) { return t.id === id; });
if (!ok) {
document.documentElement.removeAttribute('data-sidebar');
try { localStorage.removeItem('flk-sidebar-theme'); } catch (e) {}
return '';
}
document.documentElement.setAttribute('data-sidebar', id);
try { localStorage.setItem('flk-sidebar-theme', id); } catch (e) {}
return id;
},
currentSidebarStyle: function () {
try { return localStorage.getItem('flk-sidebar-style') || 'gradient'; } catch (e) { return 'gradient'; }
},
applySidebarStyle: function (mode) {
if (mode !== 'plain') mode = 'gradient';
if (mode === 'plain') {
document.documentElement.setAttribute('data-sidebar-style', 'plain');
} else {
document.documentElement.removeAttribute('data-sidebar-style');
}
try { localStorage.setItem('flk-sidebar-style', mode); } catch (e) {}
return mode;
},
currentBg: function () {
try { return localStorage.getItem('flk-page-bg') || ''; } catch (e) { return ''; }
},
applyBg: function (id) {
if (!id) {
document.documentElement.removeAttribute('data-bg');
try { localStorage.removeItem('flk-page-bg'); } catch (e) {}
return '';
}
var ok = (this.backgrounds || []).some(function (t) { return t.id === id; });
if (!ok) {
document.documentElement.removeAttribute('data-bg');
try { localStorage.removeItem('flk-page-bg'); } catch (e) {}
return '';
}
document.documentElement.setAttribute('data-bg', id);
try { localStorage.setItem('flk-page-bg', id); } catch (e) {}
return id;
}
};
if (!document.documentElement.getAttribute('data-theme')) {
window.FlkThemes.apply(window.FlkThemes.current());
}
(function () {
var s = window.FlkThemes.currentSidebar();
if (s) window.FlkThemes.applySidebar(s);
if (window.FlkThemes.applySidebarStyle) window.FlkThemes.applySidebarStyle(window.FlkThemes.currentSidebarStyle());
var b = window.FlkThemes.currentBg();
if (b) window.FlkThemes.applyBg(b);
})();
</script>
{% block scripts %}{% endblock %}
</body>
</html>