mirror of
https://github.com/wasrusgen/zov-tech.git
synced 2026-06-03 17:44:48 +00:00
feat(miniapp): manager home v2 — greeting + hero today-task + 2x2 quick actions + active projects + bottom nav
This commit is contained in:
parent
435ef6817b
commit
017d179746
608
design-drafts/A_v2_desktop.html
Normal file
608
design-drafts/A_v2_desktop.html
Normal file
File diff suppressed because one or more lines are too long
@ -85,66 +85,174 @@ function getInitial(name) {
|
||||
|
||||
/* ----------------- Renders ----------------- */
|
||||
function renderManager(me) {
|
||||
const status = me.status || "active";
|
||||
const statusUntil = me.status_until ? `до ${me.status_until}` : "";
|
||||
const initial = me.user?.avatar_initial || getInitial(me.user?.full_name);
|
||||
const tgId = me.user?.tg_id ? `ID ${me.user.tg_id}` : "";
|
||||
// Новый главный экран — «утро менеджера»
|
||||
return renderManagerHome(me);
|
||||
}
|
||||
|
||||
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 = "";
|
||||
document.body.classList.add("has-bottom-nav");
|
||||
|
||||
// Greeting
|
||||
app.appendChild(el(`
|
||||
<header class="profile-card">
|
||||
<div class="role-tag">Менеджер</div>
|
||||
<div class="head-row">
|
||||
<div class="info">
|
||||
<div class="name">${me.user?.full_name || ""}</div>
|
||||
<div class="meta">${me.user?.salon || ""}</div>
|
||||
<header class="greeting">
|
||||
<div class="greeting-text">
|
||||
<div class="greeting-kicker">${timeOfDay()}</div>
|
||||
<div class="greeting-headline">${firstName},<br>
|
||||
<span class="accent">${phraseTail}</span>
|
||||
</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>
|
||||
<button class="bell-btn" aria-label="Уведомления">
|
||||
${ICONS.bell}
|
||||
<span class="dot"></span>
|
||||
</button>
|
||||
</header>
|
||||
`));
|
||||
|
||||
const sections = [
|
||||
{
|
||||
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));
|
||||
});
|
||||
|
||||
// Hero task
|
||||
if (todayTask) {
|
||||
app.appendChild(el(`
|
||||
<div class="footer-hint">
|
||||
<div class="signature">Куратор партнёрской сети — Руслан Васильев</div>
|
||||
<div class="meta">
|
||||
<a href="https://t.me/wasrusgen">@wasrusgen</a> · ЗОВ
|
||||
<section class="hero">
|
||||
<div class="hero-meta">
|
||||
<span class="left">
|
||||
<span>На сегодня</span><span class="sep">—</span><span>${todayTask.time}</span>
|
||||
</span>
|
||||
<span class="hero-tag">${todayTask.tag}</span>
|
||||
</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>
|
||||
`));
|
||||
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) {
|
||||
@ -152,6 +260,9 @@ function renderClient(me) {
|
||||
const greetName = me.user?.full_name || "Здравствуйте";
|
||||
|
||||
app.innerHTML = "";
|
||||
document.body.classList.remove("has-bottom-nav");
|
||||
const oldNav = document.getElementById("bottom-nav");
|
||||
if (oldNav) oldNav.remove();
|
||||
|
||||
app.appendChild(el(`
|
||||
<header class="profile-card">
|
||||
|
||||
@ -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>`,
|
||||
|
||||
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>`,
|
||||
};
|
||||
|
||||
@ -517,3 +517,407 @@ button { font: inherit; cursor: pointer; border: none; background: none; color:
|
||||
margin: 0 0 var(--s2);
|
||||
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;
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
<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">
|
||||
<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>
|
||||
<body>
|
||||
<main id="app">
|
||||
@ -20,7 +20,7 @@
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="assets/icons.js?v=20260509g"></script>
|
||||
<script src="assets/app.js?v=20260509g"></script>
|
||||
<script src="assets/icons.js?v=20260509h"></script>
|
||||
<script src="assets/app.js?v=20260509h"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user