Загрузка данных
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>КСИВА — цифровое удостоверение личности · Форма №1-КС</title>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Crect x='7' y='7' width='10' height='10' fill='%23a8342a' transform='rotate(45 12 12)'/%3E%3C/svg%3E">
<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=Oswald:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&family=Marck+Script&display=swap" rel="stylesheet">
<style>
:root{
--bg:#151310; --bg2:#1c1913;
--line:rgba(213,201,168,.16);
--paper:#e9e1ca; --paper2:#ded2b2;
--ink:#26262b; --ink-soft:#565560;
--wax:#a8342a; --wax2:#c05a4e;
--brass:#b08d43;
--phos:#8fd694;
--uv:#6a2fd0; --uv2:#b18cff;
--f-head:'Oswald',sans-serif;
--f-mono:'IBM Plex Mono',monospace;
--f-sign:'Marck Script',cursive;
--grain:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="140" height="140"><filter id="n"><feTurbulence type="fractalNoise" baseFrequency="0.85" numOctaves="2"/></filter><rect width="140" height="140" filter="url(%23n)" opacity="0.07"/></svg>');
--stampmask:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="160" height="160"><filter id="r"><feTurbulence type="fractalNoise" baseFrequency="0.45" numOctaves="3" seed="7"/></filter><rect width="160" height="160" filter="url(%23r)" opacity="0.92"/></svg>');
}
*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth}
body{
background:var(--bg); color:var(--paper);
font-family:var(--f-mono); overflow-x:hidden;
}
body::before{ /* зерно сукна */
content:''; position:fixed; inset:0; z-index:0; pointer-events:none;
background-image:var(--grain); background-size:140px;
}
body::after{ /* виньетка */
content:''; position:fixed; inset:0; z-index:0; pointer-events:none;
box-shadow:inset 0 0 22vmax rgba(0,0,0,.55);
}
::selection{background:var(--wax); color:var(--paper)}
::-webkit-scrollbar{width:9px}
::-webkit-scrollbar-thumb{background:#3a352a; border:2px solid var(--bg)}
::-webkit-scrollbar-track{background:var(--bg)}
/* ===== Шапка ===== */
header{
position:relative; z-index:2;
display:flex; align-items:center; justify-content:space-between; gap:20px;
padding:14px 28px; border-bottom:1px solid var(--line);
font-size:10px; letter-spacing:.22em; text-transform:uppercase;
}
.brand{display:flex; align-items:center; gap:12px; color:rgba(233,225,202,.85)}
.brand svg{flex:none}
header .mid{color:rgba(233,225,202,.45)}
header .right{display:flex; align-items:center; gap:10px; color:rgba(233,225,202,.55)}
.dot{width:6px;height:6px;border-radius:50%;background:var(--phos);animation:pulse 1.6s infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.25}}
@media(max-width:860px){header .mid{display:none}}
/* ===== Общая обёртка ===== */
main{position:relative; z-index:1; max-width:1240px; margin:0 auto; padding:0 28px}
/* ===== Заголовок ===== */
.hero{padding:64px 0 40px; position:relative}
.eyebrow{
font-size:11px; letter-spacing:.34em; text-transform:uppercase;
color:var(--brass); display:flex; align-items:center; gap:14px;
}
.eyebrow::after{content:''; flex:1; max-width:220px; height:1px; background:var(--line)}
.hero-row{display:flex; align-items:flex-end; justify-content:space-between; gap:32px; flex-wrap:wrap}
h1.giant{
font-family:var(--f-head); font-weight:700;
font-size:clamp(84px,15vw,196px); line-height:.92;
letter-spacing:.01em; text-transform:uppercase; margin-top:14px;
}
h1.giant .out{
color:transparent; -webkit-text-stroke:2.5px var(--paper);
}
.slogan{
margin-top:22px; font-size:13px; color:rgba(233,225,202,.6);
max-width:420px; line-height:1.75;
}
.slogan b{color:var(--paper); font-weight:500}
.hero-meta{
text-align:right; font-size:11px; line-height:2.1; letter-spacing:.14em;
color:rgba(233,225,202,.5); text-transform:uppercase; padding-bottom:10px;
}
.hero-meta b{color:var(--wax2); font-weight:600}
@media(max-width:860px){.hero-meta{text-align:left}}
/* ===== Сцена: карта + панель ===== */
.workbench{
display:grid; grid-template-columns:minmax(0,1fr) minmax(0,460px);
gap:56px; align-items:start; padding-bottom:90px;
}
@media(max-width:1000px){.workbench{grid-template-columns:1fr; gap:40px}}
.stage{
position:relative; z-index:31;
display:flex; justify-content:center; padding:120px 0 60px;
perspective:1500px; user-select:none; -webkit-user-select:none;
}
.card3d{
position:relative; width:min(344px,80vw); aspect-ratio:63/88;
transform-style:preserve-3d; cursor:grab; touch-action:none;
transition:box-shadow .5s;
}
.card3d:active{cursor:grabbing}
body.uv-on .card3d{box-shadow:0 0 0 1px rgba(177,140,255,.3), 0 0 90px rgba(106,47,208,.5)}
.strap{
position:absolute; top:-118px; left:calc(50% - 13px); width:26px; height:120px;
background:#33331f; border-left:1px solid rgba(233,225,202,.12); border-right:1px solid rgba(0,0,0,.4);
backface-visibility:hidden; transform:translateZ(-1px);
}
.clip{
position:absolute; top:-34px; left:calc(50% - 24px); width:48px; height:30px;
background:#9a9486; border:1px solid #4d493f; border-radius:3px;
backface-visibility:hidden; transform:translateZ(-.5px);
}
.clip::after{
content:''; position:absolute; left:7px; right:7px; top:10px; height:9px;
background:#2e2c26; border-radius:2px;
}
.card{
position:absolute; inset:0; transform-style:preserve-3d;
animation:cardIn 1s cubic-bezier(.2,.9,.25,1) both;
}
@keyframes cardIn{from{opacity:0; transform:translateY(26px)}to{opacity:1; transform:none}}
.face{
position:absolute; inset:0; backface-visibility:hidden; -webkit-backface-visibility:hidden;
background:var(--paper); color:var(--ink); overflow:hidden;
box-shadow:0 24px 60px rgba(0,0,0,.5);
}
.face::before{content:''; position:absolute; inset:0; background-image:var(--grain); pointer-events:none}
.face::after{ /* светотень бумаги */
content:''; position:absolute; inset:0; pointer-events:none;
background:radial-gradient(130% 90% at 28% 6%, rgba(255,255,255,.22), transparent 55%),
radial-gradient(110% 90% at 92% 100%, rgba(88,66,26,.2), transparent 62%);
}
.face.back{transform:rotateY(180deg)}
body.uv-on .face{box-shadow:0 24px 60px rgba(20,8,50,.7)}
/* --- лицевая сторона --- */
.face.front{padding:18px 18px 12px; display:flex; flex-direction:column}
.c-head{display:flex; justify-content:space-between; align-items:flex-start; gap:8px;
border-bottom:2px solid var(--ink); padding-bottom:10px}
.c-title{font-family:var(--f-head); font-weight:600; font-size:16.5px; letter-spacing:.06em}
.c-sub{font-size:8px; letter-spacing:.22em; color:var(--ink-soft); margin-top:3px}
.c-mid{display:flex; gap:14px; margin-top:14px; flex:1}
.c-photo{
position:relative; width:104px; height:132px; flex:none;
border:1px solid rgba(38,38,43,.45); background:#cfc6ab; overflow:hidden;
}
.c-photo img{width:100%; height:100%; object-fit:cover; filter:grayscale(1) contrast(1.12) brightness(.94) sepia(.18);
opacity:0; transition:opacity .6s}
.c-photo img.ld{opacity:1}
.photo-stamp{
position:absolute; left:-6px; bottom:8px; transform:rotate(-11deg);
font-family:var(--f-head); font-size:9px; letter-spacing:.14em; line-height:1.25;
color:var(--wax); border:1.5px solid var(--wax); padding:2px 5px; text-align:center;
}
.c-fields{flex:1; min-width:0}
.fio{display:flex; flex-direction:column; gap:1px; margin-bottom:10px}
.fio span:first-child{font-family:var(--f-head); font-weight:600; font-size:20px; letter-spacing:.04em; line-height:1.1}
.fio span:last-child{font-family:var(--f-head); font-weight:500; font-size:13.5px; letter-spacing:.08em}
.drow{display:flex; align-items:baseline; gap:6px; font-size:9px; padding:3.5px 0}
.drow dt{color:var(--ink-soft); letter-spacing:.1em; text-transform:uppercase; white-space:nowrap}
.drow .dots{flex:1; border-bottom:1px dotted rgba(38,38,43,.4); transform:translateY(-2px)}
.drow dd{font-weight:600; font-size:10.5px; letter-spacing:.04em; white-space:nowrap}
.c-bottom{display:flex; justify-content:space-between; align-items:flex-end; margin-top:6px}
.sign{font-family:var(--f-sign); font-size:26px; color:#2b2c46; transform:rotate(-3deg); line-height:1}
.sign-line{font-size:7.5px; letter-spacing:.2em; color:var(--ink-soft); text-transform:uppercase;
border-top:1px solid rgba(38,38,43,.5); padding-top:3px; margin-top:5px}
.holo{
position:relative; width:66px; height:66px; border-radius:50%; flex:none;
background:conic-gradient(from 0deg, rgba(255,0,132,.5), rgba(255,214,0,.45), rgba(0,255,170,.45), rgba(0,140,255,.5), rgba(160,0,255,.45), rgba(255,0,132,.5));
display:flex; align-items:center; justify-content:center;
animation:huespin 7s linear infinite;
transition:transform .2s;
}
.holo::after{
content:''; position:absolute; inset:0; border-radius:50%;
background:linear-gradient(115deg, transparent 30%, rgba(255,255,255,.75) 46%, rgba(255,255,255,.1) 54%, transparent 62%);
mix-blend-mode:screen;
}
.holo span{
position:relative; font-family:var(--f-head); font-weight:700; font-size:17px;
color:rgba(255,255,255,.85); text-shadow:0 1px 2px rgba(0,0,0,.35);
}
@keyframes huespin{to{filter:hue-rotate(360deg)}}
.c-strip{display:flex; justify-content:space-between; align-items:flex-end; margin-top:10px;
border-top:1px solid rgba(38,38,43,.35); padding-top:7px}
.micro{font-size:5.5px; letter-spacing:.14em; color:rgba(38,38,43,.55); overflow:hidden;
white-space:nowrap; max-width:55%; line-height:1.6}
.perf{
font-family:var(--f-mono); font-weight:600; font-size:13px; letter-spacing:.3em;
color:transparent; -webkit-text-fill-color:transparent;
background-image:radial-gradient(circle, rgba(38,38,43,.85) 1px, transparent 1.4px);
background-size:4px 4px;
-webkit-background-clip:text; background-clip:text;
}
.issue-stamp{
position:absolute; right:12px; top:46%; transform:rotate(8deg) scale(2.4); opacity:0;
z-index:3; text-align:center; padding:5px 12px;
font-family:var(--f-head); color:var(--wax); border:2.5px solid var(--wax);
}
.issue-stamp b{display:block; font-size:17px; letter-spacing:.24em; font-weight:600}
.issue-stamp i{display:block; font-style:normal; font-family:var(--f-mono); font-size:8.5px; letter-spacing:.14em; margin-top:2px}
.issue-stamp.on{animation:stampIn .4s cubic-bezier(.2,1.6,.35,1) forwards}
.stamp-ok{
position:absolute; left:50%; top:40%; z-index:4; opacity:0; pointer-events:none;
transform:translate(-50%,-50%) rotate(-13deg) scale(2.6); text-align:center;
color:var(--wax); border:3px double var(--wax); padding:8px 20px; width:76%;
}
.stamp-ok b{display:block; font-family:var(--f-head); font-size:26px; letter-spacing:.3em; font-weight:600; text-indent:.3em}
.stamp-ok i{display:block; font-style:normal; font-family:var(--f-mono); font-size:8px; letter-spacing:.18em; margin-top:3px}
.stamp-ok.on{animation:stampIn .42s cubic-bezier(.2,1.5,.3,1) forwards}
@keyframes stampIn{
0%{opacity:0; transform:translate(-50%,-50%) rotate(-4deg) scale(2.6)}
60%{opacity:1; transform:translate(-50%,-50%) rotate(-13deg) scale(.94)}
100%{opacity:.92; transform:translate(-50%,-50%) rotate(-13deg) scale(1)}
}
.issue-stamp.on{animation:stampInIssue .4s cubic-bezier(.2,1.6,.35,1) forwards}
@keyframes stampInIssue{
0%{opacity:0; transform:rotate(2deg) scale(2.4)}
60%{opacity:1; transform:rotate(8deg) scale(.95)}
100%{opacity:.85; transform:rotate(8deg) scale(1)}
}
.stamp-look{-webkit-mask-image:var(--stampmask); mask-image:var(--stampmask); -webkit-mask-size:190px; mask-size:190px}
/* --- оборотная сторона --- */
.face.back{padding:18px 18px 12px; display:flex; flex-direction:column}
.guilloche{
position:absolute; inset:0; opacity:.55; pointer-events:none;
background:
repeating-radial-gradient(circle at 18% 28%, rgba(38,38,43,.12) 0 1px, transparent 1px 9px),
repeating-radial-gradient(circle at 84% 66%, rgba(38,38,43,.1) 0 1px, transparent 1px 12px);
}
.b-head{font-family:var(--f-head); font-size:11px; letter-spacing:.24em; font-weight:600;
border-bottom:1px solid rgba(38,38,43,.5); padding-bottom:8px; position:relative; z-index:1}
.b-rows{position:relative; z-index:1; margin-top:12px}
.b-rows .drow{font-size:9px; padding:5px 0}
.b-rows dd{font-size:9.5px}
.b-bar{position:relative; z-index:1; margin-top:auto}
.barcode{position:relative; height:44px; margin-bottom:5px}
.barcode span{position:absolute; top:0; bottom:0; background:var(--ink)}
.bc-num{font-size:9px; letter-spacing:.34em; text-align:center; color:var(--ink)}
.b-note{position:relative; z-index:1; font-size:8px; color:var(--ink-soft); text-align:center;
margin:9px 6px; letter-spacing:.06em; line-height:1.6}
.mrz{
position:relative; z-index:1; background:rgba(38,38,43,.07); border:1px solid rgba(38,38,43,.3);
padding:7px 8px; display:flex; flex-direction:column; gap:2px;
}
.mrz span{font-size:10px; letter-spacing:1.5px; white-space:nowrap; color:#202128; font-weight:500}
/* --- UV-слой --- */
.uv{
position:absolute; inset:0; z-index:5; pointer-events:none; opacity:0; transition:opacity .45s;
-webkit-mask-image:radial-gradient(circle 135px at var(--ux,50%) var(--uy,50%), #000 0 34%, rgba(0,0,0,.85) 55%, transparent 78%);
mask-image:radial-gradient(circle 135px at var(--ux,50%) var(--uy,50%), #000 0 34%, rgba(0,0,0,.85) 55%, transparent 78%);
}
body.uv-on .uv{opacity:1}
.uv-tint{position:absolute; inset:0; background:rgba(106,47,208,.17)}
.uv-big{
position:absolute; font-family:var(--f-head); font-weight:700; letter-spacing:.1em;
color:transparent; -webkit-text-stroke:1.5px rgba(177,140,255,.85); white-space:nowrap;
}
.uv-front .uv-big{font-size:42px; top:26%; left:6%; transform:rotate(-14deg)}
.uv-back .uv-big{font-size:34px; top:34%; right:4%; transform:rotate(10deg)}
.uv-ring{position:absolute; border:1px dashed rgba(255,90,209,.55); border-radius:50%}
.uv-front .uv-ring{width:110px; height:110px; right:12px; bottom:34px}
.uv-back .uv-ring{width:120px; height:120px; left:14px; top:20%}
.fibers{position:absolute; inset:0}
.fibers i{position:absolute; height:1.5px; border-radius:1px; opacity:.9}
.glare{
position:absolute; inset:0; z-index:8; pointer-events:none; mix-blend-mode:soft-light;
background:radial-gradient(circle at var(--gx,30%) var(--gy,25%), rgba(255,255,255,.9), transparent 55%);
transition:opacity .4s;
}
body.uv-on .glare{opacity:.15}
.scan{
position:absolute; left:0; right:0; top:-3px; height:2px; z-index:7; pointer-events:none;
background:rgba(176,141,67,.55); opacity:0;
}
.card3d:hover .scan{animation:scanmove 3.2s linear infinite}
@keyframes scanmove{0%{top:-3px; opacity:0}12%{opacity:.7}88%{opacity:.7}100%{top:100%; opacity:0}}
.flash{position:absolute; inset:0; z-index:9; background:#fff; opacity:0; pointer-events:none}
.card.issuing .flash{animation:reissueFlash 1s ease}
@keyframes reissueFlash{0%{opacity:0}18%{opacity:.95}40%{opacity:.1}55%{opacity:.7}100%{opacity:0}}
.card-shadow{
position:absolute; bottom:26px; left:50%; width:62%; height:26px; margin-left:-31%;
background:radial-gradient(ellipse, rgba(0,0,0,.55), transparent 70%);
filter:blur(9px); transition:opacity .4s;
}
#uvDim{
position:fixed; inset:0; z-index:30; pointer-events:none;
background:rgba(7,4,17,.82); opacity:0; transition:opacity .45s;
}
body.uv-on #uvDim{opacity:1}
/* ===== Панель управления ===== */
.panel{
background:var(--bg2); border:1px solid rgba(176,141,67,.3);
padding:26px 26px 22px; position:relative;
}
.panel::before{content:''; position:absolute; inset:0; background-image:var(--grain); pointer-events:none}
.panel-head{
display:flex; justify-content:space-between; align-items:baseline; gap:10px;
font-family:var(--f-head); font-size:14px; letter-spacing:.22em; font-weight:600;
border-bottom:1px solid var(--line); padding-bottom:12px; margin-bottom:10px;
}
.panel-head span{font-family:var(--f-mono); font-size:11px; color:var(--wax2); letter-spacing:.08em; font-weight:500}
.rows .row{display:flex; align-items:baseline; gap:8px; padding:7px 0; font-size:12px}
.rows dt{color:rgba(233,225,202,.48); font-size:10px; letter-spacing:.1em; text-transform:uppercase; white-space:nowrap}
.rows .dots{flex:1; border-bottom:1px dotted rgba(213,201,168,.28); transform:translateY(-3px)}
.rows dd{font-size:12.5px; font-weight:500; text-align:right}
.rows dd.status{display:flex; align-items:center; gap:8px; color:var(--phos); font-size:11px; letter-spacing:.12em}
.rows dd.status::before{content:''; width:6px; height:6px; border-radius:50%; background:var(--phos); animation:pulse 1.6s infinite}
.term{
margin-top:16px; background:#0c0b08; border:1px solid var(--line);
padding:12px 14px; height:172px; overflow-y:auto;
font-size:11px; line-height:2; color:#b9ad8f;
}
.term::-webkit-scrollbar{width:5px}
.tl{white-space:pre-wrap; word-break:break-word}
.tl .pfx{color:rgba(143,214,148,.4)}
.tl.good{color:var(--phos)}
.tl.idle{color:rgba(185,173,143,.45)}
.btns{display:flex; gap:8px; margin-top:16px; flex-wrap:wrap}
.btn{
flex:1; min-width:120px; display:flex; align-items:center; justify-content:center; gap:8px;
font-family:var(--f-mono); font-size:10.5px; letter-spacing:.14em; text-transform:uppercase;
padding:13px 10px; background:transparent; color:var(--paper);
border:1px solid rgba(213,201,168,.35); cursor:pointer;
transition:background .15s, color .15s, border-color .15s;
}
.btn svg{flex:none}
.btn:hover{background:var(--paper); color:#17150f}
.btn:active{transform:translateY(1px)}
.btn.busy{opacity:.4; pointer-events:none}
.btn.uvbtn.on{background:rgba(106,47,208,.22); border-color:#8f6ae0; color:#cdb6ff}
.btn.uvbtn.on:hover{background:rgba(106,47,208,.38); color:#efe7ff}
.btn.primary{border-color:var(--wax); color:var(--wax2)}
.btn.primary:hover{background:var(--wax); color:var(--paper)}
.sub-row{display:flex; justify-content:space-between; align-items:center; margin-top:14px; gap:10px}
.linkbtn{
background:none; border:none; cursor:pointer; font-family:var(--f-mono);
font-size:10px; letter-spacing:.12em; color:rgba(233,225,202,.55);
border-bottom:1px dotted rgba(233,225,202,.4); padding:2px 0; text-transform:uppercase;
transition:color .15s;
}
.linkbtn:hover{color:var(--brass)}
/* ===== Журнал отметок ===== */
section{position:relative; z-index:1}
.sec-head{display:flex; align-items:center; gap:20px; margin-bottom:34px}
.sec-head h2{
font-family:var(--f-head); font-weight:600; font-size:clamp(22px,3.4vw,34px);
letter-spacing:.14em; text-transform:uppercase; white-space:nowrap;
}
.sec-head .rule{flex:1; height:1px; background:var(--line)}
.sec-head .counter{text-align:right; line-height:1.1}
.sec-head .counter small{font-size:9px; letter-spacing:.24em; color:rgba(233,225,202,.45); display:block}
.sec-head .counter b{font-family:var(--f-head); font-size:34px; font-weight:600; color:var(--brass)}
#marks{padding:20px 0 70px}
.mark-rail{display:flex; flex-wrap:wrap; gap:22px 26px; align-items:center}
.mark{
--rot:-3deg; display:inline-block; padding:7px 14px 8px; text-align:center;
border:2.5px solid rgba(192,90,78,.75); color:var(--wax2);
-webkit-mask-image:var(--stampmask); mask-image:var(--stampmask); -webkit-mask-size:210px; mask-size:210px;
transform:rotate(var(--rot)); animation:markIn .4s cubic-bezier(.2,1.5,.3,1) both;
}
.mark b{display:block; font-family:var(--f-head); font-weight:600; font-size:16px; letter-spacing:.24em; text-indent:.24em}
.mark i{display:block; font-style:normal; font-size:8.5px; letter-spacing:.14em; opacity:.8; margin-top:2px}
@keyframes markIn{from{opacity:0; transform:rotate(var(--rot)) scale(2.2)}to{opacity:1; transform:rotate(var(--rot)) scale(1)}}
.mark-hint{font-size:10px; color:rgba(233,225,202,.35); letter-spacing:.12em}
/* ===== Особые отметки / регламент ===== */
#notes{padding:0 0 90px}
.notes-grid{display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:56px; align-items:start}
@media(max-width:900px){.notes-grid{grid-template-columns:1fr}}
.sheet{
background:var(--paper); color:var(--ink); padding:30px 32px 34px;
transform:rotate(-.6deg); position:relative;
box-shadow:0 20px 50px rgba(0,0,0,.45);
}
.sheet::before{content:''; position:absolute; inset:0; background-image:var(--grain); pointer-events:none}
.sheet::after{ /* дырки от дырокола */
content:''; position:absolute; left:12px; top:24%; width:9px; height:9px; border-radius:50%;
background:var(--bg); box-shadow:0 90px 0 var(--bg);
}
.sheet h3{font-family:var(--f-head); font-size:13px; letter-spacing:.26em; font-weight:600;
border-bottom:2px solid var(--ink); padding-bottom:9px; margin-bottom:16px}
.typed{font-size:12.5px; line-height:2.05; min-height:200px; white-space:pre-wrap}
.caret{display:inline-block; width:8px; height:14px; background:var(--ink); vertical-align:-2px;
animation:pulse 1s steps(1) infinite; margin-left:1px}
.reglist{list-style:none}
.reglist li{display:flex; gap:18px; padding:17px 4px; border-bottom:1px solid var(--line);
transition:transform .2s, background .2s}
.reglist li:hover{transform:translateX(6px)}
.reglist .num{font-size:11px; color:var(--brass); letter-spacing:.1em; padding-top:3px}
.reglist b{display:block; font-family:var(--f-head); font-weight:600; font-size:15px;
letter-spacing:.16em; text-transform:uppercase; margin-bottom:5px}
.reglist p{font-size:12px; line-height:1.85; color:rgba(233,225,202,.6)}
/* ===== Подвал ===== */
footer{border-top:1px solid var(--line); padding:34px 28px 44px; position:relative; z-index:1; overflow:hidden}
.mrz-foot{display:flex; flex-direction:column; gap:6px; margin-bottom:26px}
.mrz-foot span{
font-size:10px; letter-spacing:.34em; color:rgba(233,225,202,.42);
white-space:nowrap; overflow:hidden;
}
.foot-row{display:flex; justify-content:space-between; gap:16px; flex-wrap:wrap;
font-size:10px; letter-spacing:.16em; text-transform:uppercase; color:rgba(233,225,202,.4)}
.foot-row b{color:var(--wax2); font-weight:500}
/* ===== Модалка формы №2-КС ===== */
.overlay{
position:fixed; inset:0; z-index:100; display:flex; align-items:center; justify-content:center;
background:rgba(10,8,5,.72); backdrop-filter:blur(4px);
opacity:0; pointer-events:none; transition:opacity .25s; padding:20px;
}
.overlay.open{opacity:1; pointer-events:auto}
.modal{
width:min(480px,100%); background:var(--paper); color:var(--ink);
padding:28px 30px 26px; position:relative; transform:translateY(18px); transition:transform .3s;
box-shadow:0 30px 80px rgba(0,0,0,.6);
}
.overlay.open .modal{transform:none}
.modal::before{content:''; position:absolute; inset:0; background-image:var(--grain); pointer-events:none}
.modal h3{font-family:var(--f-head); font-size:15px; letter-spacing:.2em; font-weight:600}
.modal .m-sub{font-size:9.5px; letter-spacing:.14em; color:var(--ink-soft); margin:4px 0 20px;
border-bottom:2px solid var(--ink); padding-bottom:12px}
.m-close{
position:absolute; top:14px; right:14px; width:30px; height:30px; border:1px solid rgba(38,38,43,.4);
background:none; cursor:pointer; color:var(--ink); display:flex; align-items:center; justify-content:center;
transition:background .15s,color .15s;
}
.m-close:hover{background:var(--ink); color:var(--paper)}
.field{margin-bottom:15px; color-scheme:light}
.field label{display:block; font-size:9px; letter-spacing:.2em; text-transform:uppercase;
color:var(--ink-soft); margin-bottom:3px}
.field input{
width:100%; background:transparent; border:none; border-bottom:1px solid rgba(38,38,43,.55);
font-family:var(--f-mono); font-size:14px; padding:6px 2px; color:var(--ink);
text-transform:uppercase; border-radius:0;
}
.field input:focus{outline:none; border-bottom-color:var(--wax)}
.field.err input{border-bottom-color:var(--wax); background:rgba(168,52,42,.06)}
.field-2{display:grid; grid-template-columns:1fr 1fr; gap:16px}
.form-err{font-size:10px; color:var(--wax); min-height:15px; letter-spacing:.06em; margin:2px 0 8px}
.m-submit{
width:100%; font-family:var(--f-head); font-size:14px; letter-spacing:.26em; font-weight:600;
background:var(--wax); color:var(--paper); border:none; padding:14px; cursor:pointer;
text-transform:uppercase; transition:filter .15s, transform .05s;
}
.m-submit:hover{filter:brightness(1.12)}
.m-submit:active{transform:translateY(1px)}
/* ===== Тосты ===== */
#toasts{position:fixed; bottom:26px; left:50%; transform:translateX(-50%); z-index:90;
display:flex; flex-direction:column; gap:8px; align-items:center; pointer-events:none}
.toast{
background:#0f0d0a; border:1px solid rgba(176,141,67,.4); border-left:3px solid var(--brass);
color:var(--paper); font-size:11.5px; letter-spacing:.06em; padding:12px 18px;
opacity:0; transform:translateY(12px); transition:.3s; max-width:min(460px,90vw);
}
.toast.show{opacity:1; transform:none}
/* ===== Reveal ===== */
.rv{opacity:0; transform:translateY(26px); transition:opacity .8s ease, transform .8s ease}
.rv.in{opacity:1; transform:none}
</style>
</head>
<body>
<header>
<div class="brand">
<svg width="16" height="16" viewBox="0 0 24 24"><rect x="7" y="7" width="10" height="10" fill="#a8342a" transform="rotate(45 12 12)"/><rect x="9.5" y="9.5" width="5" height="5" fill="none" stroke="#e9e1ca" stroke-width="1"/></svg>
ФОРМА №1-КС
</div>
<div class="mid">ЕДИНЫЙ РЕЕСТР ЦИФРОВЫХ ЛИЧНЫХ ДЕЛ · ЦРЛД-1</div>
<div class="right"><span id="clock">00:00:00</span><span class="dot"></span>РЕЕСТР АКТИВЕН</div>
</header>
<main>
<!-- ============ ЗАГОЛОВОК ============ -->
<section class="hero">
<div class="eyebrow">Цифровое удостоверение личности · редакция 3.1</div>
<div class="hero-row">
<div>
<h1 class="giant">КСИ<span class="out">ВА</span></h1>
<p class="slogan">Настоящий документ подтверждает одно простое обстоятельство: <b>вы — это вы</b>. Остальные сведения реестр добудет сам.</p>
</div>
<div class="hero-meta">
ДОКУМЕНТ <b id="docNoHero">№ 0000-000 / КС</b><br>
ЭКЗЕМПЛЯР: ЕДИНСТВЕННЫЙ<br>
СЕРИЯ: КД · СТАТУС: В СИЛЕ<br>
ПОДДЕЛКА ПРЕСЛЕДУЕТСЯ
</div>
</div>
</section>
<!-- ============ СЦЕНА: КАРТА + ПАНЕЛЬ ============ -->
<div class="workbench">
<div class="stage rv in">
<div class="card3d" id="card3d">
<div class="strap"></div>
<div class="clip"></div>
<div class="card" id="card">
<!-- ЛИЦО -->
<div class="face front">
<div class="c-head">
<div>
<div class="c-title">ЦИФРОВОЕ УДОСТОВЕРЕНИЕ</div>
<div class="c-sub">СЛУЖБА РЕЕСТРА · ФОРМА 1-КС</div>
</div>
<svg width="34" height="34" viewBox="0 0 34 34">
<rect x="9" y="9" width="16" height="16" fill="none" stroke="#26262b" stroke-width="1.6" transform="rotate(45 17 17)"/>
<rect x="12.5" y="12.5" width="9" height="9" fill="none" stroke="#26262b" stroke-width="1" transform="rotate(45 17 17)"/>
<circle cx="17" cy="17" r="1.8" fill="#a8342a"/>
</svg>
</div>
<div class="c-mid">
<div class="c-photo">
<img id="photo" alt="Фото владельца">
<span class="photo-stamp stamp-look">ФОТО<br>ЗАВЕРЕНО</span>
</div>
<div class="c-fields">
<div class="fio"><span id="fioL1">—</span><span id="fioL2">—</span></div>
<dl>
<div class="drow"><dt>Дата рождения</dt><span class="dots"></span><dd id="dBirth">—</dd></div>
<div class="drow"><dt>Место выдачи</dt><span class="dots"></span><dd id="dPlace">—</dd></div>
<div class="drow"><dt>Серия, номер</dt><span class="dots"></span><dd id="dSeries">—</dd></div>
<div class="drow"><dt>Действ. до</dt><span class="dots"></span><dd id="dValid">—</dd></div>
</dl>
</div>
</div>
<div class="c-bottom">
<div>
<div class="sign" id="sign">—</div>
<div class="sign-line">подпись владельца</div>
</div>
<div class="holo" id="holo"><span>КС</span></div>
</div>
<div class="c-strip">
<div class="micro">ЦИФРОВОЕУДОСТОВЕРЕНИЕЛИЧНОСТИ·РЕЕСТР·ФОРМА1КС·ЦИФРОВОЕУДОСТОВЕРЕНИЕЛИЧНОСТИ·РЕЕСТР·ФОРМА1КС·</div>
<div class="perf" id="perfoNum">№ 0000-0000</div>
</div>
<div class="issue-stamp stamp-look"><b>ВЫДАНО</b><i id="issueStampDate">—</i></div>
<div class="stamp-ok stamp-look"><b>ПОДЛИННО</b><i class="ok-sub">РЕЕСТР · СВЕРКА ПРОЙДЕНА</i></div>
</div>
<!-- ОБОРОТ -->
<div class="face back">
<div class="guilloche"></div>
<div class="b-head">ОБОРОТНАЯ СТОРОНА · СЛУЖЕБНЫЕ СВЕДЕНИЯ</div>
<div class="b-rows">
<div class="drow"><dt>Орган выдачи</dt><span class="dots"></span><dd>ЦРЛД-1 «ЦИФРОВОЙ ПЕРЕВАЛ»</dd></div>
<div class="drow"><dt>Особая отметка</dt><span class="dots"></span><dd>НОСИТ КСИВУ С ДОСТОИНСТВОМ</dd></div>
<div class="drow"><dt>Код документа</dt><span class="dots"></span><dd id="codeHex">#00000000</dd></div>
</div>
<div class="b-bar">
<div class="barcode" id="barcode"></div>
<div class="bc-num" id="bcNum">0000 0000 / КС</div>
</div>
<div class="b-note">При утрате документа не переживайте: ксива цифровая, а вы — нет.</div>
<div class="mrz"><span id="mrz1"></span><span id="mrz2"></span></div>
<div class="stamp-ok stamp-look"><b>ПОДЛИННО</b><i class="ok-sub">РЕЕСТР · СВЕРКА ПРОЙДЕНА</i></div>
</div>
<!-- UV-слои -->
<div class="uv uv-front"><div class="uv-tint"></div><div class="uv-big">ПОДЛИННО</div><div class="uv-ring"></div><div class="fibers"></div></div>
<div class="uv uv-back"><div class="uv-tint"></div><div class="uv-big">КС·01</div><div class="uv-ring"></div><div class="fibers"></div></div>
<div class="glare"></div>
<div class="scan"></div>
<div class="flash"></div>
</div>
</div>
<div class="card-shadow" id="cardShadow"></div>
</div>
<!-- ПАНЕЛЬ -->
<aside class="panel rv in">
<div class="panel-head">ЛИЧНОЕ ДЕЛО <span id="caseNo">№ —</span></div>
<dl class="rows">
<div class="row"><dt>Владелец</dt><span class="dots"></span><dd id="owner">—</dd></div>
<div class="row"><dt>Дата выдачи</dt><span class="dots"></span><dd id="dIssue">—</dd></div>
<div class="row"><dt>Реестр</dt><span class="dots"></span><dd>ЦРЛД-1, секция «Физлица»</dd></div>
<div class="row"><dt>Статус</dt><span class="dots"></span><dd class="status">ДЕЙСТВИТЕЛЕН</dd></div>
</dl>
<div class="term" id="term"></div>
<div class="btns">
<button class="btn" id="btnFlip" type="button">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 0 1 15.5-6.2M21 12a9 9 0 0 1-15.5 6.2"/><path d="M18 2v4h-4M6 22v-4h4"/></svg>
<span>Перевернуть</span>
</button>
<button class="btn uvbtn" id="btnUV" type="button">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M9 18h6M10 21h4"/><path d="M12 3a6 6 0 0 1 4 10.5c-.8.7-1 1.5-1 2.5h-6c0-1-.2-1.8-1-2.5A6 6 0 0 1 12 3z"/></svg>
<span>УФ-контроль</span>
</button>
<button class="btn primary" id="btnVerify" type="button">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l7 3v5c0 4.6-3 7.6-7 9.2C8 18.6 5 15.6 5 11V6z"/><path d="M9.2 11.8l2 2 3.8-4"/></svg>
<span>Подлинность</span>
</button>
</div>
<div class="btns" style="margin-top:8px">
<button class="btn primary" id="btnReissue" type="button">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20l1.2-4.2L16.5 4.5l3 3L8.2 18.8z"/><path d="M14.5 6.5l3 3"/></svg>
<span>Оформить на себя</span>
</button>
<button class="btn" id="btnCopy" type="button">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="1"/><path d="M5 15V5a1 1 0 0 1 1-1h10"/></svg>
<span>Реквизиты</span>
</button>
</div>
</aside>
</div>
<!-- ============ ЖУРНАЛ ОТМЕТОК ============ -->
<section id="marks" class="rv">
<div class="sec-head">
<h2>Журнал отметок</h2>
<div class="rule"></div>
<div class="counter"><small>операций</small><b id="opCount">001</b></div>
</div>
<div class="mark-rail" id="markRail"></div>
</section>
<!-- ============ ОСОБЫЕ ОТМЕТКИ ============ -->
<section id="notes" class="rv">
<div class="sec-head"><h2>Особые отметки</h2><div class="rule"></div></div>
<div class="notes-grid">
<div class="sheet">
<h3>ЛИСТ №2 · МАШИНОПИСЬ</h3>
<div class="typed" id="typew"><span class="caret"></span></div>
</div>
<ul class="reglist">
<li><span class="num">01</span><div><b>Голограмма</b><p>Меняет цвет при наклоне карты. Проверяется невооружённым глазом и лёгким недоверием.</p></div></li>
<li><span class="num">02</span><div><b>УФ-слой</b><p>Включите режим «УФ-контроль» и поводите светом по поверхности — проявятся волокна и скрытая надпись.</p></div></li>
<li><span class="num">03</span><div><b>Штамп реестра</b><p>Отметка «ПОДЛИННО» ставится после сверки с реестром. Стереть её можно, честность — нельзя.</p></div></li>
<li><span class="num">04</span><div><b>Перевыпуск</b><p>Ксива оформляется на любое имя по форме №2-КС. Без очереди, без вопросов и без походов в окошко.</p></div></li>
</ul>
</div>
</section>
</main>
<footer>
<div class="mrz-foot"><span id="mrzF1"></span><span id="mrzF2"></span></div>
<div class="foot-row">
<span>СЛУЖБА ЦИФРОВОГО РЕЕСТРА · <span id="year"></span></span>
<span><b>ПОДДЕЛКА КСИВЫ ПРЕСЛЕДУЕТСЯ</b> ПО ЗАКОНУ И ПО ПОНЯТИЯМ</span>
</div>
</footer>
<!-- Модалка: форма №2-КС -->
<div class="overlay" id="overlay" role="dialog" aria-modal="true" aria-label="Оформление ксивы на себя">
<form class="modal" id="reissueForm" novalidate>
<button class="m-close" type="button" id="mClose" aria-label="Закрыть">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M5 5l14 14M19 5L5 19"/></svg>
</button>
<h3>ЗАЯВЛЕНИЕ · ФОРМА №2-КС</h3>
<div class="m-sub">ЗАПОЛНЯЕТСЯ РАЗБОЧЛИВО, ЖЕЛАТЕЛЬНО — ПРАВДИВО</div>
<div class="field" id="fSurname"><label>Фамилия *</label><input id="iSurname" type="text" maxlength="24" autocomplete="off" placeholder="ЗОРКИЙ"></div>
<div class="field" id="fName"><label>Имя *</label><input id="iName" type="text" maxlength="24" autocomplete="off" placeholder="АЛЕКСАНДР"></div>
<div class="field" id="fPatron"><label>Отчество (при наличии)</label><input id="iPatron" type="text" maxlength="24" autocomplete="off" placeholder="ИЛЬИЧ"></div>
<div class="field-2">
<div class="field" id="fBday"><label>Дата рождения *</label><input id="iBday" type="date"></div>
<div class="field" id="fCity"><label>Город выдачи *</label><input id="iCity" type="text" maxlength="24" autocomplete="off" placeholder="МОСКВА"></div>
</div>
<div class="form-err" id="formErr"></div>
<button class="m-submit" type="submit">Подписать и выдать</button>
</form>
</div>
<div id="uvDim"></div>
<div id="toasts"></div>
<script>
(() => {
'use strict';
const $ = s => document.querySelector(s);
const $$ = s => document.querySelectorAll(s);
const sleep = ms => new Promise(r => setTimeout(r, ms));
const cap = s => s ? s.charAt(0).toUpperCase() + s.slice(1).toLowerCase() : s;
/* ---------- утилиты ---------- */
function hashStr(s){ let h=2166136261; for(const c of s){ h^=c.codePointAt(0); h=Math.imul(h,16777619); } return h>>>0; }
function mulberry32(a){ return function(){ a|=0; a=a+0x6D2B79F5|0; let t=Math.imul(a^a>>>15,1|a); t=t+Math.imul(t^t>>>7,61|t)^t; return ((t^t>>>14)>>>0)/4294967296; }; }
const timeNow = () => new Date().toLocaleTimeString('ru-RU',{hour:'2-digit',minute:'2-digit'});
const fmtDate = d => String(d.getDate()).padStart(2,'0')+'.'+String(d.getMonth()+1).padStart(2,'0')+'.'+d.getFullYear();
const TL={'а':'a','б':'b','в':'v','г':'g','д':'d','е':'e','ё':'e','ж':'zh','з':'z','и':'i','й':'i','к':'k','л':'l','м':'m','н':'n','о':'o','п':'p','р':'r','с':'s','т':'t','у':'u','ф':'f','х':'kh','ц':'ts','ч':'ch','ш':'sh','щ':'shch','ъ':'','ы':'y','ь':'','э':'e','ю':'iu','я':'ia'};
const translit = s => s.toLowerCase().split('').map(c => TL.hasOwnProperty(c)?TL[c]:c).join('').toUpperCase().replace(/[^A-Z0-9]/g,'<');
/* ---------- звук (тихий, процедурный) ---------- */
let ac=null;
document.addEventListener('pointerdown', () => {
if(!ac){ try{ ac = new (window.AudioContext||window.webkitAudioContext)(); }catch(e){} }
if(ac && ac.state==='suspended') ac.resume();
});
function tick(){ if(!ac) return; const o=ac.createOscillator(), g=ac.createGain();
o.type='square'; o.frequency.value=1500; g.gain.setValueAtTime(.014, ac.currentTime);
g.gain.exponentialRampToValueAtTime(.0001, ac.currentTime+.03);
o.connect(g).connect(ac.destination); o.start(); o.stop(ac.currentTime+.035); }
function flipSnd(){ if(!ac) return; const o=ac.createOscillator(), g=ac.createGain();
o.type='triangle'; o.frequency.setValueAtTime(320, ac.currentTime);
o.frequency.exponentialRampToValueAtTime(140, ac.currentTime+.09);
g.gain.setValueAtTime(.06, ac.currentTime); g.gain.exponentialRampToValueAtTime(.0001, ac.currentTime+.1);
o.connect(g).connect(ac.destination); o.start(); o.stop(ac.currentTime+.11); }
function thud(){ if(!ac) return;
const len=ac.sampleRate*.1, buf=ac.createBuffer(1,len,ac.sampleRate), d=buf.getChannelData(0);
for(let i=0;i<len;i++) d[i]=(Math.random()*2-1)*Math.pow(1-i/len,2.2);
const src=ac.createBufferSource(); src.buffer=buf;
const f=ac.createBiquadFilter(); f.type='lowpass'; f.frequency.value=280;
const g=ac.createGain(); g.gain.setValueAtTime(.5, ac.currentTime);
g.gain.exponentialRampToValueAtTime(.001, ac.currentTime+.14);
src.connect(f).connect(g).connect(ac.destination); src.start();
const o=ac.createOscillator(), og=ac.createGain(); o.type='sine'; o.frequency.value=68;
og.gain.setValueAtTime(.22, ac.currentTime); og.gain.exponentialRampToValueAtTime(.001, ac.currentTime+.16);
o.connect(og).connect(ac.destination); o.start(); o.stop(ac.currentTime+.17); }
/* ---------- состояние ---------- */
const state = {
flipTarget:0, flipCur:0, tX:0, tY:0, tXT:0, tYT:0,
dragging:false, moved:false, startX:0, startFlip:0,
uv:false, uvLogged:false, verified:false, ops:0, issueStr:'—',
doc:{ surname:'ЗОРКИЙ', name:'АЛЕКСАНДР', patronymic:'ИЛЬИЧ', bday:'1987-11-07', city:'ЛЕНИНГРАД' }
};
const card3d=$('#card3d'), card=$('#card'), stage=card3d.parentElement, shadow=$('#cardShadow');
const term=$('#term'), btnVerify=$('#btnVerify');
/* ---------- UV-волокна ---------- */
function buildFibers(){
const colors=['#ff5ad1','#63e0ff','#9dff6e','#ffd166','#c49bff'];
$$('.fibers').forEach(box=>{
box.innerHTML='';
const r=mulberry32(42);
for(let i=0;i<55;i++){
const f=document.createElement('i');
f.style.left=(r()*100)+'%'; f.style.top=(r()*100)+'%';
f.style.width=(7+r()*13)+'px';
f.style.transform=`rotate(${r()*180}deg)`;
f.style.background=colors[Math.floor(r()*colors.length)];
box.appendChild(f);
}
});
}
buildFibers();
/* ---------- штрих-код ---------- */
function buildBarcode(seed){
const el=$('#barcode'); el.innerHTML='';
const r=mulberry32(seed); let x=0;
while(x<98){
const w=.8+r()*2.4;
if(r()>.32){
const b=document.createElement('span');
b.style.left=x+'%'; b.style.width=Math.min(w, 98-x)+'%';
el.appendChild(b);
}
x+=w+.6+r()*1.7;
}
}
/* ---------- MRZ ---------- */
function makeMRZ(doc,h){
const lt=translit(doc.surname), fn=translit(doc.name), fp=doc.patronymic?translit(doc.patronymic):'';
let l1=('KC1CS<'+lt+'<'+fn+(fp?'<'+fp:'')).replace(/[^A-Z0-9<]/g,'').slice(0,36).padEnd(36,'<');
const bp=doc.bday.split('-'), b6=bp[0].slice(2)+bp[1]+bp[2];
const v=new Date(); v.setFullYear(v.getFullYear()+10);
const v6=String(v.getFullYear()).slice(2)+String(v.getMonth()+1).padStart(2,'0')+String(v.getDate()).padStart(2,'0');
let l2=(b6+(h%10)+'RUS'+v6+((h>>>3)%10)+'<'+String(h).padStart(8,'0')).replace(/[^A-Z0-9<]/g,'').slice(0,36).padEnd(36,'<');
return [l1,l2];
}
/* ---------- выдача документа ---------- */
function setDoc(doc){
state.doc=doc;
const key=(doc.surname+doc.name+(doc.patronymic||'')+doc.bday+doc.city).toLowerCase();
const h=hashStr(key);
const num=String(1000+h%9000)+'-'+String(100+(h>>>7)%900);
const ser='КД '+(40+h%60);
const issue=new Date(), valid=new Date(issue); valid.setFullYear(valid.getFullYear()+10);
state.issueStr=fmtDate(issue);
const bp=doc.bday.split('-'), bStr=bp[2]+'.'+bp[1]+'.'+bp[0];
$('#fioL1').textContent=doc.surname.toUpperCase();
$('#fioL2').textContent=(doc.name+(doc.patronymic?' '+doc.patronymic:'')).toUpperCase();
$('#dBirth').textContent=bStr;
$('#dPlace').textContent=doc.city.toUpperCase();
$('#dSeries').textContent=ser+' · №'+num;
$('#dValid').textContent=fmtDate(valid);
$('#sign').textContent=cap(doc.name)+' '+cap(doc.surname);
$('#issueStampDate').textContent=state.issueStr;
$('#perfoNum').textContent='№ '+num;
$('#docNoHero').textContent='№ '+num+' / КС';
$('#caseNo').textContent='№ '+num+'/КС';
$('#owner').textContent=cap(doc.surname)+' '+cap((doc.name||' ')[0])+'.'+(doc.patronymic?cap(doc.patronymic[0])+'.':'');
$('#dIssue').textContent=state.issueStr;
$('#codeHex').textContent='#'+h.toString(16).toUpperCase().padStart(8,'0');
$('#bcNum').textContent=num.replace('-',' ')+' / КС';
const img=$('#photo'); img.classList.remove('ld');
img.src='https://picsum.photos/seed/'+encodeURIComponent(doc.surname+doc.name+h)+'/240/300.jpg';
img.onload=()=>img.classList.add('ld');
const mrz=makeMRZ(doc,h);
$('#mrz1').textContent=mrz[0]; $('#mrz2').textContent=mrz[1];
$('#mrzF1').textContent=mrz[0]; $('#mrzF2').textContent=mrz[1];
buildBarcode(h);
}
/* ---------- терминал ---------- */
function termReset(){
term.innerHTML='';
const d=document.createElement('div');
d.className='tl idle';
d.innerHTML='<span class="pfx">> </span>РЕЕСТР НА СВЯЗИ · ОЖИДАНИЕ КОМАНД';
term.appendChild(d);
}
async function typeLine(text, good){
const d=document.createElement('div'); d.className='tl'+(good?' good':'');
d.innerHTML='<span class="pfx">> </span><span class="tx"></span>';
term.appendChild(d);
const tx=d.querySelector('.tx'); let n=0;
for(const ch of text){
tx.textContent+=ch;
if(++n%3===0) tick();
term.scrollTop=term.scrollHeight;
await sleep(11);
}
}
const STEPS=[
{t:'ЗАПРОС К РЕЕСТРУ ЦРЛД-1 … СЕКЦИЯ «ФИЗЛИЦА»'},
{t:'СВЕРКА СЕРИИ И НОМЕРА … СОВПАДАЕТ'},
{t:'ГОЛОГРАММА: ИНТЕРФЕРЕНЦИЯ В НОРМЕ'},
{t:'УФ-СЛОЙ: ЗАЩИТНЫЕ ВОЛОКНА ОБНАРУЖЕНЫ'},
{t:'ПОДПИСЬ: ПОХОЖА НА ВЛАДЕЛЬЦА'},
{t:'ИТОГ: ДОКУМЕНТ ПОДЛИНЕН. СТАВЛЮ ШТАМП.', good:true}
];
btnVerify.addEventListener('click', async () => {
if(state.verified){ // сведение отметки
state.verified=false;
$$('.stamp-ok').forEach(s=>s.classList.remove('on'));
termReset();
btnVerify.querySelector('span').textContent='ПОДЛИННОСТЬ';
addStamp('СВЕДЕНО','отметка снята · '+timeNow());
toast('Отметка сведена. Реестр не в обиде.');
flipSnd();
return;
}
btnVerify.classList.add('busy');
termReset();
for(const step of STEPS){
await typeLine(step.t, step.good);
await sleep(150);
}
state.verified=true; state.ops++; updateOps();
$$('.stamp-ok').forEach(s=>{
s.querySelector('.ok-sub').textContent='РЕЕСТР · '+timeNow();
s.classList.add('on');
});
thud();
addStamp('ВЕРИФИЦИРОВАНО', timeNow());
btnVerify.classList.remove('busy');
btnVerify.querySelector('span').textContent='СВЕСТИ ОТМЕТКУ';
});
/* ---------- 3D: наклон + переворот ---------- */
stage.addEventListener('pointermove', e => {
if(state.dragging) return;
const r=stage.getBoundingClientRect();
state.tYT=((e.clientX-r.left)/r.width-.5)*16;
state.tXT=-((e.clientY-r.top)/r.height-.5)*12;
});
stage.addEventListener('pointerleave', () => { state.tXT=0; state.tYT=0; });
card3d.addEventListener('pointermove', e => {
const r=card3d.getBoundingClientRect();
card.style.setProperty('--gx', ((e.clientX-r.left)/r.width*100)+'%');
card.style.setProperty('--gy', ((e.clientY-r.top)/r.height*100)+'%');
if(state.uv){ setUV(e.clientX-r.left, e.clientY-r.top); lastPtr=performance.now(); }
});
let lastPtr=0, uvT=0, uvX=170, uvY=240, uvTX=170, uvTY=240;
function setUV(x,y){ uvTX=x; uvTY=y; }
card3d.addEventListener('pointerdown', e => {
state.dragging=true; state.moved=false;
state.startX=e.clientX; state.startFlip=state.flipTarget;
card3d.setPointerCapture(e.pointerId);
});
card3d.addEventListener('pointermove', e => {
if(!state.dragging) return;
const dx=e.clientX-state.startX;
if(Math.abs(dx)>6) state.moved=true;
state.flipTarget=state.startFlip+dx*.45;
state.tXT=0; state.tYT=0;
});
function endDrag(){
if(!state.dragging) return;
state.dragging=false;
const snapped=Math.round(state.flipTarget/180)*180;
state.flipTarget=state.moved?snapped:snapped+180;
if(state.moved) flipSnd();
}
card3d.addEventListener('pointerup', endDrag);
card3d.addEventListener('pointercancel', endDrag);
/* ---------- цикл отрисовки ---------- */
function loop(){
if(state.dragging) state.flipCur=state.flipTarget;
else state.flipCur+=(state.flipTarget-state.flipCur)*.1;
state.tX+=(state.tXT-state.tX)*.08;
state.tY+=(state.tYT-state.tY)*.08;
card3d.style.transform=`rotateX(${state.tX.toFixed(2)}deg) rotateY(${(state.tY+state.flipCur).toFixed(2)}deg)`;
shadow.style.transform=`translateX(${((state.tY+state.flipCur%360)*.55).toFixed(1)}px)`;
if(state.uv){
if(performance.now()-lastPtr>1400){ // лампа дрейфует сама
uvT+=.011;
const w=card3d.offsetWidth, h=card3d.offsetHeight;
setUV(w/2+Math.sin(uvT)*w*.34, h/2+Math.cos(uvT*.8)*h*.3);
}
uvX+=(uvTX-uvX)*.16; uvY+=(uvTY-uvY)*.16;
$$('.uv').forEach(u=>{
u.style.setProperty('--ux', uvX.toFixed(1)+'px');
u.style.setProperty('--uy', uvY.toFixed(1)+'px');
});
}
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
/* ---------- кнопки ---------- */
$('#btnFlip').addEventListener('click', () => { state.flipTarget+=180; flipSnd(); });
$('#btnUV').addEventListener('click', () => {
state.uv=!state.uv;
document.body.classList.toggle('uv-on', state.uv);
$('#btnUV').classList.toggle('on', state.uv);
if(state.uv){
lastPtr=0;
if(!state.uvLogged){ state.uvLogged=true; addStamp('УФ-КОНТРОЛЬ', timeNow()); state.ops++; updateOps(); }
toast('УФ-лампа включена. Поводите светом по карте.');
}
});
$('#btnCopy').addEventListener('click', async () => {
const d=state.doc;
const text=`КСИВА · ${$('#owner').textContent} · ${$('#dSeries').textContent} · выдана ${state.issueStr}, ЦРЛД-1`;
try{ await navigator.clipboard.writeText(text); }
catch(e){
const t=document.createElement('textarea'); t.value=text; document.body.appendChild(t);
t.select(); document.execCommand('copy'); t.remove();
}
toast('Реквизиты скопированы. При предъявлении — уверенным тоном.');
});
/* ---------- модалка ---------- */
const overlay=$('#overlay');
$('#btnReissue').addEventListener('click', () => overlay.classList.add('open'));
$('#mClose').addEventListener('click', () => overlay.classList.remove('open'));
overlay.addEventListener('click', e => { if(e.target===overlay) overlay.classList.remove('open'); });
document.addEventListener('keydown', e => { if(e.key==='Escape') overlay.classList.remove('open'); });
$('#reissueForm').addEventListener('submit', e => {
e.preventDefault();
const sur=$('#iSurname').value.trim(), nam=$('#iName').value.trim(),
pat=$('#iPatron').value.trim(), bd=$('#iBday').value, ci=$('#iCity').value.trim();
let ok=true;
const mark=(id,cond)=>{ $(id).classList.toggle('err', !cond); if(!cond) ok=false; };
mark('#fSurname', !!sur); mark('#fName', !!nam);
mark('#fBday', !!bd); mark('#fCity', !!ci);
$('#formErr').textContent = ok ? '' : 'НЕЗАПОЛНЕННЫЕ ГРАФЫ НЕ ЯВЛЯЮТСЯ ГРАФАМИ. ЗАПОЛНИТЕ ГРАФЫ СО ЗВЁЗДОЧКОЙ.';
if(!ok) return;
overlay.classList.remove('open');
reissue({ surname:sur.toUpperCase(), name:nam.toUpperCase(),
patronymic:pat?pat.toUpperCase():'', bday:bd, city:ci.toUpperCase() });
});
function reissue(doc){
setDoc(doc);
state.verified=false; state.uvLogged=false;
$$('.stamp-ok').forEach(s=>s.classList.remove('on'));
termReset();
btnVerify.querySelector('span').textContent='ПОДЛИННОСТЬ';
card.classList.remove('issuing'); void card.offsetWidth; card.classList.add('issuing');
state.ops++; updateOps();
addStamp('ПЕРЕОФОРМЛЕНО', state.issueStr);
toast('Документ выдан: '+$('#owner').textContent+'. С предыдущим владельцем покончено.');
thud();
}
/* ---------- журнал ---------- */
const rail=$('#markRail');
function updateOps(){ $('#opCount').textContent=String(state.ops).padStart(3,'0'); }
function addStamp(kind, sub){
const m=document.createElement('div'); m.className='mark';
m.style.setProperty('--rot',(Math.random()*9-4.5).toFixed(1)+'deg');
m.innerHTML='<b></b><i></i>';
m.querySelector('b').textContent=kind;
m.querySelector('i').textContent=sub;
rail.prepend(m);
const hint=rail.querySelector('.mark-hint'); if(hint) hint.remove();
}
/* ---------- машинка ---------- */
const TY_TEXT='Настоящая ксива выдана на основании данных реестра и доброго имени владельца. Срок действия — до первого сомнения. При проверке сохранять спокойствие и лёгкую ухмылку. Владелец обязан носить документ с достоинством, голограмму — не царапать, а при утере — не переживать: всё цифровое, перевыпустите здесь же.';
let typed=false;
function startType(){
if(typed) return; typed=true;
const box=$('#typew');
const tn=document.createTextNode(''), caret=box.querySelector('.caret');
box.insertBefore(tn, caret);
let i=0;
(function step(){
if(i<TY_TEXT.length){
tn.textContent+=TY_TEXT[i++];
if(i%3===0) tick();
setTimeout(step, 21);
}
})();
}
new IntersectionObserver((es,o)=>{ es.forEach(e=>{ if(e.isIntersecting){ startType(); o.disconnect(); } }); },
{threshold:.35}).observe($('#typew'));
/* ---------- reveal при скролле ---------- */
const io=new IntersectionObserver(es=>es.forEach(e=>{ if(e.isIntersecting){ e.target.classList.add('in'); io.unobserve(e.target); } }),{threshold:.12});
$$('.rv').forEach(el=>io.observe(el));
/* ---------- тосты ---------- */
function toast(msg){
const box=$('#toasts'), t=document.createElement('div');
t.className='toast'; t.textContent=msg; box.appendChild(t);
requestAnimationFrame(()=>t.classList.add('show'));
setTimeout(()=>{ t.classList.remove('show'); setTimeout(()=>t.remove(),320); }, 3000);
}
/* ---------- часы, запуск ---------- */
function clock(){ $('#clock').textContent=new Date().toLocaleTimeString('ru-RU'); }
clock(); setInterval(clock,1000);
$('#year').textContent=new Date().getFullYear();
setDoc(state.doc);
termReset();
updateOps();
setTimeout(()=>{ // первичный оттиск «ВЫДАНО»
$$('.issue-stamp').forEach(s=>s.classList.add('on'));
addStamp('ВЫДАНО', state.issueStr); state.ops=1; updateOps();
const hint=document.createElement('span');
hint.className='mark-hint'; hint.textContent='// журнал заполняется по мере ваших действий';
rail.appendChild(hint);
}, 1200);
})();
</script>
</body>
</html>