Загрузка данных


<!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">&gt; </span>РЕЕСТР НА СВЯЗИ · ОЖИДАНИЕ КОМАНД';
    term.appendChild(d);
  }
  async function typeLine(text, good){
    const d=document.createElement('div'); d.className='tl'+(good?' good':'');
    d.innerHTML='<span class="pfx">&gt; </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>