feat(miniapp): manager home v2 — greeting + hero today-task + 2x2 quick actions + active projects + bottom nav

This commit is contained in:
wasrusgen 2026-05-09 12:59:41 +03:00
parent 435ef6817b
commit 017d179746
5 changed files with 1189 additions and 48 deletions

File diff suppressed because one or more lines are too long

View File

@ -85,66 +85,174 @@ function getInitial(name) {
/* ----------------- Renders ----------------- */ /* ----------------- Renders ----------------- */
function renderManager(me) { function renderManager(me) {
const status = me.status || "active"; // Новый главный экран — «утро менеджера»
const statusUntil = me.status_until ? `до ${me.status_until}` : ""; return renderManagerHome(me);
const initial = me.user?.avatar_initial || getInitial(me.user?.full_name); }
const tgId = me.user?.tg_id ? `ID ${me.user.tg_id}` : "";
function timeOfDay(date = new Date()) {
const h = date.getHours();
if (h >= 5 && h < 12) return "Доброе утро";
if (h >= 12 && h < 18) return "Добрый день";
if (h >= 18 && h < 23) return "Добрый вечер";
return "Доброй ночи";
}
function pluralRu(n, forms) {
// forms = ["замер", "замера", "замеров"]
const mod10 = n % 10, mod100 = n % 100;
if (mod100 >= 11 && mod100 <= 14) return forms[2];
if (mod10 === 1) return forms[0];
if (mod10 >= 2 && mod10 <= 4) return forms[1];
return forms[2];
}
function renderManagerHome(me) {
// === MOCK DATA (Этап 1 — визуал, без реального backend) ===
const firstName = (me.user?.full_name || "").split(/\s+/)[0] || "Артём";
const todayTask = {
time: "15:30",
tag: "ЗАМЕР",
client: "А. Пестова",
address: "ЖК Сады Пекина, корп. 3",
phone: "+7 999 000-00-00",
};
const projects = [
{ name: "Семья Иваниковых", address: "ул. Орджоникидзе, 14 — 47", stage: "Согласование", date: "14 мая", progress: 0.40, statusLabel: "Ожидает клиента", statusKind: "waiting" },
{ name: "Кабанова И. С.", address: "Никольская набережная, 20", stage: "Производство", date: "21 мая", progress: 0.60, statusLabel: "В работе", statusKind: "active" },
{ name: "Карелин А.", address: "посёлок Сосновый, дом 4", stage: "Замер", date: "сегодня", progress: 0.10, statusLabel: "Срочно", statusKind: "urgent" },
{ name: "Петросян Г.", address: "ул. Лесная, 18 — 12", stage: "Доставка", date: "16 мая", progress: 0.85, statusLabel: "В работе", statusKind: "active" },
{ name: "Тимирясов И.", address: "пос. Барвиха, дом 8", stage: "Монтаж", date: "11 мая", progress: 0.95, statusLabel: "Завершается", statusKind: "active" },
];
const unreadChats = 2;
const tasksTodayCount = todayTask ? 1 : 0;
const taskWord = pluralRu(tasksTodayCount, ["замер", "замера", "замеров"]);
const phraseTail = tasksTodayCount === 0 ? "ничего на сегодня" : `${tasksTodayCount === 1 ? "один" : tasksTodayCount} ${taskWord} сегодня`;
// === RENDER ===
app.innerHTML = ""; app.innerHTML = "";
document.body.classList.add("has-bottom-nav");
// Greeting
app.appendChild(el(` app.appendChild(el(`
<header class="profile-card"> <header class="greeting">
<div class="role-tag">Менеджер</div> <div class="greeting-text">
<div class="head-row"> <div class="greeting-kicker">${timeOfDay()}</div>
<div class="info"> <div class="greeting-headline">${firstName},<br>
<div class="name">${me.user?.full_name || ""}</div> <span class="accent">${phraseTail}</span>
<div class="meta">${me.user?.salon || ""}</div>
</div> </div>
<div class="avatar">${initial}</div>
</div>
<div class="meta-row">
<span class="status-row">
<span class="status-dot ${status}"></span>
<span>${statusLabel(status)}</span>
</span>
${statusUntil ? `<span class="sep">·</span><span>${statusUntil}</span>` : ""}
${tgId ? `<span class="sep">·</span><span>${tgId}</span>` : ""}
</div> </div>
<button class="bell-btn" aria-label="Уведомления">
${ICONS.bell}
<span class="dot"></span>
</button>
</header> </header>
`)); `));
const sections = [ // Hero task
{ if (todayTask) {
label: "Работа с клиентами",
items: [
{ icon: "wrench", color: "green", label: "Подбор техники для клиента", href: "#/m/podbor" },
{ icon: "ruler", color: "blue", label: "Замеры", href: "#/m/measurements" },
{ icon: "clipboard", color: "gold", label: "Заявки клиентов", soon: true },
{ icon: "briefcase", color: "gray", label: "Сделки", soon: true },
],
},
{
label: "Аккаунт",
items: [
{ icon: "wallet", color: "gold", label: "Мой статус и доступ", href: "#/m/status" },
{ icon: "help", color: "blue", label: "Связь с куратором", href: "#/m/help" },
],
},
];
sections.forEach(section => {
app.appendChild(el(`<div class="section-label">${section.label}</div>`));
app.appendChild(buildMenu(section.items));
});
app.appendChild(el(` app.appendChild(el(`
<div class="footer-hint"> <section class="hero">
<div class="signature">Куратор партнёрской сети Руслан Васильев</div> <div class="hero-meta">
<div class="meta"> <span class="left">
<a href="https://t.me/wasrusgen">@wasrusgen</a> · ЗОВ <span>На сегодня</span><span class="sep"></span><span>${todayTask.time}</span>
</span>
<span class="hero-tag">${todayTask.tag}</span>
</div> </div>
<div class="hero-client">${todayTask.client}</div>
<div class="hero-address">${todayTask.address}</div>
<div class="hero-actions">
<button class="btn-gold">${ICONS.ruler}<span>Начать замер</span></button>
<a class="btn-icon-dark" href="tel:${todayTask.phone}" aria-label="Позвонить">${ICONS.phone}</a>
</div>
</section>
`));
}
// Quick actions
const quickActions = [
{ icon: "camera", title: "Новый замер", subtitle: "С фото" },
{ icon: "cube", title: "3D просмотр", subtitle: "Проекты" },
{ icon: "bolt", title: "Коммуникации", subtitle: "Чек-лист" },
{ icon: "package", title: "Каталог техники", subtitle: "Встройка" },
];
app.appendChild(el(`<div class="section-head"><span class="label">Быстрые действия</span></div>`));
const grid = el(`<div class="quick-grid"></div>`);
quickActions.forEach(qa => {
const card = el(`
<button class="quick-card">
<div class="icon">${ICONS[qa.icon] || ""}</div>
<div>
<div class="title">${qa.title}</div>
<div class="subtitle">${qa.subtitle}</div>
</div>
</button>
`);
card.addEventListener("click", () => { haptic("impact"); tg?.showAlert?.(`«${qa.title}» — скоро`); });
grid.appendChild(card);
});
app.appendChild(grid);
// Active projects
app.appendChild(el(`
<div class="section-head">
<span class="label">Активные проекты <span class="count">· ${projects.length}</span></span>
<span class="more">Все</span>
</div> </div>
`)); `));
const list = el(`<div class="project-list"></div>`);
projects.forEach(p => {
const card = el(`
<article class="project-card">
<div class="project-head">
<div class="project-title">${p.name}</div>
<span class="project-pill ${p.statusKind}">${p.statusLabel}</span>
</div>
<div class="project-address">${p.address}</div>
<div class="project-progress"><div class="bar" style="width:${Math.round(p.progress * 100)}%"></div></div>
<div class="project-foot">
<span class="stage">${p.stage}</span>
<span>${p.date}</span>
</div>
</article>
`);
card.addEventListener("click", () => { haptic("impact"); tg?.showAlert?.(`Проект «${p.name}» — скоро`); });
list.appendChild(card);
});
app.appendChild(list);
// Bottom nav (fixed, outside #app)
renderBottomNav("home", { unreadChats });
}
function renderBottomNav(active, opts = {}) {
// Удаляем предыдущий, если есть
const old = document.getElementById("bottom-nav");
if (old) old.remove();
const tabs = [
{ key: "home", icon: "home", label: "Главная" },
{ key: "projects", icon: "folder", label: "Проекты" },
{ key: "fab", icon: "plus", label: "" },
{ key: "chat", icon: "chat", label: "Чат", badge: opts.unreadChats || 0 },
{ key: "profile", icon: "user", label: "Профиль" },
];
const nav = el(`<nav class="bottom-nav" id="bottom-nav" role="tablist"></nav>`);
tabs.forEach(t => {
const isFab = t.key === "fab";
const isActive = t.key === active;
const btn = el(`
<button class="${isFab ? "fab" : ""} ${isActive && !isFab ? "active" : ""}" aria-label="${t.label || "Создать"}">
${ICONS[t.icon] || ""}
${isFab || !t.label ? "" : `<span>${t.label}</span>`}
${t.badge ? `<span class="badge">${t.badge}</span>` : ""}
</button>
`);
btn.addEventListener("click", () => {
haptic("impact");
if (t.key !== active) tg?.showAlert?.(`«${t.label || "Новое"}» — скоро`);
});
nav.appendChild(btn);
});
document.body.appendChild(nav);
} }
function renderClient(me) { function renderClient(me) {
@ -152,6 +260,9 @@ function renderClient(me) {
const greetName = me.user?.full_name || "Здравствуйте"; const greetName = me.user?.full_name || "Здравствуйте";
app.innerHTML = ""; app.innerHTML = "";
document.body.classList.remove("has-bottom-nav");
const oldNav = document.getElementById("bottom-nav");
if (oldNav) oldNav.remove();
app.appendChild(el(` app.appendChild(el(`
<header class="profile-card"> <header class="profile-card">

View File

@ -27,4 +27,22 @@ const ICONS = {
user: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.2"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg>`, user: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.2"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg>`,
chevron: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.2"><path d="m9 6 6 6-6 6"/></svg>`, chevron: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.2"><path d="m9 6 6 6-6 6"/></svg>`,
bell: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>`,
camera: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"/><circle cx="12" cy="13" r="3"/></svg>`,
cube: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M21 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.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/></svg>`,
bolt: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/></svg>`,
package: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="m7.5 4.27 9 5.15"/><path d="M21 8a2 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.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/></svg>`,
folder: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" 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>`,
plus: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.2"><path d="M5 12h14"/><path d="M12 5v14"/></svg>`,
chat: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>`,
home: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"/><path d="M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>`,
}; };

View File

@ -517,3 +517,407 @@ button { font: inherit; cursor: pointer; border: none; background: none; color:
margin: 0 0 var(--s2); margin: 0 0 var(--s2);
color: var(--ink); color: var(--ink);
} }
/* ============================================================
HOME Manager dashboard (mockup v2)
============================================================ */
/* App content needs bottom padding for fixed nav */
body.has-bottom-nav #app {
padding-bottom: calc(96px + env(safe-area-inset-bottom));
}
/* ============= Greeting ============= */
.greeting {
display: flex;
align-items: flex-start;
gap: var(--s4);
margin-bottom: var(--s5);
}
.greeting-text { flex: 1; min-width: 0; }
.greeting-kicker {
font-family: var(--font-mono);
font-size: 10.5px;
font-weight: 500;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: var(--s2);
}
.greeting-headline {
font-family: var(--font-display);
font-style: italic;
font-size: 32px;
font-weight: 400;
line-height: 1.1;
letter-spacing: -0.02em;
color: var(--ink);
}
.greeting-headline .accent {
color: var(--accent-2); /* walnut акцент на части фразы */
}
.bell-btn {
width: 40px;
height: 40px;
border-radius: var(--r-pill);
background: var(--card);
border: 1px solid var(--line-strong);
display: grid;
place-items: center;
position: relative;
flex-shrink: 0;
color: var(--ink);
cursor: pointer;
}
.bell-btn svg { width: 20px; height: 20px; stroke-width: 1.6; }
.bell-btn .dot {
position: absolute;
top: 7px;
right: 9px;
width: 7px;
height: 7px;
border-radius: var(--r-pill);
background: #C0392B;
border: 1.5px solid var(--paper);
}
/* ============= Hero task card ============= */
.hero {
background: var(--ink);
color: var(--paper);
border-radius: var(--r-card);
padding: var(--s4);
margin-bottom: var(--s6);
position: relative;
overflow: hidden;
}
.hero::before {
/* тонкая бренд-полоса слева */
content: "";
position: absolute;
top: 0; bottom: 0; left: 0;
width: 3px;
background: linear-gradient(180deg, var(--accent-2), var(--status-active));
}
.hero-meta {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--s2);
font-family: var(--font-mono);
font-size: 10.5px;
font-weight: 500;
letter-spacing: 0.12em;
text-transform: uppercase;
color: rgba(251, 247, 240, 0.55);
margin-bottom: var(--s3);
}
.hero-meta .left { display: inline-flex; align-items: center; gap: var(--s2); }
.hero-meta .left .sep { color: rgba(251, 247, 240, 0.30); }
.hero-tag {
font-family: var(--font-mono);
font-size: 10px;
font-weight: 500;
letter-spacing: 0.14em;
padding: 3px 8px;
border-radius: var(--r-tag);
background: rgba(251, 247, 240, 0.08);
color: rgba(251, 247, 240, 0.85);
}
.hero-client {
font-family: var(--font-ui);
font-size: 22px;
font-weight: 600;
letter-spacing: -0.01em;
color: var(--paper);
margin-bottom: 4px;
}
.hero-address {
font-family: var(--font-ui);
font-size: 14px;
color: rgba(251, 247, 240, 0.70);
margin-bottom: var(--s4);
}
.hero-actions {
display: flex;
gap: var(--s2);
}
.btn-gold {
flex: 1;
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--s2);
padding: 12px var(--s3);
border-radius: var(--r-btn);
background: #C9985E;
color: #1F1A14;
font-family: var(--font-ui);
font-size: 14px;
font-weight: 600;
letter-spacing: -0.01em;
cursor: pointer;
}
.btn-gold svg { width: 18px; height: 18px; stroke-width: 1.6; }
.btn-icon-dark {
width: 48px;
flex-shrink: 0;
display: grid;
place-items: center;
border-radius: var(--r-btn);
background: rgba(251, 247, 240, 0.10);
color: var(--paper);
cursor: pointer;
}
.btn-icon-dark svg { width: 20px; height: 20px; stroke-width: 1.6; }
/* ============= Section header (with right-action) ============= */
.section-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--s3);
padding: 0 var(--s2);
margin: var(--s5) 0 var(--s2);
}
.section-head .label {
font-family: var(--font-mono);
font-size: 10.5px;
font-weight: 500;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--muted);
}
.section-head .label .count {
color: var(--ink);
margin-left: 4px;
}
.section-head .more {
font-family: var(--font-mono);
font-size: 10.5px;
font-weight: 500;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--accent-2);
cursor: pointer;
}
/* ============= Quick actions (2x2 grid) ============= */
.quick-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--s3);
}
.quick-card {
background: var(--card);
border: 1px solid var(--line-strong);
border-radius: var(--r-card);
padding: var(--s3);
min-height: 88px;
display: flex;
flex-direction: column;
justify-content: space-between;
cursor: pointer;
transition: background 0.12s;
}
.quick-card:active { background: var(--paper-2); }
.quick-card .icon {
width: 28px;
height: 28px;
display: grid;
place-items: center;
color: var(--accent-2);
margin-bottom: var(--s4);
}
.quick-card .icon svg { width: 22px; height: 22px; stroke-width: 1.6; }
.quick-card .title {
font-family: var(--font-ui);
font-size: 14.5px;
font-weight: 600;
letter-spacing: -0.01em;
color: var(--ink);
margin-bottom: 2px;
}
.quick-card .subtitle {
font-family: var(--font-mono);
font-size: 10px;
font-weight: 500;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
}
/* ============= Project card ============= */
.project-list {
display: flex;
flex-direction: column;
gap: var(--s3);
}
.project-card {
background: var(--card);
border: 1px solid var(--line-strong);
border-radius: var(--r-card);
padding: var(--s4);
cursor: pointer;
}
.project-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--s2);
margin-bottom: 4px;
}
.project-title {
font-family: var(--font-ui);
font-size: 16px;
font-weight: 600;
letter-spacing: -0.01em;
color: var(--ink);
flex: 1;
}
.project-pill {
font-family: var(--font-mono);
font-size: 9.5px;
font-weight: 500;
letter-spacing: 0.10em;
text-transform: uppercase;
padding: 3px 8px;
border-radius: var(--r-tag);
white-space: nowrap;
}
.project-pill.waiting { background: var(--paper-2); color: var(--accent-2); }
.project-pill.active { background: var(--paper-2); color: var(--status-active); }
.project-pill.urgent { background: rgba(192,57,43,0.10); color: #8C3F1E; }
.project-address {
font-family: var(--font-ui);
font-size: 13.5px;
color: var(--muted);
margin-bottom: var(--s3);
}
.project-progress {
height: 3px;
background: var(--line);
border-radius: var(--r-pill);
overflow: hidden;
margin-bottom: var(--s2);
}
.project-progress .bar {
height: 100%;
background: var(--accent-2);
border-radius: inherit;
}
.project-foot {
display: flex;
justify-content: space-between;
font-family: var(--font-mono);
font-size: 10.5px;
font-weight: 500;
letter-spacing: 0.10em;
text-transform: uppercase;
color: var(--muted);
}
.project-foot .stage { color: var(--ink-2); }
/* ============= Bottom navigation ============= */
.bottom-nav {
position: fixed;
left: 0; right: 0; bottom: 0;
z-index: 100;
display: flex;
align-items: flex-end;
justify-content: space-around;
background: var(--paper);
border-top: 1px solid var(--line-strong);
padding: 6px 0 calc(8px + env(safe-area-inset-bottom));
font-family: var(--font-mono);
font-size: 9.5px;
font-weight: 500;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.bottom-nav button {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
color: var(--muted);
padding: 6px 2px;
position: relative;
cursor: pointer;
}
.bottom-nav button.active { color: var(--ink); }
.bottom-nav button svg { width: 22px; height: 22px; stroke-width: 1.6; }
.bottom-nav .fab {
flex: 0 0 auto;
width: 52px;
height: 52px;
border-radius: var(--r-pill);
background: var(--ink);
color: var(--paper);
display: grid;
place-items: center;
margin: 0 8px;
transform: translateY(-12px);
box-shadow: 0 4px 14px rgba(31, 26, 20, 0.25);
}
.bottom-nav .fab svg { width: 22px; height: 22px; stroke-width: 2.2; color: var(--paper); }
.bottom-nav .badge {
position: absolute;
top: 2px;
right: 22%;
min-width: 16px;
height: 16px;
padding: 0 4px;
border-radius: var(--r-pill);
background: #C0392B;
color: var(--paper);
font-size: 10px;
font-weight: 600;
display: grid;
place-items: center;
letter-spacing: 0;
}

View File

@ -12,7 +12,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <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&family=Geist:wght@400;500;600&family=Newsreader:ital,wght@0,400..600;1,400..600&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Geist:wght@400;500;600&family=Newsreader:ital,wght@0,400..600;1,400..600&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap">
<script src="https://telegram.org/js/telegram-web-app.js"></script> <script src="https://telegram.org/js/telegram-web-app.js"></script>
<link rel="stylesheet" href="assets/styles.css?v=20260509g"> <link rel="stylesheet" href="assets/styles.css?v=20260509h">
</head> </head>
<body> <body>
<main id="app"> <main id="app">
@ -20,7 +20,7 @@
<div class="spinner"></div> <div class="spinner"></div>
</div> </div>
</main> </main>
<script src="assets/icons.js?v=20260509g"></script> <script src="assets/icons.js?v=20260509h"></script>
<script src="assets/app.js?v=20260509g"></script> <script src="assets/app.js?v=20260509h"></script>
</body> </body>
</html> </html>