mirror of
https://github.com/wasrusgen/wasrusgen1-crm.git
synced 2026-06-03 15:24:47 +00:00
887 lines
66 KiB
HTML
887 lines
66 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Архитектура платформы @wasrusgen1 CRM</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&family=Montserrat:wght@700;800;900&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
:root{
|
||
--bg:#0F0F1A;
|
||
--accent:#003E7E;
|
||
--accent2:#4338CA;
|
||
--success:#76BD22;
|
||
--warn:#F59E0B;
|
||
--danger:#EF4444;
|
||
--text:#F8FAFC;
|
||
--card:#1A1A2E;
|
||
--muted:#94A3B8;
|
||
--border:rgba(148,163,184,.16);
|
||
--mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
|
||
--display:'Montserrat',sans-serif;
|
||
}
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
html,body{height:100%}
|
||
body{
|
||
font-family:'Inter',system-ui,sans-serif;
|
||
background:linear-gradient(145deg,#0A0A18 0%,#13103A 45%,#0D0C22 75%,#0F0F1A 100%);
|
||
color:var(--text);
|
||
overflow:hidden;
|
||
}
|
||
/* ===== DECK ===== */
|
||
.deck{position:fixed;inset:0}
|
||
.slide{
|
||
position:absolute;inset:0;
|
||
display:flex;flex-direction:column;
|
||
padding:64px 88px 90px;
|
||
opacity:0;visibility:hidden;
|
||
transform:translateY(14px);
|
||
transition:opacity .5s ease,transform .5s ease,visibility .5s;
|
||
overflow:hidden;
|
||
}
|
||
.slide.active{opacity:1;visibility:visible;transform:none;z-index:2}
|
||
/* aspect frame to keep 16:9 readable on any screen */
|
||
.stage{
|
||
position:relative;width:100%;height:100%;
|
||
max-width:1920px;max-height:1080px;margin:auto;
|
||
}
|
||
|
||
/* per-slide brand watermark (top-right) */
|
||
.slide-brand{
|
||
position:absolute;top:0;right:0;z-index:5;display:flex;align-items:center;
|
||
pointer-events:none;
|
||
}
|
||
|
||
/* background glows */
|
||
.slide::before{
|
||
content:"";position:absolute;width:760px;height:760px;border-radius:50%;
|
||
background:radial-gradient(circle,rgba(67,56,202,.16),transparent 62%);
|
||
top:-300px;right:-200px;pointer-events:none;
|
||
}
|
||
.slide::after{
|
||
content:"";position:absolute;width:620px;height:620px;border-radius:50%;
|
||
background:radial-gradient(circle,rgba(118,189,34,.08),transparent 65%);
|
||
bottom:-280px;left:-160px;pointer-events:none;
|
||
}
|
||
|
||
/* ===== TYPO ===== */
|
||
.kicker{
|
||
font-family:var(--mono);font-size:14px;letter-spacing:.28em;text-transform:uppercase;
|
||
color:var(--accent2);margin-bottom:18px;display:flex;align-items:center;gap:12px;
|
||
}
|
||
.kicker::before{content:"";width:30px;height:2px;background:var(--accent);display:inline-block}
|
||
h1{font-family:var(--display);font-size:64px;font-weight:900;line-height:1.04;letter-spacing:-.02em}
|
||
h2{font-family:var(--display);font-size:44px;font-weight:800;line-height:1.08;letter-spacing:-.015em;margin-bottom:10px}
|
||
.sub{color:var(--muted);font-size:20px;font-weight:400;line-height:1.5;max-width:980px}
|
||
.slide-body{flex:1;min-height:0;margin-top:34px;display:flex;flex-direction:column}
|
||
|
||
/* ===== TITLE SLIDE ===== */
|
||
.title-slide{justify-content:center}
|
||
.title-slide h1{font-size:84px;max-width:1300px}
|
||
.title-slide .lede{font-size:30px;color:var(--accent2);font-weight:600;margin-top:26px}
|
||
.brandtag{
|
||
display:inline-flex;align-items:center;gap:14px;
|
||
font-family:var(--mono);font-size:16px;color:var(--muted);
|
||
border:1px solid var(--border);border-radius:100px;padding:12px 22px;margin-bottom:40px;
|
||
background:rgba(26,26,46,.6);width:max-content;
|
||
}
|
||
.dot{width:10px;height:10px;border-radius:50%;background:var(--success);box-shadow:0 0 14px var(--success)}
|
||
.title-meta{
|
||
margin-top:64px;display:flex;gap:64px;flex-wrap:wrap;
|
||
}
|
||
.title-meta div span{display:block;font-family:var(--mono);font-size:13px;color:var(--muted);letter-spacing:.1em;text-transform:uppercase;margin-bottom:6px}
|
||
.title-meta div strong{font-size:24px;font-weight:700}
|
||
|
||
/* ===== CARDS / GRID ===== */
|
||
.grid{display:grid;gap:22px}
|
||
.g2{grid-template-columns:repeat(2,1fr)}
|
||
.g3{grid-template-columns:repeat(3,1fr)}
|
||
.g4{grid-template-columns:repeat(4,1fr)}
|
||
.card{
|
||
background:linear-gradient(160deg,var(--card),rgba(26,26,46,.55));
|
||
border:1px solid var(--border);border-radius:18px;padding:28px 26px;
|
||
position:relative;overflow:hidden;
|
||
}
|
||
.card .ic{
|
||
width:46px;height:46px;border-radius:12px;display:flex;align-items:center;justify-content:center;
|
||
font-size:22px;margin-bottom:16px;background:rgba(0,62,126,.18);border:1px solid rgba(0,62,126,.4);
|
||
}
|
||
.card h3{font-size:21px;font-weight:700;margin-bottom:8px}
|
||
.card p{color:var(--muted);font-size:16px;line-height:1.55}
|
||
.card.accent{border-color:rgba(67,56,202,.5)}
|
||
.card.accent .ic{background:rgba(67,56,202,.16);border-color:rgba(67,56,202,.4)}
|
||
.card.success .ic{background:rgba(118,189,34,.16);border-color:rgba(118,189,34,.4)}
|
||
.card.warn .ic{background:rgba(245,158,11,.14);border-color:rgba(245,158,11,.35)}
|
||
.tag{font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--accent2);font-weight:600}
|
||
.pill{display:inline-block;font-family:var(--mono);font-size:13px;padding:4px 12px;border-radius:100px;border:1px solid var(--border);color:var(--muted)}
|
||
.pill.rec{color:var(--success);border-color:rgba(118,189,34,.4);background:rgba(118,189,34,.1)}
|
||
|
||
/* ===== CODE / DIAGRAM BLOCK ===== */
|
||
.codeblock{
|
||
font-family:var(--mono);background:#0A0A12;border:1px solid var(--border);
|
||
border-radius:16px;padding:30px 34px;font-size:18px;line-height:1.85;color:#CBD5E1;
|
||
box-shadow:inset 0 0 0 1px rgba(0,62,126,.08);position:relative;
|
||
}
|
||
.codeblock .winbar{position:absolute;top:14px;left:18px;display:flex;gap:7px}
|
||
.codeblock .winbar i{width:11px;height:11px;border-radius:50%;display:inline-block}
|
||
.codeblock pre{margin-top:14px;white-space:pre;overflow:auto}
|
||
.c-key{color:var(--accent2)} .c-ok{color:var(--success)} .c-warn{color:var(--warn)}
|
||
.c-mut{color:var(--muted)} .c-acc{color:#5B8BD0;font-weight:700} .c-file{color:#F8FAFC}
|
||
|
||
/* ===== ARCH MAP ===== */
|
||
.archmap{display:flex;flex-direction:column;align-items:center;gap:0;font-family:var(--mono)}
|
||
.node{
|
||
border:1px solid var(--border);border-radius:14px;padding:16px 28px;background:var(--card);
|
||
text-align:center;min-width:300px;
|
||
}
|
||
.node strong{display:block;font-size:20px;font-family:'Inter';font-weight:700}
|
||
.node span{font-size:13px;color:var(--muted)}
|
||
.node.master{border-color:var(--accent2);box-shadow:0 0 32px rgba(67,56,202,.28);background:rgba(67,56,202,.1)}
|
||
.node.git{border-color:rgba(0,62,126,.5)}
|
||
.node.vps{border-color:rgba(118,189,34,.4);background:rgba(118,189,34,.05)}
|
||
.arrow{color:var(--accent2);font-size:22px;padding:8px 0;font-family:var(--mono)}
|
||
.arrow small{display:block;font-size:11px;color:var(--muted);letter-spacing:.1em}
|
||
.tenants{display:flex;gap:18px;margin-top:6px}
|
||
.tenant{
|
||
border:1px dashed rgba(118,189,34,.4);border-radius:12px;padding:14px 20px;
|
||
font-family:var(--mono);font-size:14px;text-align:center;background:rgba(118,189,34,.04);
|
||
}
|
||
.tenant b{color:var(--success)} .tenant span{display:block;color:var(--muted);font-size:12px;margin-top:4px}
|
||
|
||
/* ===== TABLE ===== */
|
||
table{width:100%;border-collapse:collapse;font-size:18px}
|
||
th,td{text-align:left;padding:18px 22px;border-bottom:1px solid var(--border)}
|
||
th{font-family:var(--mono);font-size:13px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);font-weight:600}
|
||
td{color:#E2E8F0}
|
||
tr:last-child td{border-bottom:none}
|
||
td .mono{font-family:var(--mono)}
|
||
.num{font-family:var(--mono);font-weight:700;color:var(--accent2);text-align:right}
|
||
.num.ok{color:var(--success)}
|
||
tbody tr:hover{background:rgba(0,62,126,.07)}
|
||
|
||
/* ===== CHECKLIST ===== */
|
||
.checklist{display:grid;grid-template-columns:1fr 1fr;gap:16px 38px;align-self:stretch}
|
||
.check{
|
||
display:flex;align-items:center;gap:18px;cursor:pointer;user-select:none;
|
||
background:var(--card);border:1px solid var(--border);border-radius:14px;padding:20px 24px;
|
||
transition:.25s;
|
||
}
|
||
.check:hover{border-color:rgba(0,62,126,.5);transform:translateX(3px)}
|
||
.box{
|
||
width:30px;height:30px;border-radius:8px;border:2px solid var(--muted);flex:0 0 auto;
|
||
display:flex;align-items:center;justify-content:center;transition:.2s;font-weight:900;color:transparent;
|
||
}
|
||
.check.done .box{background:var(--success);border-color:var(--success);color:#0c2b06}
|
||
.check.done .box::after{content:"✓"}
|
||
.check .label{font-size:19px;font-weight:500;transition:.2s}
|
||
.check.done .label{color:var(--muted);text-decoration:line-through}
|
||
.check .mono{font-family:var(--mono);color:var(--accent2);font-size:16px}
|
||
|
||
/* ===== TIMELINE / ROADMAP ===== */
|
||
.timeline{display:flex;align-items:stretch;gap:0;margin-top:30px}
|
||
.phase{flex:1;position:relative;padding:0 14px}
|
||
.phase .ring{
|
||
width:26px;height:26px;border-radius:50%;border:3px solid var(--accent);
|
||
background:var(--bg);margin:0 auto 22px;position:relative;z-index:2;
|
||
}
|
||
.phase.now .ring{background:var(--accent);box-shadow:0 0 22px var(--accent)}
|
||
.phase::before{
|
||
content:"";position:absolute;top:11px;left:-50%;width:100%;height:3px;
|
||
background:var(--border);z-index:1;
|
||
}
|
||
.phase:first-child::before{display:none}
|
||
.phase.now::before,.phase.now ~ .phase::before{background:var(--border)}
|
||
.phase-card{
|
||
background:var(--card);border:1px solid var(--border);border-radius:16px;padding:24px 22px;text-align:center;height:100%;
|
||
}
|
||
.phase.now .phase-card{border-color:var(--accent);background:rgba(0,62,126,.1)}
|
||
.phase .ph-tag{font-family:var(--mono);font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:var(--accent2);margin-bottom:8px}
|
||
.phase h3{font-size:22px;font-weight:700;margin-bottom:10px}
|
||
.phase p{font-size:15px;color:var(--muted);line-height:1.5}
|
||
|
||
/* ===== ER DIAGRAM ===== */
|
||
.er .codeblock{font-size:16px;line-height:1.75}
|
||
|
||
/* generic list */
|
||
.blist{list-style:none;display:flex;flex-direction:column;gap:14px}
|
||
.blist li{display:flex;gap:14px;font-size:19px;line-height:1.5;align-items:flex-start}
|
||
.blist li::before{content:"▸";color:var(--accent2);font-weight:900;margin-top:2px}
|
||
.blist li b{color:var(--text)}
|
||
.blist li span{color:var(--muted)}
|
||
|
||
/* steps for lifecycle */
|
||
.steps{display:flex;flex-direction:column;gap:14px}
|
||
.step{display:flex;gap:22px;align-items:center;background:var(--card);border:1px solid var(--border);border-radius:14px;padding:20px 26px}
|
||
.step .no{font-family:var(--mono);font-size:26px;font-weight:700;color:var(--accent2);width:46px;flex:0 0 auto}
|
||
.step .txt{flex:1}
|
||
.step .txt b{font-size:20px;font-weight:700;display:block}
|
||
.step .txt span{color:var(--muted);font-size:16px}
|
||
.step .time{font-family:var(--mono);font-size:15px;color:var(--success);background:rgba(118,189,34,.12);border:1px solid rgba(118,189,34,.3);border-radius:100px;padding:7px 16px;white-space:nowrap}
|
||
|
||
/* P&L econ */
|
||
.pl{display:grid;grid-template-columns:1fr 1fr;gap:26px}
|
||
.pl .block{background:var(--card);border:1px solid var(--border);border-radius:16px;padding:30px}
|
||
.pl .block.rev{border-color:rgba(118,189,34,.35)}
|
||
.pl .block.cost{border-color:rgba(245,158,11,.35)}
|
||
.pl .row{display:flex;justify-content:space-between;padding:12px 0;border-bottom:1px dashed var(--border);font-size:18px}
|
||
.pl .row:last-child{border:none}
|
||
.pl .row b{font-family:var(--mono)}
|
||
.be{margin-top:24px;display:flex;gap:24px}
|
||
.be .stat{flex:1;background:linear-gradient(160deg,rgba(0,62,126,.14),rgba(26,26,46,.6));border:1px solid rgba(0,62,126,.4);border-radius:16px;padding:24px 28px}
|
||
.be .stat span{font-family:var(--mono);font-size:13px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}
|
||
.be .stat strong{display:block;font-size:38px;font-weight:800;color:var(--accent2);margin-top:6px}
|
||
|
||
/* ===== PHONE FRAMES (new slides) ===== */
|
||
.screens{display:flex;gap:40px;justify-content:center;align-items:flex-start;flex-wrap:wrap}
|
||
.phone{
|
||
width:330px;border-radius:30px;background:#000;padding:8px;
|
||
box-shadow:0 20px 60px rgba(0,0,0,.5);flex:0 0 auto;
|
||
}
|
||
.phone.sm{width:300px}
|
||
.phscreen{
|
||
border-radius:24px;background:#F5F6F8;overflow:hidden;color:#1A1A2E;
|
||
font-family:'Inter',sans-serif;
|
||
}
|
||
.statusbar{
|
||
background:#003E7E;color:#fff;height:30px;display:flex;align-items:center;
|
||
justify-content:space-between;padding:0 16px;font-size:12px;font-weight:600;font-family:var(--mono);
|
||
}
|
||
.statusbar .sb-r{display:flex;gap:5px;align-items:center;letter-spacing:1px}
|
||
.ph-head{
|
||
background:#003E7E;color:#fff;padding:10px 16px 14px;display:flex;align-items:center;
|
||
justify-content:space-between;
|
||
}
|
||
.ph-head .ph-title{font-size:15px;font-weight:800;font-family:var(--display)}
|
||
.ph-head .ph-sub{font-size:11px;color:rgba(255,255,255,.7);margin-top:2px}
|
||
.ph-head .ph-badge{font-size:11px;font-weight:600;background:rgba(255,255,255,.15);padding:4px 10px;border-radius:100px}
|
||
.ph-body{padding:12px;display:flex;flex-direction:column;gap:10px;min-height:330px}
|
||
.scard{
|
||
background:#fff;border-radius:14px;padding:12px 14px;border:1px solid #E5E7EB;
|
||
box-shadow:0 1px 4px rgba(0,0,0,.05);
|
||
}
|
||
.scard .sc-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}
|
||
.scard .sc-name{font-size:13px;font-weight:700;color:#1A1A2E}
|
||
.scard .sc-sub{font-size:11px;color:#8A94A6;margin-bottom:6px}
|
||
.scard .sc-money{font-size:13px;font-weight:700;color:#003E7E;font-variant-numeric:tabular-nums}
|
||
.scard .sc-warn{font-size:11px;color:#D97706;font-weight:600;margin-top:4px;display:flex;align-items:center;gap:4px}
|
||
.sbar{height:7px;background:#E5E7EB;border-radius:100px;overflow:hidden;margin-top:8px}
|
||
.sbar i{display:block;height:100%;background:#76BD22;border-radius:100px}
|
||
.sbar.warnbar i{background:#003E7E}
|
||
.sbadge{font-size:9px;font-weight:700;padding:2px 8px;border-radius:100px}
|
||
.sbadge.done{background:#DCFCE7;color:#15803D}
|
||
.sbadge.wait{background:#FEF3C7;color:#D97706}
|
||
.sbadge.act{background:#DBEAFE;color:#1D4ED8}
|
||
|
||
/* schedule grid (chess) */
|
||
.chess{width:100%;border-collapse:collapse;font-size:11px;background:#fff;border-radius:12px;overflow:hidden}
|
||
.chess th,.chess td{border:1px solid #E5E7EB;padding:7px 8px;text-align:left;color:#1A1A2E}
|
||
.chess th{background:#003E7E;color:#fff;font-size:10px;font-weight:700}
|
||
.chess td.time{font-family:var(--mono);color:#8A94A6;font-weight:600;background:#F9FAFB}
|
||
.cell-done{color:#15803D;font-weight:600}
|
||
.cell-act{color:#003E7E;font-weight:700}
|
||
.cell-free{color:#9CA3AF}
|
||
|
||
/* day schedule */
|
||
.daysched{display:flex;flex-direction:column;gap:2px;font-size:12px}
|
||
.ds-row{display:flex;gap:10px;align-items:flex-start;padding:6px 0}
|
||
.ds-time{font-family:var(--mono);font-size:11px;color:#003E7E;font-weight:700;width:46px;flex:0 0 auto}
|
||
.ds-block{background:#fff;border:1px solid #E5E7EB;border-left:3px solid #003E7E;border-radius:8px;padding:7px 10px;flex:1}
|
||
.ds-block.green{border-left-color:#76BD22}
|
||
.ds-block b{font-size:12px;font-weight:700;color:#1A1A2E;display:block}
|
||
.ds-block span{font-size:10px;color:#8A94A6}
|
||
.ds-marker{font-size:11px;color:#8A94A6;font-weight:600;padding:5px 0}
|
||
.ds-btn{margin-top:8px;background:#003E7E;color:#fff;text-align:center;padding:9px;border-radius:10px;font-size:12px;font-weight:700}
|
||
|
||
.scaption{text-align:center;color:var(--muted);font-size:17px;margin-top:24px;font-family:var(--mono);letter-spacing:.02em}
|
||
|
||
/* security 2-col */
|
||
.seccols{display:grid;grid-template-columns:0.95fr 1.05fr;gap:34px;height:100%;align-items:stretch}
|
||
.sec-arch{display:flex;flex-direction:column}
|
||
.sec-arch .codeblock{flex:1;font-size:15px;line-height:1.7}
|
||
.secgrid{display:grid;grid-template-columns:1fr 1fr;gap:16px;align-content:start}
|
||
.seccard{
|
||
background:var(--card);border:1px solid var(--border);border-radius:14px;padding:18px 18px;
|
||
}
|
||
.seccard .si{display:block;margin-bottom:8px}
|
||
.seccard h4{font-size:16px;font-weight:700;margin-bottom:5px}
|
||
.seccard p{color:var(--muted);font-size:12.5px;line-height:1.5}
|
||
.seccard.b1{border-color:rgba(118,189,34,.35)}
|
||
.seccard.b2{border-color:rgba(0,62,126,.4)}
|
||
|
||
/* ===== NAV ===== */
|
||
.nav{position:fixed;bottom:26px;left:50%;transform:translateX(-50%);display:flex;align-items:center;gap:18px;z-index:50}
|
||
.nav button{
|
||
width:46px;height:46px;border-radius:12px;border:1px solid var(--border);background:rgba(26,26,46,.85);
|
||
color:var(--text);font-size:20px;cursor:pointer;transition:.2s;backdrop-filter:blur(8px);
|
||
}
|
||
.nav button:hover{border-color:var(--accent2);color:var(--accent2)}
|
||
.nav .counter{font-family:var(--mono);font-size:15px;color:var(--muted);min-width:64px;text-align:center}
|
||
.nav .counter b{color:var(--text)}
|
||
.progress{position:fixed;top:0;left:0;height:3px;background:linear-gradient(90deg,var(--accent),var(--success));z-index:60;transition:width .5s ease;box-shadow:0 0 10px var(--accent)}
|
||
.brandmark{position:fixed;bottom:30px;left:40px;font-family:var(--mono);font-size:13px;color:var(--muted);z-index:50;letter-spacing:.1em}
|
||
.pagenum{position:fixed;bottom:30px;right:40px;font-family:var(--mono);font-size:13px;color:var(--muted);z-index:50}
|
||
|
||
.twocol{display:grid;grid-template-columns:1fr 1fr;gap:48px;align-items:center;height:100%}
|
||
.footnote{margin-top:auto;padding-top:22px;color:var(--muted);font-size:15px;font-family:var(--mono)}
|
||
|
||
@media(max-width:1100px){
|
||
.slide{padding:40px 44px 90px}
|
||
h1{font-size:48px}.title-slide h1{font-size:56px}h2{font-size:34px}
|
||
.g4{grid-template-columns:repeat(2,1fr)}.g3{grid-template-columns:repeat(2,1fr)}
|
||
.checklist{grid-template-columns:1fr}.timeline{flex-direction:column;gap:14px}
|
||
.phase::before{display:none}.twocol{grid-template-columns:1fr;gap:24px}
|
||
.seccols{grid-template-columns:1fr;gap:20px}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="progress" id="progress"></div>
|
||
<div class="deck" id="deck">
|
||
|
||
<!-- 1 TITLE -->
|
||
<section class="slide title-slide active">
|
||
<div class="slide-brand"><span style="display:inline-flex;flex-direction:column;align-items:flex-end;line-height:1;gap:2px"><span style="font-family:'Montserrat',sans-serif;font-size:10px;font-weight:500;letter-spacing:.04em;color:rgba(255,255,255,.45)">@wasrusgen1</span><span style="font-family:'Montserrat',sans-serif;font-size:22px;font-weight:900;letter-spacing:-1px;color:#fff;line-height:1">CRM</span></span></div>
|
||
<div class="stage" style="display:flex;flex-direction:column;justify-content:center">
|
||
<div class="brandtag"><span class="dot"></span> @wasrusgen1 CRM · PLATFORM ARCHITECTURE</div>
|
||
<h1>Архитектура платформы<br><span style="color:var(--accent2)">@wasrusgen1 CRM</span></h1>
|
||
<p class="lede">Один продукт — много клиентов. Полный контроль.</p>
|
||
<div class="title-meta">
|
||
<div><span>Документ для</span><strong>Собственника системы</strong></div>
|
||
<div><span>Тема</span><strong>Ввод проекта в работу</strong></div>
|
||
<div><span>Дата</span><strong>2026</strong></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 2 SYSTEM MAP -->
|
||
<section class="slide">
|
||
<div class="slide-brand"><span style="display:inline-flex;flex-direction:column;align-items:flex-end;line-height:1;gap:2px"><span style="font-family:'Montserrat',sans-serif;font-size:10px;font-weight:500;letter-spacing:.04em;color:rgba(255,255,255,.45)">@wasrusgen1</span><span style="font-family:'Montserrat',sans-serif;font-size:22px;font-weight:900;letter-spacing:-1px;color:#fff;line-height:1">CRM</span></span></div>
|
||
<div class="stage">
|
||
<div class="kicker">Слайд 02 · Big picture</div>
|
||
<h2>Карта системы</h2>
|
||
<p class="sub">Единый код в GitHub разворачивается на VPS. Каждый клиент — отдельный поддомен и отдельная база.</p>
|
||
<div class="slide-body" style="justify-content:center">
|
||
<div class="archmap">
|
||
<div class="node master"><strong>Руслан · Мастер-панель</strong><span>управление всеми проектами</span></div>
|
||
<div class="arrow">↓</div>
|
||
<div class="node git"><strong>GitHub</strong><span>единый исходный код</span></div>
|
||
<div class="arrow">↓ <small>авто-деплой CI/CD</small></div>
|
||
<div class="node vps"><strong>VPS сервер · Nginx + Docker</strong><span>роутинг по поддоменам</span></div>
|
||
<div class="arrow">↓</div>
|
||
<div class="tenants">
|
||
<div class="tenant"><b>salon1.crm.ru</b><span>→ PostgreSQL DB_1</span></div>
|
||
<div class="tenant"><b>salon2.crm.ru</b><span>→ PostgreSQL DB_2</span></div>
|
||
<div class="tenant"><b>salonN.crm.ru</b><span>→ PostgreSQL DB_N</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 3 THREE MODELS -->
|
||
<section class="slide">
|
||
<div class="slide-brand"><span style="display:inline-flex;flex-direction:column;align-items:flex-end;line-height:1;gap:2px"><span style="font-family:'Montserrat',sans-serif;font-size:10px;font-weight:500;letter-spacing:.04em;color:rgba(255,255,255,.45)">@wasrusgen1</span><span style="font-family:'Montserrat',sans-serif;font-size:22px;font-weight:900;letter-spacing:-1px;color:#fff;line-height:1">CRM</span></span></div>
|
||
<div class="stage">
|
||
<div class="kicker">Слайд 03 · Стратегия</div>
|
||
<h2>Три модели управления</h2>
|
||
<p class="sub">Как обслуживать множество клиентов на одной кодовой базе.</p>
|
||
<div class="slide-body">
|
||
<div class="grid g3" style="flex:1;align-content:center">
|
||
<div class="card">
|
||
<div class="ic">A</div>
|
||
<h3>Мультитенант</h3>
|
||
<p>Один деплой, одна БД, разделение по <span class="mono" style="color:var(--accent2)">tenant_id</span>. Дёшево в масштабе, но слабая изоляция и риск общего сбоя.</p>
|
||
<div style="margin-top:14px"><span class="pill">сложно стартовать</span></div>
|
||
</div>
|
||
<div class="card success">
|
||
<div class="ic">B</div>
|
||
<h3>Мультидеплой</h3>
|
||
<p>Отдельный контейнер + отдельная БД на клиента. Полная изоляция, простой старт, легко считать расходы по клиенту.</p>
|
||
<div style="margin-top:14px"><span class="pill rec">★ старт здесь</span></div>
|
||
</div>
|
||
<div class="card accent">
|
||
<div class="ic">C</div>
|
||
<h3>Гибрид</h3>
|
||
<p>Общий код и инфра-слой, изолированные БД, общий мониторинг и единое обновление. Баланс цены и изоляции на росте.</p>
|
||
<div style="margin-top:14px"><span class="pill rec">★ цель</span></div>
|
||
</div>
|
||
</div>
|
||
<div class="footnote">Рекомендация: начать с модели <b style="color:var(--success)">B (мультидеплой)</b> → перейти на <b style="color:var(--accent2)">C (гибрид)</b> при 5+ клиентах.</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 4 LIFECYCLE -->
|
||
<section class="slide">
|
||
<div class="slide-brand"><span style="display:inline-flex;flex-direction:column;align-items:flex-end;line-height:1;gap:2px"><span style="font-family:'Montserrat',sans-serif;font-size:10px;font-weight:500;letter-spacing:.04em;color:rgba(255,255,255,.45)">@wasrusgen1</span><span style="font-family:'Montserrat',sans-serif;font-size:22px;font-weight:900;letter-spacing:-1px;color:#fff;line-height:1">CRM</span></span></div>
|
||
<div class="stage">
|
||
<div class="kicker">Слайд 04 · Процесс</div>
|
||
<h2>Жизненный цикл нового проекта</h2>
|
||
<p class="sub">От подписи договора до запуска — около 3 рабочих дней.</p>
|
||
<div class="slide-body">
|
||
<div class="steps" style="margin:auto 0">
|
||
<div class="step"><div class="no">01</div><div class="txt"><b>Договор с клиентом</b><span>согласование условий, тариф, доступы</span></div><div class="time">1 день</div></div>
|
||
<div class="step"><div class="no">02</div><div class="txt"><b>Настройка инстанса</b><span>создание БД, конфиг, поддомен, деплой</span></div><div class="time">2–4 ч</div></div>
|
||
<div class="step"><div class="no">03</div><div class="txt"><b>Загрузка данных</b><span>салоны, пользователи, справочники</span></div><div class="time">1–2 ч</div></div>
|
||
<div class="step"><div class="no">04</div><div class="txt"><b>Обучение команды</b><span>КД, администраторы, мастера</span></div><div class="time">1–2 дня</div></div>
|
||
<div class="step"><div class="no">05</div><div class="txt"><b>Старт → техподдержка</b><span>пилот, мониторинг, SLA</span></div><div class="time">запуск</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 5 STACK -->
|
||
<section class="slide">
|
||
<div class="slide-brand"><span style="display:inline-flex;flex-direction:column;align-items:flex-end;line-height:1;gap:2px"><span style="font-family:'Montserrat',sans-serif;font-size:10px;font-weight:500;letter-spacing:.04em;color:rgba(255,255,255,.45)">@wasrusgen1</span><span style="font-family:'Montserrat',sans-serif;font-size:22px;font-weight:900;letter-spacing:-1px;color:#fff;line-height:1">CRM</span></span></div>
|
||
<div class="stage">
|
||
<div class="kicker">Слайд 05 · Технологии</div>
|
||
<h2>Стек технологий</h2>
|
||
<p class="sub">Проверенные инструменты, без экзотики — быстрый найм и поддержка.</p>
|
||
<div class="slide-body">
|
||
<div class="grid g4" style="flex:1;align-content:center">
|
||
<div class="card"><div class="ic"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg></div><h3>Frontend</h3><p>HTML/JS (текущий прототип) → <b style="color:var(--accent2)">React</b> в v2</p></div>
|
||
<div class="card"><div class="ic"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg></div><h3>Backend</h3><p>Node.js + Express — REST API, авторизация</p></div>
|
||
<div class="card success"><div class="ic"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg></div><h3>База данных</h3><p>PostgreSQL — <b style="color:var(--success)">одна БД на клиента</b></p></div>
|
||
<div class="card"><div class="ic"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><polygon points="13,2 3,14 12,14 11,22 21,10 12,10 13,2"/></svg></div><h3>Кеш / сессии</h3><p>Redis — сессии, очереди, кеш</p></div>
|
||
<div class="card"><div class="ic"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z"/><polyline points="3.27,6.96 12,12.01 20.73,6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg></div><h3>Файлы</h3><p>S3 / MinIO — фото, документы, экспорты</p></div>
|
||
<div class="card accent"><div class="ic"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><polygon points="13,2 3,14 12,14 11,22 21,10 12,10 13,2"/></svg></div><h3>Деплой</h3><p>Docker Compose + GitHub Actions CI/CD</p></div>
|
||
<div class="card"><div class="ic"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M18 10h-1.26A8 8 0 109 20h9a5 5 0 000-10z"/></svg></div><h3>Хостинг</h3><p>VPS: Hetzner / Timeweb / Selectel</p></div>
|
||
<div class="card"><div class="ic"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0110 0v4"/></svg></div><h3>Сеть</h3><p>Nginx reverse-proxy + Let's Encrypt HTTPS</p></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 6 ER DIAGRAM -->
|
||
<section class="slide er">
|
||
<div class="slide-brand"><span style="display:inline-flex;flex-direction:column;align-items:flex-end;line-height:1;gap:2px"><span style="font-family:'Montserrat',sans-serif;font-size:10px;font-weight:500;letter-spacing:.04em;color:rgba(255,255,255,.45)">@wasrusgen1</span><span style="font-family:'Montserrat',sans-serif;font-size:22px;font-weight:900;letter-spacing:-1px;color:#fff;line-height:1">CRM</span></span></div>
|
||
<div class="stage">
|
||
<div class="kicker">Слайд 06 · Данные</div>
|
||
<h2>Схема базы данных</h2>
|
||
<p class="sub">8 ключевых сущностей. Каждый клиент получает изолированную копию этой схемы.</p>
|
||
<div class="slide-body" style="justify-content:center">
|
||
<div class="codeblock">
|
||
<div class="winbar"><i style="background:#ff5f57"></i><i style="background:#febc2e"></i><i style="background:#28c840"></i></div>
|
||
<pre><span class="c-mut"># SCHEMA.md — ER-диаграмма (одна БД на клиента)</span>
|
||
|
||
<span class="c-acc">┌────────────┐</span <span class="c-acc">┌────────────┐</span <span class="c-acc">┌────────────┐</span
|
||
<span class="c-acc">│ salons │</span<span class="c-key">──1:N──</span<span class="c-acc">│ users │</span<span class="c-key">──1:N──</span<span class="c-acc">│shiftRequest│</span
|
||
<span class="c-acc">└─────┬──────┘</span <span class="c-acc">└─────┬──────┘</span <span class="c-acc">└────────────┘</span
|
||
<span class="c-key">│ 1:N</span <span class="c-key">│ 1:N</span
|
||
<span class="c-acc">┌─────┴──────┐</span <span class="c-acc">┌─────┴──────┐</span <span class="c-acc">┌────────────┐</span
|
||
<span class="c-acc">│ clients │</span<span class="c-key">──1:N──</span<span class="c-acc">│ orders │</span<span class="c-key">──1:N──</span<span class="c-acc">│ ratings │</span
|
||
<span class="c-acc">└─────┬──────┘</span <span class="c-acc">└─────┬──────┘</span <span class="c-acc">└────────────┘</span
|
||
<span class="c-key">│ 1:N</span <span class="c-key">│ 1:N</span
|
||
<span class="c-acc">┌─────┴──────┐</span <span class="c-acc">┌─────┴──────┐</span
|
||
<span class="c-acc">│appointments│</span<span class="c-key">───────│</span <span class="c-acc">│ requests │</span
|
||
<span class="c-acc">└────────────┘</span <span class="c-acc">└────────────┘</span
|
||
|
||
<span class="c-ok">salons</span> → сеть салонов клиента
|
||
<span class="c-ok">users</span> → сотрудники (КД / админ / мастер)
|
||
<span class="c-ok">clients</span> → клиентская база салона
|
||
<span class="c-ok">orders</span> → сделки / заказы
|
||
<span class="c-ok">appointments</span>→ записи на услуги
|
||
<span class="c-ok">requests</span> → заявки / лиды
|
||
<span class="c-ok">shiftRequests</span>→ заявки на смены сотрудников
|
||
<span class="c-ok">ratings</span> → оценки и обратная связь</pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 7 MASTER PANEL -->
|
||
<section class="slide">
|
||
<div class="slide-brand"><span style="display:inline-flex;flex-direction:column;align-items:flex-end;line-height:1;gap:2px"><span style="font-family:'Montserrat',sans-serif;font-size:10px;font-weight:500;letter-spacing:.04em;color:rgba(255,255,255,.45)">@wasrusgen1</span><span style="font-family:'Montserrat',sans-serif;font-size:22px;font-weight:900;letter-spacing:-1px;color:#fff;line-height:1">CRM</span></span></div>
|
||
<div class="stage">
|
||
<div class="kicker">Слайд 07 · Инструмент владельца</div>
|
||
<h2>Мастер-панель Руслана</h2>
|
||
<p class="sub">Что нужно разработать для управления всем парком проектов из одного места.</p>
|
||
<div class="slide-body">
|
||
<div class="grid g2" style="flex:1;align-content:center">
|
||
<div class="card accent"><div class="ic"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2"/><rect x="9" y="3" width="6" height="4" rx="2"/></svg></div><h3>Список проектов</h3><p>Все клиентские инстансы: статус, last activity, число пользователей, тариф.</p></div>
|
||
<div class="card success"><div class="ic"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></div><h3>Создать новый проект</h3><p>Кнопка «Новый проект» → деплой инстанса в 1 клик (или скриптом).</p></div>
|
||
<div class="card"><div class="ic"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><polyline points="22,12 18,12 15,21 9,3 6,12 2,12"/></svg></div><h3>Мониторинг</h3><p>Uptime, ошибки, нагрузка CPU/RAM, статус БД по каждому клиенту.</p></div>
|
||
<div class="card"><div class="ic"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><polyline points="23,4 23,10 17,10"/><polyline points="1,20 1,14 7,14"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/></svg></div><h3>Массовое обновление</h3><p>Выкатить новую версию на все проекты одновременно одной командой.</p></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 8 [NEW] WHAT EACH ROLE SEES -->
|
||
<section class="slide">
|
||
<div class="slide-brand"><span style="display:inline-flex;flex-direction:column;align-items:flex-end;line-height:1;gap:2px"><span style="font-family:'Montserrat',sans-serif;font-size:10px;font-weight:500;letter-spacing:.04em;color:rgba(255,255,255,.45)">@wasrusgen1</span><span style="font-family:'Montserrat',sans-serif;font-size:22px;font-weight:900;letter-spacing:-1px;color:#fff;line-height:1">CRM</span></span></div>
|
||
<div class="stage">
|
||
<div class="kicker">Слайд 08 · Интерфейсы по ролям</div>
|
||
<h2>Что видит каждая роль</h2>
|
||
<p class="sub">Один продукт — разные кабинеты. У каждой роли свой экран и свой набор данных.</p>
|
||
<div class="slide-body" style="justify-content:center">
|
||
<div class="screens">
|
||
|
||
<!-- Screen 1: КД дашборд -->
|
||
<div class="phone">
|
||
<div class="phscreen">
|
||
<div class="statusbar"><span>9:41</span><span class="sb-r">⣾ ▌</span></div>
|
||
<div class="ph-head">
|
||
<div><div class="ph-title">📊 КД Дашборд</div><div class="ph-sub">вся сеть · реальное время</div></div>
|
||
<div class="ph-badge">май 2026</div>
|
||
</div>
|
||
<div class="ph-body">
|
||
<div class="scard">
|
||
<div class="sc-top"><span class="sc-name">🏪 Салон Ленина</span><span class="sbadge done">27/30</span></div>
|
||
<div class="sc-sub">заказов выполнено</div>
|
||
<div class="sc-money">₽ 1 537 000 / 1 700 000</div>
|
||
<div class="sc-warn">⚠ 1 просрочен</div>
|
||
<div class="sbar"><i style="width:90%"></i></div>
|
||
</div>
|
||
<div class="scard">
|
||
<div class="sc-top"><span class="sc-name">🏪 Салон Победы</span><span class="sbadge done">20/22</span></div>
|
||
<div class="sc-sub">заказов выполнено</div>
|
||
<div class="sc-money">₽ 1 310 000 / 1 500 000</div>
|
||
<div class="sc-warn">⚠ 1 просрочен</div>
|
||
<div class="sbar"><i style="width:87%"></i></div>
|
||
</div>
|
||
<div class="scard">
|
||
<div class="sc-top"><span class="sc-name">🏪 Салон Садовая</span><span class="sbadge act">15/19</span></div>
|
||
<div class="sc-sub">заказов выполнено</div>
|
||
<div class="sc-money">₽ 980 000 / 1 250 000</div>
|
||
<div class="sbar"><i style="width:78%"></i></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Screen 2: Администратор шахматка -->
|
||
<div class="phone">
|
||
<div class="phscreen">
|
||
<div class="statusbar"><span>9:41</span><span class="sb-r">⣾ ▌</span></div>
|
||
<div class="ph-head">
|
||
<div><div class="ph-title">Салон Ленина</div><div class="ph-sub">Шахматка · сегодня, 22 мая</div></div>
|
||
<div class="ph-badge">Админ</div>
|
||
</div>
|
||
<div class="ph-body">
|
||
<table class="chess">
|
||
<thead><tr><th>Время</th><th>Анна К.</th><th>Мария С.</th></tr></thead>
|
||
<tbody>
|
||
<tr><td class="time">10:00</td><td class="cell-done">✅ Орлова</td><td class="cell-free">—</td></tr>
|
||
<tr><td class="time">11:00</td><td class="cell-done">✅ Соколова</td><td class="cell-free">—</td></tr>
|
||
<tr><td class="time">12:00</td><td class="cell-free">○ Свободно</td><td class="cell-done">✅ Лебедев</td></tr>
|
||
<tr><td class="time">14:00</td><td class="cell-act">🔵 Ким Л.</td><td class="cell-act">🔵 Петрова</td></tr>
|
||
<tr><td class="time">15:00</td><td class="cell-act">🔵 Ким Л.</td><td class="cell-act">🔵 Петрова</td></tr>
|
||
<tr><td class="time">16:00</td><td class="cell-act">🔵 Захарова</td><td class="cell-free">—</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
<div class="scaption">КД — вся сеть в реальном времени · Администратор — управление командой</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 9 [NEW] MANAGER MOBILE -->
|
||
<section class="slide">
|
||
<div class="slide-brand"><span style="display:inline-flex;flex-direction:column;align-items:flex-end;line-height:1;gap:2px"><span style="font-family:'Montserrat',sans-serif;font-size:10px;font-weight:500;letter-spacing:.04em;color:rgba(255,255,255,.45)">@wasrusgen1</span><span style="font-family:'Montserrat',sans-serif;font-size:22px;font-weight:900;letter-spacing:-1px;color:#fff;line-height:1">CRM</span></span></div>
|
||
<div class="stage">
|
||
<div class="kicker">Слайд 09 · Мобильный кабинет</div>
|
||
<h2>Менеджер: работа из телефона</h2>
|
||
<p class="sub">Весь рабочий день — в кармане. Заказы, расписание, чек-ин на объекте.</p>
|
||
<div class="slide-body" style="justify-content:center">
|
||
<div class="screens">
|
||
|
||
<!-- Screen 1: orders -->
|
||
<div class="phone sm">
|
||
<div class="phscreen">
|
||
<div class="statusbar"><span>9:41</span><span class="sb-r">⣾ ▌</span></div>
|
||
<div class="ph-head">
|
||
<div><div class="ph-title">Мои заказы</div><div class="ph-sub">3 активных + 3 лида</div></div>
|
||
<div class="ph-badge">Менеджер</div>
|
||
</div>
|
||
<div class="ph-body">
|
||
<div class="scard">
|
||
<div class="sc-top"><span class="sc-name">МБ-2025-041 · Иванова А.С.</span></div>
|
||
<div class="sc-sub">Кухня · Ленина 34</div>
|
||
<div class="sc-warn">⚠ Техника — ждём 2 позиции</div>
|
||
<div class="sc-money" style="margin-top:6px">₽ 186 000 · аванс ✓</div>
|
||
</div>
|
||
<div class="scard">
|
||
<div class="sc-top"><span class="sc-name">МБ-2025-038 · Петров К.Н.</span><span class="sbadge done">готов</span></div>
|
||
<div class="sc-sub">Шкаф-купе · Победы 12</div>
|
||
<div class="sc-money" style="margin-top:6px">₽ 94 000 · оплачен ✓</div>
|
||
</div>
|
||
<div class="scard">
|
||
<div class="sc-top"><span class="sc-name">МБ-2025-044 · Сидоров</span><span class="sbadge wait">замер</span></div>
|
||
<div class="sc-sub">Гардероб · Садовая 5</div>
|
||
<div class="sc-money" style="margin-top:6px">оценка ₽ 120 000</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Screen 2: day schedule -->
|
||
<div class="phone sm">
|
||
<div class="phscreen">
|
||
<div class="statusbar"><span>9:41</span><span class="sb-r">⣾ ▌</span></div>
|
||
<div class="ph-head">
|
||
<div><div class="ph-title">Расписание дня</div><div class="ph-sub">22 мая · 4 встречи</div></div>
|
||
<div class="ph-badge">Сегодня</div>
|
||
</div>
|
||
<div class="ph-body">
|
||
<div class="daysched">
|
||
<div class="ds-marker">━● 10:00 — Начало смены</div>
|
||
<div class="ds-row"><span class="ds-time">12:00</span><div class="ds-block"><b>Ким Л. — Консультация</b><span>Кухня · 2 ч</span></div></div>
|
||
<div class="ds-row"><span class="ds-time">15:30</span><div class="ds-block green"><b>Захаров П. — Замер</b><span>Садовая 5 · GPS-чекин</span></div></div>
|
||
<div class="ds-row"><span class="ds-time">17:00</span><div class="ds-block"><b>Лебедева — Договор</b><span>офис · подпись</span></div></div>
|
||
<div class="ds-marker">━○ 19:00 — Конец смены</div>
|
||
<div class="ds-btn">+ Добавить встречу</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
<div class="scaption">Заказы · Расписание · GPS-чекин · База знаний</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 10 CHECKLIST -->
|
||
<section class="slide">
|
||
<div class="slide-brand"><span style="display:inline-flex;flex-direction:column;align-items:flex-end;line-height:1;gap:2px"><span style="font-family:'Montserrat',sans-serif;font-size:10px;font-weight:500;letter-spacing:.04em;color:rgba(255,255,255,.45)">@wasrusgen1</span><span style="font-family:'Montserrat',sans-serif;font-size:22px;font-weight:900;letter-spacing:-1px;color:#fff;line-height:1">CRM</span></span></div>
|
||
<div class="stage">
|
||
<div class="kicker">Слайд 10 · Onboarding</div>
|
||
<h2>Как подключить нового клиента</h2>
|
||
<p class="sub">Интерактивный чеклист — кликните по пунктам. <span style="color:var(--accent2)">Прогресс сохраняется в сессии.</span></p>
|
||
<div class="slide-body" style="justify-content:center">
|
||
<div class="checklist" id="checklist">
|
||
<div class="check"><div class="box"></div><div class="label">Подписан договор</div></div>
|
||
<div class="check"><div class="box"></div><div class="label">Получены данные: сеть, салоны, сотрудники</div></div>
|
||
<div class="check"><div class="box"></div><div class="label">Создан поддомен <span class="mono">client-name.wasrusgen1.ru</span></div></div>
|
||
<div class="check"><div class="box"></div><div class="label">Развёрнута БД и заполнены справочники</div></div>
|
||
<div class="check"><div class="box"></div><div class="label">Созданы учётные записи (КД, Администраторы)</div></div>
|
||
<div class="check"><div class="box"></div><div class="label">Проведено обучение команды</div></div>
|
||
<div class="check"><div class="box"></div><div class="label">Запущен пилот</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 11 [EXPANDED] DATABASE & SECURITY -->
|
||
<section class="slide">
|
||
<div class="slide-brand"><span style="display:inline-flex;flex-direction:column;align-items:flex-end;line-height:1;gap:2px"><span style="font-family:'Montserrat',sans-serif;font-size:10px;font-weight:500;letter-spacing:.04em;color:rgba(255,255,255,.45)">@wasrusgen1</span><span style="font-family:'Montserrat',sans-serif;font-size:22px;font-weight:900;letter-spacing:-1px;color:#fff;line-height:1">CRM</span></span></div>
|
||
<div class="stage">
|
||
<div class="kicker">Слайд 11 · Архитектура и защита</div>
|
||
<h2>База данных и безопасность</h2>
|
||
<p class="sub">Данные каждого клиента физически отделены — утечка одного не затрагивает других.</p>
|
||
<div class="slide-body">
|
||
<div class="seccols">
|
||
|
||
<!-- Section A: DB architecture -->
|
||
<div class="sec-arch">
|
||
<div class="tag" style="margin-bottom:12px">A · Архитектура базы данных</div>
|
||
<div class="codeblock">
|
||
<div class="winbar"><i style="background:#ff5f57"></i><i style="background:#febc2e"></i><i style="background:#28c840"></i></div>
|
||
<pre><span class="c-acc">PostgreSQL</span> <span class="c-mut">per-tenant</span>
|
||
<span class="c-ok">├── crm_client1/</span>
|
||
<span class="c-ok">│ ├── </span><span class="c-file">salons · users · clients</span>
|
||
<span class="c-ok">│ ├── </span><span class="c-file">orders · appointments</span>
|
||
<span class="c-ok">│ ├── </span><span class="c-file">requests · ratings</span>
|
||
<span class="c-ok">│ └── </span><span class="c-file">shift_requests</span>
|
||
<span class="c-ok">├── crm_client2/</span>
|
||
<span class="c-ok">└── crm_clientN/</span>
|
||
|
||
<span class="c-acc">Redis</span> <span class="c-mut"># сессии + KPI-кеш</span>
|
||
<span class="c-acc">MinIO/S3</span> <span class="c-mut"># документы, фото</span>
|
||
<span class="c-acc">Nginx</span> <span class="c-mut"># routing + TLS termination</span></pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section B: Security grid -->
|
||
<div>
|
||
<div class="tag" style="margin-bottom:12px">B · Безопасность</div>
|
||
<div class="secgrid">
|
||
<div class="seccard b1"><div style="display:flex;align-items:center;justify-content:center;width:44px;height:44px;background:rgba(0,62,126,.15);border-radius:10px;flex-shrink:0;margin-bottom:8px"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><polyline points="9,12 11,14 15,10"/></svg></div><h4>Физическая изоляция</h4><p>Каждый клиент — отдельная PostgreSQL БД. Код одного клиента физически не может достать данные другого.</p></div>
|
||
<div class="seccard b2"><div style="display:flex;align-items:center;justify-content:center;width:44px;height:44px;background:rgba(0,62,126,.15);border-radius:10px;flex-shrink:0;margin-bottom:8px"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 11-7.778 7.778 5.5 5.5 0 017.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"/></svg></div><h4>Авторизация JWT</h4><p>Access (15 мин) + Refresh (30 дней). Роль и salonId в payload. Проверка на каждом запросе.</p></div>
|
||
<div class="seccard b2"><div style="display:flex;align-items:center;justify-content:center;width:44px;height:44px;background:rgba(0,62,126,.15);border-radius:10px;flex-shrink:0;margin-bottom:8px"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0110 0v4"/></svg></div><h4>HTTPS / TLS</h4><p>Let's Encrypt, автообновление. HTTP → 301 redirect. HSTS включён.</p></div>
|
||
<div class="seccard b1"><div style="display:flex;align-items:center;justify-content:center;width:44px;height:44px;background:rgba(0,62,126,.15);border-radius:10px;flex-shrink:0;margin-bottom:8px"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><polyline points="1,4 1,10 7,10"/><path d="M3.51 15a9 9 0 102.13-9.36L1 10"/></svg></div><h4>Резервные копии</h4><p>Ежедневный pg_dump в S3. Хранение 30 дней. RTO < 1 ч, RPO < 24 ч.</p></div>
|
||
<div class="seccard b2"><div style="display:flex;align-items:center;justify-content:center;width:44px;height:44px;background:rgba(0,62,126,.15);border-radius:10px;flex-shrink:0;margin-bottom:8px"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14,2 14,8 20,8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10,9 9,9 8,9"/></svg></div><h4>Аудит-лог</h4><p>Каждое изменение заказа/клиента — с userId + timestamp. Просмотр только admin/owner.</p></div>
|
||
<div class="seccard b1"><div style="display:flex;align-items:center;justify-content:center;width:44px;height:44px;background:rgba(0,62,126,.15);border-radius:10px;flex-shrink:0;margin-bottom:8px"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg></div><h4>RBAC</h4><p>5 ролей (owner, admin, manager, measurer, assembler). Матрица прав в SCHEMA.md.</p></div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 12 INFRA COST -->
|
||
<section class="slide">
|
||
<div class="slide-brand"><span style="display:inline-flex;flex-direction:column;align-items:flex-end;line-height:1;gap:2px"><span style="font-family:'Montserrat',sans-serif;font-size:10px;font-weight:500;letter-spacing:.04em;color:rgba(255,255,255,.45)">@wasrusgen1</span><span style="font-family:'Montserrat',sans-serif;font-size:22px;font-weight:900;letter-spacing:-1px;color:#fff;line-height:1">CRM</span></span></div>
|
||
<div class="stage">
|
||
<div class="kicker">Слайд 12 · Инфраструктура</div>
|
||
<h2>Инфраструктура и стоимость</h2>
|
||
<p class="sub">Расходы растут вместе с числом клиентов — на старте почти нулевые.</p>
|
||
<div class="slide-body" style="justify-content:center">
|
||
<div class="card" style="padding:8px 16px">
|
||
<table>
|
||
<thead><tr><th>Этап</th><th>Конфигурация</th><th style="text-align:right">$/мес</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><b>Прототип</b> <span style="color:var(--muted)">(сейчас)</span></td><td class="mono">GitHub Pages</td><td class="num ok">$0</td></tr>
|
||
<tr><td><b>Пилот</b> <span style="color:var(--muted)">(1–3 клиента)</span></td><td class="mono">VPS 2CPU / 4GB + Postgres</td><td class="num">~$25–35</td></tr>
|
||
<tr><td><b>Рост</b> <span style="color:var(--muted)">(4–10 клиентов)</span></td><td class="mono">VPS 4CPU / 8GB + backup</td><td class="num">~$50–80</td></tr>
|
||
<tr><td><b>Масштаб</b> <span style="color:var(--muted)">(10+ клиентов)</span></td><td class="mono">2 VPS + балансировщик</td><td class="num">~$120–200</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="footnote">При 8 000 ₽/мес с клиента стоимость инфраструктуры — менее <b style="color:var(--success)">3 %</b> от выручки даже на масштабе.</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 13 ECONOMICS -->
|
||
<section class="slide">
|
||
<div class="slide-brand"><span style="display:inline-flex;flex-direction:column;align-items:flex-end;line-height:1;gap:2px"><span style="font-family:'Montserrat',sans-serif;font-size:10px;font-weight:500;letter-spacing:.04em;color:rgba(255,255,255,.45)">@wasrusgen1</span><span style="font-family:'Montserrat',sans-serif;font-size:22px;font-weight:900;letter-spacing:-1px;color:#fff;line-height:1">CRM</span></span></div>
|
||
<div class="stage">
|
||
<div class="kicker">Слайд 13 · Экономика</div>
|
||
<h2>Экономика продукта · P&L</h2>
|
||
<p class="sub">Точка безубыточности — первый же клиент. Маржа растёт с каждым следующим.</p>
|
||
<div class="slide-body" style="justify-content:center">
|
||
<div class="pl">
|
||
<div class="block cost">
|
||
<div class="tag" style="color:var(--warn)">— РАСХОДЫ / мес</div>
|
||
<div class="row"><span>Сервер (VPS + Postgres)</span><b>~$35</b></div>
|
||
<div class="row"><span>Поддержка (N часов)</span><b>переменные</b></div>
|
||
<div class="row"><span>Прочее (домены, бэкап)</span><b>~$5</b></div>
|
||
<div class="row" style="color:var(--warn)"><span><b>Итого база</b></span><b>~$40</b></div>
|
||
</div>
|
||
<div class="block rev">
|
||
<div class="tag" style="color:var(--success)">+ ДОХОДЫ / мес</div>
|
||
<div class="row"><span>Тариф на клиента</span><b>8 000 ₽</b></div>
|
||
<div class="row"><span>5 клиентов</span><b>40 000 ₽</b></div>
|
||
<div class="row"><span>10 клиентов</span><b>80 000 ₽</b></div>
|
||
<div class="row" style="color:var(--success)"><span><b>Модель X × 8 000 ₽</b></span><b>растёт линейно</b></div>
|
||
</div>
|
||
</div>
|
||
<div class="be">
|
||
<div class="stat"><span>Break-even</span><strong>1 клиент</strong></div>
|
||
<div class="stat"><span>Целевая маржа</span><strong>85 %+</strong></div>
|
||
<div class="stat"><span>При</span><strong>5+ клиентах</strong></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 14 ROADMAP -->
|
||
<section class="slide">
|
||
<div class="slide-brand"><span style="display:inline-flex;flex-direction:column;align-items:flex-end;line-height:1;gap:2px"><span style="font-family:'Montserrat',sans-serif;font-size:10px;font-weight:500;letter-spacing:.04em;color:rgba(255,255,255,.45)">@wasrusgen1</span><span style="font-family:'Montserrat',sans-serif;font-size:22px;font-weight:900;letter-spacing:-1px;color:#fff;line-height:1">CRM</span></span></div>
|
||
<div class="stage">
|
||
<div class="kicker">Слайд 14 · Roadmap</div>
|
||
<h2>Roadmap продукта</h2>
|
||
<p class="sub">Четыре фазы — от прототипа до AI-функций.</p>
|
||
<div class="slide-body" style="justify-content:center">
|
||
<div class="timeline">
|
||
<div class="phase now">
|
||
<div class="ring"></div>
|
||
<div class="phase-card"><div class="ph-tag">Фаза 1 · сейчас</div><h3>Прототип</h3><p>Готовый интерфейс → первый пилотный клиент.</p></div>
|
||
</div>
|
||
<div class="phase">
|
||
<div class="ring"></div>
|
||
<div class="phase-card"><div class="ph-tag">Фаза 2</div><h3>Бэкенд</h3><p>Node.js + PostgreSQL + авторизация (JWT).</p></div>
|
||
</div>
|
||
<div class="phase">
|
||
<div class="ring"></div>
|
||
<div class="phase-card"><div class="ph-tag">Фаза 3</div><h3>Мобильное (PWA)</h3><p>Установка на телефон, офлайн, push-уведомления.</p></div>
|
||
</div>
|
||
<div class="phase">
|
||
<div class="ring"></div>
|
||
<div class="phase-card"><div class="ph-tag">Фаза 4</div><h3>AI-функции</h3><p>Прогноз сделок, автоматизация рутины, аналитика.</p></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 15 SLA -->
|
||
<section class="slide">
|
||
<div class="slide-brand"><span style="display:inline-flex;flex-direction:column;align-items:flex-end;line-height:1;gap:2px"><span style="font-family:'Montserrat',sans-serif;font-size:10px;font-weight:500;letter-spacing:.04em;color:rgba(255,255,255,.45)">@wasrusgen1</span><span style="font-family:'Montserrat',sans-serif;font-size:22px;font-weight:900;letter-spacing:-1px;color:#fff;line-height:1">CRM</span></span></div>
|
||
<div class="stage">
|
||
<div class="kicker">Слайд 15 · Обязательства</div>
|
||
<h2>SLA и поддержка</h2>
|
||
<p class="sub">Понятные обязательства перед клиентом — основа доверия и удержания.</p>
|
||
<div class="slide-body">
|
||
<div class="grid g4" style="flex:1;align-content:center">
|
||
<div class="card success"><div class="ic"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12,6 12,12 16,14"/></svg></div><h3>Uptime</h3><p style="font-size:34px;font-weight:800;color:var(--success);font-family:var(--mono)">99.5 %</p></div>
|
||
<div class="card"><div class="ic"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07A19.5 19.5 0 013.07 9.8a19.79 19.79 0 01-3.07-8.64A2 2 0 012 .18h3a2 2 0 012 1.72 12.84 12.84 0 00.7 2.81 2 2 0 01-.45 2.11L6.11 7.11a16 16 0 006.77 6.77l1.06-1.06a2 2 0 012.11-.45 12.84 12.84 0 002.81.7A2 2 0 0122 15h-.08z"/></svg></div><h3>Реакция</h3><p style="font-size:34px;font-weight:800;color:var(--accent2);font-family:var(--mono)">4 ч</p><p>в рабочее время</p></div>
|
||
<div class="card"><div class="ic"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><polyline points="17,1 21,5 17,9"/><path d="M3 11V9a4 4 0 014-4h14"/><polyline points="7,23 3,19 7,15"/><path d="M21 13v2a4 4 0 01-4 4H3"/></svg></div><h3>Обновления</h3><p style="font-size:34px;font-weight:800;color:var(--accent2);font-family:var(--mono)">2 нед</p><p>регулярный цикл</p></div>
|
||
<div class="card accent"><div class="ic"><svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><polygon points="13,2 3,14 12,14 11,22 21,10 12,10 13,2"/></svg></div><h3>Onboarding</h3><p style="font-size:34px;font-weight:800;color:var(--accent);font-family:var(--mono)">3 дня</p><p>до запуска</p></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 16 NEXT STEPS -->
|
||
<section class="slide">
|
||
<div class="slide-brand"><span style="display:inline-flex;flex-direction:column;align-items:flex-end;line-height:1;gap:2px"><span style="font-family:'Montserrat',sans-serif;font-size:10px;font-weight:500;letter-spacing:.04em;color:rgba(255,255,255,.45)">@wasrusgen1</span><span style="font-family:'Montserrat',sans-serif;font-size:22px;font-weight:900;letter-spacing:-1px;color:#fff;line-height:1">CRM</span></span></div>
|
||
<div class="stage">
|
||
<div class="kicker">Слайд 16 · План действий</div>
|
||
<h2>Следующие шаги</h2>
|
||
<p class="sub">Конкретный план на 4 недели — от выбора клиента до запуска пилота.</p>
|
||
<div class="slide-body">
|
||
<div class="steps" style="margin:auto 0">
|
||
<div class="step"><div class="no">W1</div><div class="txt"><b>Выбрать первого пилотного клиента</b><span>сеть салонов, готовая дать данные и команду</span></div><div class="time">Неделя 1</div></div>
|
||
<div class="step"><div class="no">W2</div><div class="txt"><b>Развернуть бэкенд на VPS</b><span>Node.js + PostgreSQL + Docker + CI/CD</span></div><div class="time">Неделя 2</div></div>
|
||
<div class="step"><div class="no">W3</div><div class="txt"><b>Перевести прототип на реальные данные</b><span>подключить API, загрузить салоны и пользователей</span></div><div class="time">Неделя 3</div></div>
|
||
<div class="step"><div class="no">W4</div><div class="txt"><b>Запуск пилота с командой клиента</b><span>обучение, мониторинг, сбор обратной связи</span></div><div class="time">Неделя 4</div></div>
|
||
</div>
|
||
<div class="footnote">Готов начать с недели 1 — нужен выбор пилотного клиента.</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
</div>
|
||
|
||
<div class="brandmark" style="display:flex;align-items:center;gap:0"><span style="font-family:'Montserrat',sans-serif;font-weight:700;font-size:12px;color:rgba(255,255,255,.6)">@wasrusgen1</span><span style="display:inline-block;width:1.5px;height:14px;background:rgba(255,255,255,.2);margin:0 9px;flex-shrink:0"></span><span style="font-family:'Montserrat',sans-serif;font-weight:900;font-size:12px;color:rgba(255,255,255,.9);letter-spacing:.5px">CRM</span></div>
|
||
<div class="pagenum" id="pagenum">01 / 16</div>
|
||
|
||
<nav class="nav">
|
||
<button id="prev" aria-label="Назад">‹</button>
|
||
<div class="counter"><b id="cur">1</b> / 16</div>
|
||
<button id="next" aria-label="Вперёд">›</button>
|
||
</nav>
|
||
|
||
<script>
|
||
(function(){
|
||
const slides=[...document.querySelectorAll('.slide')];
|
||
let i=0;const total=slides.length;
|
||
const progress=document.getElementById('progress');
|
||
const cur=document.getElementById('cur');
|
||
const pagenum=document.getElementById('pagenum');
|
||
function pad(n){return String(n).padStart(2,'0');}
|
||
function show(n){
|
||
i=Math.max(0,Math.min(total-1,n));
|
||
slides.forEach((s,k)=>s.classList.toggle('active',k===i));
|
||
progress.style.width=((i+1)/total*100)+'%';
|
||
cur.textContent=i+1;
|
||
pagenum.textContent=pad(i+1)+' / '+pad(total);
|
||
}
|
||
document.getElementById('next').onclick=()=>show(i+1);
|
||
document.getElementById('prev').onclick=()=>show(i-1);
|
||
document.addEventListener('keydown',e=>{
|
||
if(['ArrowRight','PageDown',' '].includes(e.key)){e.preventDefault();show(i+1);}
|
||
else if(['ArrowLeft','PageUp'].includes(e.key)){e.preventDefault();show(i-1);}
|
||
else if(e.key==='Home')show(0);
|
||
else if(e.key==='End')show(total-1);
|
||
});
|
||
// interactive checklist
|
||
document.querySelectorAll('#checklist .check').forEach(c=>{
|
||
c.addEventListener('click',()=>c.classList.toggle('done'));
|
||
});
|
||
show(0);
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|