work-license-server 1.0.20 → 1.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -843,6 +843,89 @@ html[data-theme="dark"] .sec-nav-item.active {
843
843
  .modal-head { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; }
844
844
  .modal-head img { width: 46px; height: 46px; }
845
845
 
846
+ /* ---------- Интерактивный плеер презентации ---------- */
847
+ .pres-modal { padding: 14px; background: rgba(2, 5, 12, .82); }
848
+ .pres-modal .pres-card {
849
+ max-width: 1320px;
850
+ width: min(1320px, 96vw);
851
+ max-height: 94vh;
852
+ padding: 14px 18px;
853
+ display: flex;
854
+ flex-direction: column;
855
+ gap: 12px;
856
+ overflow: hidden;
857
+ }
858
+ .pres-top {
859
+ display: flex;
860
+ align-items: center;
861
+ justify-content: space-between;
862
+ gap: 12px;
863
+ flex-wrap: wrap;
864
+ }
865
+ .pres-title-wrap {
866
+ display: flex;
867
+ align-items: center;
868
+ gap: 10px;
869
+ }
870
+ .pres-title-wrap img { width: 34px; height: 34px; }
871
+ .pres-title-wrap b { display: block; font-size: 15px; line-height: 1.2; }
872
+ .pres-title-wrap span { display: block; font-size: 12px; }
873
+ .pres-sections { display: flex; gap: 6px; flex-wrap: wrap; }
874
+ .pres-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
875
+ .pres-stage {
876
+ width: 100%;
877
+ aspect-ratio: 297 / 167;
878
+ max-height: calc(94vh - 140px);
879
+ border-radius: 12px;
880
+ overflow: hidden;
881
+ border: 1px solid var(--line-strong);
882
+ background: #060d18;
883
+ position: relative;
884
+ }
885
+ .pres-stage iframe {
886
+ width: 100%;
887
+ height: 100%;
888
+ border: 0;
889
+ display: block;
890
+ }
891
+ .pres-bottom {
892
+ display: flex;
893
+ align-items: center;
894
+ justify-content: space-between;
895
+ gap: 12px;
896
+ flex-wrap: wrap;
897
+ }
898
+ .pres-counter {
899
+ font-size: 13px;
900
+ font-weight: 700;
901
+ min-width: 92px;
902
+ text-align: center;
903
+ }
904
+ .pres-dots {
905
+ display: flex;
906
+ align-items: center;
907
+ gap: 5px;
908
+ flex-wrap: wrap;
909
+ }
910
+ .pres-dot {
911
+ width: 28px;
912
+ height: 28px;
913
+ border-radius: 8px;
914
+ border: 1px solid var(--line-strong);
915
+ background: color-mix(in srgb, var(--panel-solid) 60%, transparent);
916
+ color: var(--text-2);
917
+ font-size: 12px;
918
+ font-weight: 700;
919
+ cursor: pointer;
920
+ transition: all .15s;
921
+ }
922
+ .pres-dot:hover { border-color: var(--brand); color: var(--text); }
923
+ .pres-dot.on {
924
+ background: linear-gradient(135deg, var(--brand), #2563eb);
925
+ border-color: var(--brand);
926
+ color: #fff;
927
+ }
928
+
846
929
  /* ---------- Подвал ---------- */
847
930
  .footer { margin-top: 70px; padding-top: 34px; border-top: 1px solid var(--line); display: grid; grid-template-columns: 1.3fr 1fr 1fr; gap: 30px; font-size: 13px; color: var(--muted); }
848
931
  .footer h4 { margin: 0 0 10px; color: var(--text); font-size: 14px; }
package/assets/landing.js CHANGED
@@ -2100,6 +2100,68 @@
2100
2100
  else requestAnimationFrame(frame);
2101
2101
  }
2102
2102
 
2103
+ /* ================= Интерактивный плеер презентации ODANT-WORK ================= */
2104
+ let currentPresSlide = 1;
2105
+ const PRES_TOTAL_SLIDES = 12;
2106
+
2107
+ function syncPresIndicators(slideNum) {
2108
+ currentPresSlide = Math.max(1, Math.min(PRES_TOTAL_SLIDES, Number(slideNum) || 1));
2109
+ const cnt = $('#presCounter');
2110
+ if (cnt) cnt.textContent = `Слайд ${currentPresSlide} / ${PRES_TOTAL_SLIDES}`;
2111
+ $$('#presDots .pres-dot').forEach(btn => {
2112
+ btn.classList.toggle('on', Number(btn.dataset.slide) === currentPresSlide);
2113
+ });
2114
+ const sec = currentPresSlide >= 10 ? 'franchise' : (currentPresSlide >= 8 ? 'clients' : 'product');
2115
+ $$('#presSectionTabs .chip').forEach(btn => {
2116
+ btn.classList.toggle('on', btn.dataset.sec === sec);
2117
+ });
2118
+ }
2119
+
2120
+ function openPresentationModal(startSlide = 1) {
2121
+ const modal = $('#presentationModal');
2122
+ const frame = $('#presFrame');
2123
+ if (!modal || !frame) return;
2124
+ const n = Math.max(1, Math.min(PRES_TOTAL_SLIDES, Number(startSlide) || 1));
2125
+ syncPresIndicators(n);
2126
+ const targetSrc = `/presentation?embed=1&slide=${n}`;
2127
+ if (!frame.getAttribute('src')) {
2128
+ frame.setAttribute('src', targetSrc);
2129
+ } else {
2130
+ try {
2131
+ frame.contentWindow?.postMessage({ type: 'odant-presentation-goto', slide: n }, '*');
2132
+ } catch {
2133
+ frame.setAttribute('src', targetSrc);
2134
+ }
2135
+ }
2136
+ modal.classList.add('open');
2137
+ }
2138
+
2139
+ function closePresentationModal() {
2140
+ $('#presentationModal')?.classList.remove('open');
2141
+ }
2142
+
2143
+ function gotoPresentationSlide(slideNum) {
2144
+ const n = Math.max(1, Math.min(PRES_TOTAL_SLIDES, Number(slideNum) || 1));
2145
+ syncPresIndicators(n);
2146
+ const frame = $('#presFrame');
2147
+ try {
2148
+ frame?.contentWindow?.postMessage({ type: 'odant-presentation-goto', slide: n }, '*');
2149
+ } catch { /* ignore */ }
2150
+ }
2151
+
2152
+ function stepPresentationSlide(delta) {
2153
+ gotoPresentationSlide(currentPresSlide + (Number(delta) || 0));
2154
+ }
2155
+
2156
+ window.addEventListener('message', (e) => {
2157
+ const d = e.data || {};
2158
+ if (d.type === 'odant-presentation-slide' && d.slide) {
2159
+ syncPresIndicators(d.slide);
2160
+ } else if (d.type === 'odant-presentation-close') {
2161
+ closePresentationModal();
2162
+ }
2163
+ });
2164
+
2103
2165
  /* ================= Привязка событий ================= */
2104
2166
  function bind() {
2105
2167
  document.addEventListener('click', (e) => {
@@ -2223,6 +2285,26 @@
2223
2285
  case 'cert-copy':
2224
2286
  navigator.clipboard?.writeText($('#certJsonBox')?.textContent || '');
2225
2287
  break;
2288
+ case 'open-presentation':
2289
+ e.preventDefault();
2290
+ openPresentationModal(Number(el.dataset.slide || 1));
2291
+ break;
2292
+ case 'pres-close':
2293
+ closePresentationModal();
2294
+ break;
2295
+ case 'pres-goto':
2296
+ gotoPresentationSlide(Number(el.dataset.slide || 1));
2297
+ break;
2298
+ case 'pres-prev':
2299
+ stepPresentationSlide(-1);
2300
+ break;
2301
+ case 'pres-next':
2302
+ stepPresentationSlide(1);
2303
+ break;
2304
+ case 'pres-jump-stage':
2305
+ closePresentationModal();
2306
+ if (el.dataset.targetStage) switchStage(el.dataset.targetStage);
2307
+ break;
2226
2308
  case 'ai-toggle': toggleAi(); break;
2227
2309
  case 'ai-send': sendAi(); break;
2228
2310
  }
@@ -2230,8 +2312,13 @@
2230
2312
  $('#authModal')?.addEventListener('click', (e) => { if (e.target.id === 'authModal') closeAuth(); });
2231
2313
  $('#ofertaModal')?.addEventListener('click', (e) => { if (e.target.id === 'ofertaModal') closeOfertaModal(); });
2232
2314
  $('#certModal')?.addEventListener('click', (e) => { if (e.target.id === 'certModal') $('#certModal').classList.remove('open'); });
2315
+ $('#presentationModal')?.addEventListener('click', (e) => { if (e.target.id === 'presentationModal') closePresentationModal(); });
2233
2316
  document.addEventListener('keydown', (e) => {
2234
- if (e.key === 'Escape') { closeAuth(); closeOfertaModal(); $('#certModal')?.classList.remove('open'); toggleAi(false); }
2317
+ if (e.key === 'Escape') { closeAuth(); closeOfertaModal(); closePresentationModal(); $('#certModal')?.classList.remove('open'); toggleAi(false); }
2318
+ else if ($('#presentationModal')?.classList.contains('open')) {
2319
+ if (e.key === 'ArrowRight' || e.key === 'PageDown') { e.preventDefault(); stepPresentationSlide(1); }
2320
+ else if (e.key === 'ArrowLeft' || e.key === 'PageUp') { e.preventDefault(); stepPresentationSlide(-1); }
2321
+ }
2235
2322
  });
2236
2323
  $('#authModal')?.addEventListener('keydown', (e) => {
2237
2324
  if (e.key === 'Enter' && e.target.tagName !== 'BUTTON') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "work-license-server",
3
- "version": "1.0.20",
3
+ "version": "1.0.21",
4
4
  "description": "Лицензионный сервер, биллинг ЮKassa, боты поддержки (MAX, Telegram, VK) и лендинг ПАК WORK (work.odant.org)",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/landing.js CHANGED
@@ -200,7 +200,13 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
200
200
  <span class="kicker">Платформа цифровой организации</span>
201
201
  <h1 class="h2" style="font-size:clamp(1.9rem,3vw,2.6rem);margin:4px 0 0">ODANT-WORK — учёт, документооборот, процессы и&nbsp;<span class="grad">приватный ИИ</span></h1>
202
202
  </div>
203
- <p>Все данные предприятия — справочники, проводки, документы, переписка и задачи — живут в единой модели с общими правами доступа и работают автономно в вашем контуре без передачи данных наружу.</p>
203
+ <div>
204
+ <p style="margin:0 0 12px">Все данные предприятия — справочники, проводки, документы, переписка и задачи — живут в единой модели с общими правами доступа и работают автономно в вашем контуре без передачи данных наружу.</p>
205
+ <div style="display:flex;gap:10px;flex-wrap:wrap">
206
+ <button type="button" class="btn btn-primary btn-sm" data-action="open-presentation" data-slide="1">${icon('docs')} Смотреть презентацию (12 слайдов)</button>
207
+ <a class="btn btn-ghost btn-sm" href="/assets/ODANT-WORK-presentation.pdf" download="ODANT-WORK-presentation.pdf" style="text-decoration:none">📥 Скачать PDF</a>
208
+ </div>
209
+ </div>
204
210
  </div>
205
211
 
206
212
  <div class="bento">
@@ -262,9 +268,10 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
262
268
  <h2 class="h2">ODANT-WORK · ${esc(P.sku)}</h2>
263
269
  <div class="price-big">${rub(P.basePriceRub)}</div>
264
270
  <p class="note">Вечная лицензия на ${P.baseSeats} одновременных рабочих сессий и ${P.includedSupportYears} год поддержки уровня Enterprise — в комплекте с сервером (или ${rub(P.baseLicensePriceRub || 4_000_000)} за базовую лицензию без оборудования при установке через <code>npm</code>).</p>
265
- <div class="hero-cta" style="margin-top:20px">
271
+ <div class="hero-cta" style="margin-top:20px;display:flex;gap:10px;flex-wrap:wrap">
266
272
  <button class="btn btn-primary" data-stage="clients">Перейти к покупке →</button>
267
273
  <button class="btn btn-ghost" data-stage="franchise">Региональная франшиза</button>
274
+ <button class="btn btn-ghost" data-action="open-presentation" data-slide="1">${icon('docs')} Презентация</button>
268
275
  </div>
269
276
  </div>
270
277
  <dl class="spec">
@@ -332,7 +339,13 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
332
339
  <span class="kicker">Тарифы поддержки и конкурентных мест</span>
333
340
  <h2 class="h2">Условия лицензирования и SLA</h2>
334
341
  </div>
335
- <p>Сразу после установки из <code>npm</code> система работает бесплатно в режиме настройки (1 сессия BOSS, ИИ-агенты отключены). Базовая постоянная лицензия открывает <b>${P.baseSeats} вечных одновременных сессий</b> и включает агентскую ИИ-систему.</p>
342
+ <div>
343
+ <p style="margin:0 0 12px">Сразу после установки из <code>npm</code> система работает бесплатно в режиме настройки (1 сессия BOSS, ИИ-агенты отключены). Базовая постоянная лицензия открывает <b>${P.baseSeats} вечных одновременных сессий</b> и включает агентскую ИИ-систему.</p>
344
+ <div style="display:flex;gap:10px;flex-wrap:wrap">
345
+ <button type="button" class="btn btn-ghost btn-sm" data-action="open-presentation" data-slide="8">${icon('docs')} Презентация для клиентов (слайд 8)</button>
346
+ <a class="btn btn-ghost btn-sm" href="/assets/ODANT-WORK-presentation.pdf" download="ODANT-WORK-presentation.pdf" style="text-decoration:none">📥 Скачать PDF</a>
347
+ </div>
348
+ </div>
336
349
  </div>
337
350
  <div class="tiers">${renderTiers()}</div>
338
351
  </section>
@@ -501,7 +514,13 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
501
514
 
502
515
  <div class="sec-head" style="margin-top:8px">
503
516
  <div><span class="kicker">Партнёрская программа</span><h2 class="h2">Условия региональной франшизы ODANT-WORK</h2></div>
504
- <p>Число франшиз в субъекте пропорционально населению, паушальный взнос — экономике региона. Резерв закрепляется сразу после невозвратного залога ${rub(FRANCHISE_PROGRAM.reservationDepositRub)}, полная оплата паушального взноса — в течение 1 года.</p>
517
+ <div>
518
+ <p style="margin:0 0 12px">Число франшиз в субъекте пропорционально населению, паушальный взнос — экономике региона. Резерв закрепляется сразу после невозвратного залога ${rub(FRANCHISE_PROGRAM.reservationDepositRub)}, полная оплата паушального взноса — в течение 1 года.</p>
519
+ <div style="display:flex;gap:10px;flex-wrap:wrap">
520
+ <button type="button" class="btn btn-ghost btn-sm" data-action="open-presentation" data-slide="10">${icon('docs')} Презентация франшизы (слайд 10)</button>
521
+ <a class="btn btn-ghost btn-sm" href="/assets/ODANT-WORK-presentation.pdf" download="ODANT-WORK-presentation.pdf" style="text-decoration:none">📥 Скачать PDF</a>
522
+ </div>
523
+ </div>
505
524
  </div>
506
525
 
507
526
  <div class="split">
@@ -548,7 +567,7 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
548
567
  <h4>${esc(VENDOR_CONTACTS.company)}</h4>
549
568
  <p>${esc(VENDOR_CONTACTS.address)}</p>
550
569
  <p>${VENDOR_CONTACTS.phones.map(esc).join(', ')}</p>
551
- <p><a href="${esc(VENDOR_CONTACTS.site)}" target="_blank" rel="noopener">${esc(VENDOR_CONTACTS.site.replace(/^https?:\/\//, ''))}</a> · <a href="#" data-action="open-oferta">Публичная оферта</a></p>
570
+ <p><a href="${esc(VENDOR_CONTACTS.site)}" target="_blank" rel="noopener">${esc(VENDOR_CONTACTS.site.replace(/^https?:\/\//, ''))}</a> · <a href="#" data-action="open-oferta">Публичная оферта</a> · <a href="#" data-action="open-presentation" data-slide="1">Презентация</a> · <a href="/assets/ODANT-WORK-presentation.pdf" download="ODANT-WORK-presentation.pdf">PDF</a></p>
552
571
  </div>
553
572
  </div>
554
573
  <div class="footer-bottom">
@@ -667,6 +686,50 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
667
686
  </div>
668
687
  </div>
669
688
 
689
+ <!-- Интерактивный просмотр презентации ODANT-WORK (для клиентов и франчайзи) -->
690
+ <div class="modal pres-modal" id="presentationModal" role="dialog" aria-modal="true" aria-labelledby="presModalTitle">
691
+ <div class="card pres-card">
692
+ <div class="pres-top">
693
+ <div class="pres-title-wrap">
694
+ <img src="/assets/odant-logo.png" alt="">
695
+ <div>
696
+ <b id="presModalTitle">Презентация ODANT-WORK</b>
697
+ <span class="small muted">Для клиентов и региональных партнёров (франчайзи)</span>
698
+ </div>
699
+ </div>
700
+ <div class="pres-sections" id="presSectionTabs">
701
+ <button type="button" class="chip on" data-action="pres-goto" data-slide="1" data-sec="product">1. О платформе (1–7)</button>
702
+ <button type="button" class="chip" data-action="pres-goto" data-slide="8" data-sec="clients">2. Клиентам (8–9)</button>
703
+ <button type="button" class="chip" data-action="pres-goto" data-slide="10" data-sec="franchise">3. Франшиза (10–12)</button>
704
+ </div>
705
+ <div class="pres-actions">
706
+ <a class="btn btn-ghost btn-sm" href="/presentation" target="_blank" rel="noopener" style="text-decoration:none" title="Открыть в отдельном окне">${icon('expand')} Во весь экран</a>
707
+ <a class="btn btn-primary btn-sm" href="/assets/ODANT-WORK-presentation.pdf" download="ODANT-WORK-presentation.pdf" style="text-decoration:none">📥 Скачать PDF</a>
708
+ <button type="button" class="icon-btn" data-action="pres-close" aria-label="Закрыть презентацию">${icon('close')}</button>
709
+ </div>
710
+ </div>
711
+
712
+ <div class="pres-stage">
713
+ <iframe id="presFrame" title="Слайды презентации ODANT-WORK" loading="lazy"></iframe>
714
+ </div>
715
+
716
+ <div class="pres-bottom">
717
+ <div style="display:flex;align-items:center;gap:8px">
718
+ <button type="button" class="btn btn-ghost btn-sm" data-action="pres-prev">← Назад</button>
719
+ <span class="pres-counter" id="presCounter">Слайд 1 / 12</span>
720
+ <button type="button" class="btn btn-ghost btn-sm" data-action="pres-next">Вперёд →</button>
721
+ </div>
722
+ <div class="pres-dots" id="presDots">
723
+ ${Array.from({ length: 12 }, (_, idx) => `<button type="button" class="pres-dot ${idx === 0 ? 'on' : ''}" data-action="pres-goto" data-slide="${idx + 1}" title="Слайд ${idx + 1}">${idx + 1}</button>`).join('')}
724
+ </div>
725
+ <div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
726
+ <button type="button" class="btn btn-ghost btn-sm" data-action="pres-jump-stage" data-target-stage="clients">К калькулятору клиентов →</button>
727
+ <button type="button" class="btn btn-ghost btn-sm" data-action="pres-jump-stage" data-target-stage="franchise">К карте франшиз РФ →</button>
728
+ </div>
729
+ </div>
730
+ </div>
731
+ </div>
732
+
670
733
  <!-- ИИ-агент лицензионного центра -->
671
734
  <button class="ai-fab" data-action="ai-toggle" aria-controls="aiDrawer"><img src="/assets/odant-logo.png" alt=""><span class="t">ИИ-агент ODANT</span><span class="live"></span></button>
672
735
  <div class="ai-drawer" id="aiDrawer" aria-live="polite">
package/src/server.js CHANGED
@@ -82,6 +82,9 @@ const STATIC_ASSETS = Object.freeze({
82
82
  '/assets/landing.css': { file: 'landing.css', type: 'text/css; charset=utf-8' },
83
83
  '/assets/landing.js': { file: 'landing.js', type: 'text/javascript; charset=utf-8' },
84
84
  '/assets/admin.js': { file: 'admin.js', type: 'text/javascript; charset=utf-8' },
85
+ '/presentation': { file: 'ODANT-WORK-presentation.html', type: 'text/html; charset=utf-8' },
86
+ '/assets/ODANT-WORK-presentation.html': { file: 'ODANT-WORK-presentation.html', type: 'text/html; charset=utf-8' },
87
+ '/assets/ODANT-WORK-presentation.pdf': { file: 'ODANT-WORK-presentation.pdf', type: 'application/pdf' },
85
88
  });
86
89
  const assetCache = new Map();
87
90
  let assetVersionCache = '';