<?php
/**
 * unpack.php
 * Builds a multi-page HTML application from the SKIDATA Cashier Manual (v5.0) content.
 * - Creates folders + files
 * - Writes a small client-side app with sidebar navigation + search + hash routing
 *
 * Usage:
 * 1) Upload this file to your web folder (e.g. /manual/)
 * 2) Visit: https://your-site/manual/unpack.php
 * 3) Then open: /manual/skidata-manual-app/index.html
 */

ini_set('display_errors', '1');
error_reporting(E_ALL);

$APP_DIR = __DIR__ . DIRECTORY_SEPARATOR . 'skidata-manual-app';

function ensure_dir($path) {
    if (!is_dir($path)) {
        if (!mkdir($path, 0755, true)) {
            throw new Exception("Failed to create directory: $path");
        }
    }
}

function write_file($path, $content) {
    $dir = dirname($path);
    ensure_dir($dir);
    if (file_put_contents($path, $content) === false) {
        throw new Exception("Failed to write file: $path");
    }
}

function h($s){ return htmlspecialchars($s, ENT_QUOTES, 'UTF-8'); }

// -------------------------------------------------------------------------------------
// App files (HTML/CSS/JS) + content pages
// -------------------------------------------------------------------------------------

$files = [];

// ---------- CSS ----------
$files['assets/css/app.css'] = <<<CSS
:root{
  --bg:#0b0f14;
  --panel:#0f1621;
  --panel2:#101b2a;
  --card:#121c2b;
  --text:#e8eef7;
  --muted:#9fb0c5;
  --accent:#46a6ff;
  --accent2:#49f5c1;
  --danger:#ff4d4d;
  --warn:#ffcc66;
  --ok:#66ff99;
  --border:rgba(255,255,255,.08);
  --shadow: 0 10px 30px rgba(0,0,0,.35);
  --radius:18px;
  --radius2:22px;
  --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
  --sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji";
}

*{box-sizing:border-box}
html,body{height:100%}
body{
  margin:0;
  font-family:var(--sans);
  background: radial-gradient(1200px 800px at 20% 0%, rgba(70,166,255,.10), transparent 60%),
              radial-gradient(900px 700px at 100% 40%, rgba(73,245,193,.08), transparent 55%),
              var(--bg);
  color:var(--text);
  overflow:hidden;
}

a{color:inherit;text-decoration:none}
small{color:var(--muted)}

.app{
  display:grid;
  grid-template-columns: 360px 1fr;
  height:100vh;
}

.sidebar{
  background: linear-gradient(180deg, rgba(255,255,255,.03), transparent 50%),
              var(--panel);
  border-right:1px solid var(--border);
  padding:18px;
  overflow:auto;
}

.brand{
  display:flex;
  gap:12px;
  align-items:center;
  padding:12px 12px;
  border:1px solid var(--border);
  border-radius: var(--radius2);
  background: rgba(255,255,255,.02);
  box-shadow: var(--shadow);
}

.logo{
  width:44px;height:44px;border-radius:14px;
  background: linear-gradient(135deg, rgba(70,166,255,.9), rgba(73,245,193,.75));
  display:grid;place-items:center;
  font-weight:800;color:#071018;
}

.brand h1{
  font-size:15px;margin:0;line-height:1.1;
}
.brand p{
  font-size:12px;margin:2px 0 0 0;color:var(--muted);
}

.controls{
  margin-top:14px;
  display:grid;
  gap:10px;
}

.search{
  width:100%;
  padding:12px 12px;
  border-radius: 16px;
  border:1px solid var(--border);
  background: rgba(255,255,255,.02);
  color:var(--text);
  outline:none;
}
.search::placeholder{color:rgba(159,176,197,.75)}

.btnrow{
  display:grid;
  grid-template-columns: 1fr 1fr;
  gap:10px;
}

.btn{
  cursor:pointer;
  padding:11px 12px;
  border-radius:16px;
  border:1px solid var(--border);
  background: rgba(255,255,255,.02);
  color:var(--text);
  transition: transform .05s ease, background .2s ease, border-color .2s ease;
  font-weight:600;
  text-align:center;
  user-select:none;
}
.btn:hover{background: rgba(255,255,255,.04); border-color: rgba(70,166,255,.35)}
.btn:active{transform: translateY(1px)}

.nav{
  margin-top:14px;
  display:flex;
  flex-direction:column;
  gap:8px;
}

.groupTitle{
  margin:14px 6px 6px;
  font-size:12px;
  color:var(--muted);
  text-transform:uppercase;
  letter-spacing:.08em;
}

.nav a{
  display:flex;
  gap:10px;
  align-items:flex-start;
  padding:10px 12px;
  border-radius: 16px;
  border:1px solid transparent;
  background: transparent;
  transition: background .2s ease, border-color .2s ease;
}
.nav a:hover{
  background: rgba(255,255,255,.03);
  border-color: rgba(255,255,255,.06);
}
.nav a.active{
  background: rgba(70,166,255,.10);
  border-color: rgba(70,166,255,.25);
}
.nav .pill{
  margin-left:auto;
  font-size:11px;
  color: rgba(232,238,247,.85);
  background: rgba(255,255,255,.05);
  border:1px solid rgba(255,255,255,.08);
  padding:3px 8px;
  border-radius: 999px;
  white-space:nowrap;
}

.main{
  overflow:auto;
  padding:22px;
}

.topbar{
  position:sticky;
  top:0;
  z-index:10;
  backdrop-filter: blur(10px);
  background: linear-gradient(180deg, rgba(11,15,20,.92), rgba(11,15,20,.70));
  border:1px solid var(--border);
  border-radius: var(--radius2);
  padding:14px 16px;
  box-shadow: var(--shadow);
  display:flex;
  gap:12px;
  align-items:center;
  justify-content:space-between;
}

.breadcrumb{
  display:flex; flex-direction:column; gap:2px;
}
.breadcrumb .title{
  font-size:16px; margin:0; font-weight:800;
}
.breadcrumb .sub{
  font-size:12px; color: var(--muted);
}

.actions{
  display:flex; gap:10px; align-items:center; flex-wrap:wrap;
}

.iconbtn{
  cursor:pointer;
  border:1px solid var(--border);
  background: rgba(255,255,255,.02);
  color:var(--text);
  padding:10px 12px;
  border-radius: 16px;
  font-weight:700;
  transition: background .2s ease, border-color .2s ease;
}
.iconbtn:hover{background: rgba(255,255,255,.04); border-color: rgba(73,245,193,.25)}
.iconbtn:active{transform: translateY(1px)}

.content{
  margin-top:16px;
  border:1px solid var(--border);
  background: rgba(255,255,255,.02);
  border-radius: var(--radius2);
  box-shadow: var(--shadow);
  padding:18px;
}

.article{
  max-width: 980px;
  margin: 0 auto;
}

.article h1{font-size:30px; margin:0 0 10px}
.article h2{font-size:22px; margin:18px 0 8px}
.article h3{font-size:18px; margin:16px 0 8px}
.article p, .article li{color: rgba(232,238,247,.92); line-height:1.55}
.article ul{margin:8px 0 14px 22px}
.article hr{border:0;border-top:1px solid var(--border); margin:16px 0}

.callout{
  border:1px solid rgba(255,255,255,.10);
  background: rgba(16,27,42,.65);
  border-radius: 18px;
  padding:12px 14px;
  margin:12px 0;
  display:flex;
  gap:10px;
  align-items:flex-start;
}
.callout .tag{
  font-size:12px;
  font-weight:900;
  letter-spacing:.06em;
  text-transform:uppercase;
  padding:4px 10px;
  border-radius:999px;
  border:1px solid rgba(255,255,255,.14);
  background: rgba(255,255,255,.04);
  white-space:nowrap;
}
.callout.danger .tag{border-color: rgba(255,77,77,.35); background: rgba(255,77,77,.10)}
.callout.notice .tag{border-color: rgba(255,204,102,.35); background: rgba(255,204,102,.10)}
.callout.hint .tag{border-color: rgba(70,166,255,.35); background: rgba(70,166,255,.10)}
.callout.task .tag{border-color: rgba(73,245,193,.35); background: rgba(73,245,193,.10)}

kbd{
  font-family: var(--mono);
  font-size: 12px;
  background: rgba(255,255,255,.06);
  border:1px solid rgba(255,255,255,.10);
  padding:2px 7px;
  border-radius: 10px;
}

.code{
  font-family: var(--mono);
  font-size: 12px;
  background: rgba(255,255,255,.05);
  border:1px solid rgba(255,255,255,.10);
  padding:10px 12px;
  border-radius: 16px;
  overflow:auto;
}

.footer{
  margin-top:14px;
  color: var(--muted);
  font-size:12px;
  text-align:center;
}

@media (max-width: 980px){
  body{overflow:auto}
  .app{grid-template-columns: 1fr; height:auto; min-height:100vh}
  .sidebar{position:sticky; top:0; z-index:20; border-right:none; border-bottom:1px solid var(--border)}
  .main{padding:14px}
}
CSS;

// ---------- JS ----------
$files['assets/js/app.js'] = <<<JS
(function(){
  const \$_ = (q, el=document) => el.querySelector(q);
  const \$_all = (q, el=document) => Array.from(el.querySelectorAll(q));

  const state = {
    pages: [],
    index: {},
    current: null,
  };

  async function loadJSON(path){
    const res = await fetch(path, {cache:'no-store'});
    if(!res.ok) throw new Error('Failed to load ' + path);
    return await res.json();
  }

  async function loadPageContent(file){
    const res = await fetch('pages/' + file, {cache:'no-store'});
    if(!res.ok) return '<div class="article"><h2>Missing page</h2><p>Could not load: '+file+'</p></div>';
    return await res.text();
  }

  function setActiveNav(id){
    \$_all('.nav a').forEach(a => a.classList.toggle('active', a.dataset.id === id));
  }

  function setBreadcrumb(title, sub){
    \$('#pageTitle').textContent = title || 'SKIDATA Cashier Manual';
    \$('#pageSub').textContent = sub || '';
    document.title = (title ? title + ' — ' : '') + 'SKIDATA Cashier Manual';
  }

  function scrollTop(){
    \$('#mainScroll').scrollTo({top:0, behavior:'smooth'});
  }

  function toggleTheme(){
    // Placeholder if you want to add a light theme later
    alert('Theme toggle is ready for future expansion 🙂');
  }

  function buildNav(pages){
    const nav = \$('#nav');
    nav.innerHTML = '';
    let lastGroup = null;

    pages.forEach(p => {
      if(p.group && p.group !== lastGroup){
        const gt = document.createElement('div');
        gt.className = 'groupTitle';
        gt.textContent = p.group;
        nav.appendChild(gt);
        lastGroup = p.group;
      }

      const a = document.createElement('a');
      a.href = '#/' + encodeURIComponent(p.id);
      a.dataset.id = p.id;

      const wrap = document.createElement('div');
      wrap.style.display = 'grid';
      wrap.style.gap = '2px';

      const t = document.createElement('div');
      t.textContent = p.title;
      t.style.fontWeight = '800';
      t.style.fontSize = '13px';

      const d = document.createElement('div');
      d.textContent = p.desc || '';
      d.style.fontSize = '12px';
      d.style.color = 'rgba(159,176,197,.92)';

      wrap.appendChild(t);
      if(p.desc) wrap.appendChild(d);

      const pill = document.createElement('div');
      pill.className = 'pill';
      pill.textContent = p.pill || '';

      a.appendChild(wrap);
      if(p.pill) a.appendChild(pill);

      nav.appendChild(a);
    });
  }

  function buildSearchIndex(pages){
    const idx = {};
    pages.forEach(p => {
      idx[p.id] = (p.title + ' ' + (p.desc||'') + ' ' + (p.keywords||[]).join(' ')).toLowerCase();
    });
    state.index = idx;
  }

  function applyFilter(text){
    const q = (text || '').trim().toLowerCase();
    \$_all('.nav a').forEach(a => {
      const id = a.dataset.id;
      const hay = state.index[id] || '';
      a.style.display = (!q || hay.includes(q)) ? '' : 'none';
    });
    // Keep group titles visible if any of their items visible
    const nav = \$('#nav');
    const children = Array.from(nav.children);
    for(let i=0;i<children.length;i++){
      const el = children[i];
      if(!el.classList.contains('groupTitle')) continue;
      // groupTitle applies to following items until next groupTitle
      let visible = false;
      for(let j=i+1;j<children.length;j++){
        if(children[j].classList.contains('groupTitle')) break;
        if(children[j].tagName === 'A' && children[j].style.display !== 'none') { visible = true; break; }
      }
      el.style.display = visible ? '' : 'none';
    }
  }

  async function route(){
    const raw = location.hash || '#/home';
    const id = decodeURIComponent(raw.replace(/^#\\//,'').trim()) || 'home';
    const page = state.pages.find(p => p.id === id) || state.pages[0];

    state.current = page.id;
    setActiveNav(page.id);
    setBreadcrumb(page.title, page.sub || page.group || '');
    const html = await loadPageContent(page.file);
    \$('#content').innerHTML = html;
    scrollTop();
    highlightCallouts();
  }

  function highlightCallouts(){
    // Enhances standard "Hint/Notice/Danger/Task" blocks if present
    \$_all('[data-callout]').forEach(el => {
      const type = el.getAttribute('data-callout') || 'hint';
      el.classList.add('callout', type);
      // auto-build tag if not present
      if(!el.querySelector('.tag')){
        const tag = document.createElement('div');
        tag.className = 'tag';
        tag.textContent = type;
        el.prepend(tag);
      }
    });
  }

  async function init(){
    const pages = await loadJSON('assets/data/pages.json');
    state.pages = pages;
    buildNav(pages);
    buildSearchIndex(pages);

    window.addEventListener('hashchange', route);

    \$('#search').addEventListener('input', (e) => applyFilter(e.target.value));
    \$('#btnTop').addEventListener('click', scrollTop);
    \$('#btnTheme').addEventListener('click', toggleTheme);

    // first route
    await route();
  }

  init().catch(err => {
    console.error(err);
    document.body.innerHTML = '<pre style="color:#fff;background:#000;padding:16px;">App failed to start:\\n'+(err && err.stack ? err.stack : err)+'</pre>';
  });
})();
JS;

// ---------- Manifest + Service worker (simple offline cache) ----------
$files['manifest.webmanifest'] = <<<JSON
{
  "name": "SKIDATA Cashier Manual",
  "short_name": "SKIDATA Manual",
  "start_url": "./index.html#/home",
  "display": "standalone",
  "background_color": "#0b0f14",
  "theme_color": "#0b0f14",
  "icons": [
    { "src": "assets/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "assets/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }
  ]
}
JSON;

$files['sw.js'] = <<<JS
const CACHE = 'skidata-manual-v1';
const ASSETS = [
  './index.html',
  './manifest.webmanifest',
  './assets/css/app.css',
  './assets/js/app.js',
  './assets/data/pages.json'
];

self.addEventListener('install', (e) => {
  e.waitUntil(
    caches.open(CACHE).then(c => c.addAll(ASSETS)).then(() => self.skipWaiting())
  );
});

self.addEventListener('activate', (e) => {
  e.waitUntil(
    caches.keys().then(keys => Promise.all(keys.map(k => k !== CACHE ? caches.delete(k) : null)))
      .then(() => self.clients.claim())
  );
});

self.addEventListener('fetch', (e) => {
  e.respondWith(
    caches.match(e.request).then(cached => cached || fetch(e.request).then(resp => {
      // Cache fetched pages too
      const copy = resp.clone();
      caches.open(CACHE).then(c => c.put(e.request, copy)).catch(()=>{});
      return resp;
    }).catch(() => cached))
  );
});
JS;

// ---------- Basic icons placeholders (simple PNGs via base64 tiny images) ----------
$files['assets/icons/icon-192.png'] = base64_decode(
  'iVBORw0KGgoAAAANSUhEUgAAAMAAAADECAQAAABW1e0BAAAAK0lEQVR42u3BAQ0AAADCoPdPbQ8HFAAAAAAAAAAAAAAAAAAAAAAAAACwG2lAAAGu1O2uAAAAAElFTkSuQmCC'
);
$files['assets/icons/icon-512.png'] = base64_decode(
  'iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAQAAAD8n+0CAAAAK0lEQVR42u3BAQ0AAADCoPdPbQ8HFAAAAAAAAAAAAAAAAAAAAAAAAADwG0mAAAGfX5e0AAAAAElFTkSuQmCC'
);

// ---------- Index HTML ----------
$files['index.html'] = <<<HTML
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" />
  <meta name="theme-color" content="#0b0f14" />
  <link rel="manifest" href="manifest.webmanifest" />
  <link rel="stylesheet" href="assets/css/app.css" />
  <title>SKIDATA Cashier Manual</title>
</head>
<body>
  <div class="app">
    <aside class="sidebar">
      <div class="brand">
        <div class="logo">SD</div>
        <div>
          <h1>SKIDATA Cashier Manual</h1>
          <p>Parking.Logic / APT450.Logic — Manual Pay Station</p>
        </div>
      </div>

      <div class="controls">
        <input id="search" class="search" placeholder="Search pages (e.g. 'shift', 'lost ticket', 'gate')…" />
        <div class="btnrow">
          <div class="btn" id="btnTop">Top</div>
          <div class="btn" id="btnTheme">Theme</div>
        </div>
      </div>

      <nav id="nav" class="nav" aria-label="Manual navigation"></nav>

      <div class="footer">
        <div>Built as a multi-page HTML app.</div>
        <div><small>© SKIDATA, Inc. (content), app wrapper generated locally.</small></div>
      </div>
    </aside>

    <main class="main" id="mainScroll">
      <div class="topbar">
        <div class="breadcrumb">
          <div class="title" id="pageTitle">Loading…</div>
          <div class="sub" id="pageSub"></div>
        </div>
        <div class="actions">
          <button class="iconbtn" onclick="location.hash='#/home'">Home</button>
          <button class="iconbtn" onclick="location.hash='#/contents'">Contents</button>
          <button class="iconbtn" onclick="location.hash='#/tasks'">Tasks</button>
          <button class="iconbtn" onclick="location.hash='#/equipment'">Equipment</button>
        </div>
      </div>

      <section class="content" id="content"></section>
    </main>
  </div>

  <script>
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('./sw.js').catch(()=>{});
    }
  </script>
  <script src="assets/js/app.js"></script>
</body>
</html>
HTML;

// ---------- Pages index ----------
$pages = [
  [
    "id"=>"home","group"=>"Start Here","title"=>"Welcome","pill"=>"v5.0",
    "desc"=>"Overview + quick links.","sub"=>"Start Here","file"=>"home.html",
    "keywords"=>["welcome","overview","manual pay station","parking.logic","apt450.logic"]
  ],
  [
    "id"=>"cover","group"=>"Start Here","title"=>"Cover","pill"=>"","desc"=>"Title + version details.","sub"=>"Start Here",
    "file"=>"cover.html","keywords"=>["cashier manual","skidata","version 5.0","04.01.2015"]
  ],
  [
    "id"=>"contents","group"=>"Start Here","title"=>"Contents","pill"=>"","desc"=>"Table of contents (jump list).","sub"=>"Start Here",
    "file"=>"contents.html","keywords"=>["contents","toc","chapters","tasks","equipment","glossary","index"]
  ],
  [
    "id"=>"about","group"=>"1 — About","title"=>"1 About this Documentation","pill"=>"1","desc"=>"Purpose, print layout, symbols.","sub"=>"Chapter 1",
    "file"=>"about.html","keywords"=>["documentation","print layout","symbols","danger","notice","hint","task"]
  ],
  [
    "id"=>"intro","group"=>"2 — Introduction","title"=>"2 Introduction","pill"=>"2","desc"=>"Welcome, basic operation, pay station screen.","sub"=>"Chapter 2",
    "file"=>"intro.html","keywords"=>["welcome","basic operation","keyboard","mouse","touch-screen","menu control","pay station screen"]
  ],
  [
    "id"=>"tasks","group"=>"3 — Tasks","title"=>"3 Tasks (Overview)","pill"=>"3","desc"=>"All cashier procedures & task map.","sub"=>"Chapter 3",
    "file"=>"tasks.html","keywords"=>["tasks","cashier procedures","shift start","shift end","tickets","validation","gate"]
  ],
  [
    "id"=>"shift-start","group"=>"3 — Tasks","title"=>"3.2 Shift Start","pill"=>"","desc"=>"Log on, breaks, relief shifts, float.","sub"=>"Chapter 3",
    "file"=>"shift-start.html","keywords"=>["shift start","log on","main shift","breaks","relief shift","float","inpayment"]
  ],
  [
    "id"=>"shift-end","group"=>"3 — Tasks","title"=>"3.3 Shift End","pill"=>"","desc"=>"Outpayment, total payment, log off.","sub"=>"Chapter 3",
    "file"=>"shift-end.html","keywords"=>["shift end","outpayment","total payment","log off","disbursement"]
  ],
  [
    "id"=>"process-ticket","group"=>"3 — Tasks","title"=>"3.4 Processing a Ticket","pill"=>"","desc"=>"Normal ticket, split payments, receipts, validation, manual CC.","sub"=>"Chapter 3",
    "file"=>"process-ticket.html","keywords"=>["processing a ticket","normal ticket","split payments","receipts","manual validation","ccman"]
  ],
  [
    "id"=>"exceptions","group"=>"3 — Tasks","title"=>"3.5 Exception Tickets","pill"=>"","desc"=>"Lost, special, damaged, special sale, credit, single exit, nil.","sub"=>"Chapter 3",
    "file"=>"exceptions.html","keywords"=>["lost ticket","special ticket","damaged ticket","special sale","credit ticket","single exit","nil payment"]
  ],
  [
    "id"=>"insufficient","group"=>"3 — Tasks","title"=>"3.6 Insufficient Funds","pill"=>"","desc"=>"Issue ISF and Pay ISF flows.","sub"=>"Chapter 3",
    "file"=>"insufficient.html","keywords"=>["insufficient funds","isisf","pyisf","iou"]
  ],
  [
    "id"=>"towed","group"=>"3 — Tasks","title"=>"3.7 Towed Vehicle","pill"=>"","desc"=>"Process tow + towed vehicle tickets.","sub"=>"Chapter 3",
    "file"=>"towed.html","keywords"=>["tow","towed vehicle","two tickets"]
  ],
  [
    "id"=>"read-ticket","group"=>"3 — Tasks","title"=>"3.8 Reading a Ticket","pill"=>"","desc"=>"RDTKT and rejection details.","sub"=>"Chapter 3",
    "file"=>"read-ticket.html","keywords"=>["read ticket","rdtkt","rejection details","ticket blocked","wrong car park"]
  ],
  [
    "id"=>"jammed","group"=>"3 — Tasks","title"=>"3.9 Clearing Jammed Tickets","pill"=>"","desc"=>"Coder jam removal steps.","sub"=>"Chapter 3",
    "file"=>"jammed.html","keywords"=>["jammed ticket","desktop coder","mechanism","green lever","switch off"]
  ],
  [
    "id"=>"manual-cashiering","group"=>"3 — Tasks","title"=>"3.10 Manual Cashiering","pill"=>"","desc"=>"MANCS flow when coder is down.","sub"=>"Chapter 3",
    "file"=>"manual-cashiering.html","keywords"=>["manual cashiering","mancs","ticket number","no receipt"]
  ],
  [
    "id"=>"gate","group"=>"3 — Tasks","title"=>"3.11 Barrier Gate Control","pill"=>"","desc"=>"Open, keep open ON/OFF.","sub"=>"Chapter 3",
    "file"=>"gate.html","keywords"=>["barrier gate","control center","manual open","keep open on","keep open off","justification"]
  ],
  [
    "id"=>"intercom","group"=>"3 — Tasks","title"=>"3.12 Intercom","pill"=>"","desc"=>"Receive and make remote calls.","sub"=>"Chapter 3",
    "file"=>"intercom.html","keywords"=>["intercom","commend","responding","making call","hand piece"]
  ],
  [
    "id"=>"equipment","group"=>"4 — Equipment","title"=>"4 Equipment (Overview)","pill"=>"4","desc"=>"Maintenance, cleaning, tickets, columns, panels.","sub"=>"Chapter 4",
    "file"=>"equipment.html","keywords"=>["equipment","maintenance","cleaning","desktop coder","parking column","transaction panel"]
  ],
  [
    "id"=>"cleaning","group"=>"4 — Equipment","title"=>"4.2 Cleaning Card Instructions","pill"=>"","desc"=>"Clean desktop coder & coder unlimited.","sub"=>"Chapter 4",
    "file"=>"cleaning.html","keywords"=>["cleaning card","denatured alcohol","coding mechanism","weekly"]
  ],
  [
    "id"=>"desktop-coder","group"=>"4 — Equipment","title"=>"4.3 Desktop Coder Tickets","pill"=>"","desc"=>"Refill receipt paper (ticket stock).","sub"=>"Chapter 4",
    "file"=>"desktop-coder.html","keywords"=>["desktop coder","receipt paper","intake 1","notch bottom left"]
  ],
  [
    "id"=>"parking-column","group"=>"4 — Equipment","title"=>"4.4 Parking Column","pill"=>"","desc"=>"Open/close, load tickets, empty bins.","sub"=>"Chapter 4",
    "file"=>"parking-column.html","keywords"=>["parking column","opening","closing","load tickets","empty ticket bin","exit column basket"]
  ],
  [
    "id"=>"transaction-panel","group"=>"4 — Equipment","title"=>"4.5 Transaction Panel","pill"=>"","desc"=>"Open/close and load/empty tickets.","sub"=>"Chapter 4",
    "file"=>"transaction-panel.html","keywords"=>["transaction panel","component drawer","ticket basket","ticket throat"]
  ],
  [
    "id"=>"jammed-column-panel","group"=>"4 — Equipment","title"=>"4.6 Clearing Jams (Column/Panel)","pill"=>"","desc"=>"Jam removal for parking column & panel.","sub"=>"Chapter 4",
    "file"=>"jammed-column-panel.html","keywords"=>["jammed ticket","parking column","transaction panel","main switch","green lever","eccentric wheel"]
  ],
  [
    "id"=>"glossary","group"=>"Reference","title"=>"5 Glossary","pill"=>"","desc"=>"Definitions of key terms.","sub"=>"Reference",
    "file"=>"glossary.html","keywords"=>["glossary","apm","mps","lpr","nil payment","outpayment"]
  ],
  [
    "id"=>"indexref","group"=>"Reference","title"=>"6 Index","pill"=>"","desc"=>"Index list from the manual.","sub"=>"Reference",
    "file"=>"index.html","keywords"=>["index","topics","screens","procedures"]
  ],
];

$files['assets/data/pages.json'] = json_encode($pages, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);

// ---------- Content Pages (HTML snippets) ----------
$files['pages/home.html'] = <<<HTML
<div class="article">
  <h1>Welcome</h1>
  <p>This is a touch-friendly, multi-page HTML application built from the <b>SKIDATA Cashier Manual (v5.0)</b> content.</p>

  <div class="callout hint" data-callout="hint">
    <div>
      <p><b>Quick tip:</b> Use the search box (left) to jump straight to things like <i>shift start</i>, <i>lost ticket</i>, <i>gate</i>, or <i>jammed ticket</i>.</p>
    </div>
  </div>

  <h2>Fast links</h2>
  <ul>
    <li><a href="#/shift-start">Shift Start</a></li>
    <li><a href="#/process-ticket">Process a Ticket</a></li>
    <li><a href="#/exceptions">Exception Tickets</a></li>
    <li><a href="#/gate">Barrier Gate Control</a></li>
    <li><a href="#/cleaning">Cleaning Card Instructions</a></li>
  </ul>

  <hr />
  <p><small>Note: The original PDF contains images/figures. This app keeps the text and structure, and includes placeholders where figures were referenced.</small></p>
</div>
HTML;

$files['pages/cover.html'] = <<<HTML
<div class="article">
  <h1>Cashier Manual</h1>
  <p><b>For Parking.Logic Systems</b></p>
  <p>Features and Functions of the Manual Pay Station</p>
  <div class="code">Author: Karen Blasing Pradhan<br/>Version: 5.0 (04.01.2015)</div>
  <div class="callout notice" data-callout="notice">
    <div>
      <p>Figure placeholders were removed from the PDF conversion. If you want the original figures embedded, add images into <code>assets/img/</code> and reference them from each page.</p>
    </div>
  </div>
</div>
HTML;

$files['pages/contents.html'] = <<<HTML
<div class="article">
  <h1>Contents</h1>
  <ul>
    <li><a href="#/about">1 About this Documentation</a></li>
    <li><a href="#/intro">2 Introduction</a></li>
    <li><a href="#/tasks">3 Tasks (Overview)</a>
      <ul>
        <li><a href="#/shift-start">3.2 Shift Start</a></li>
        <li><a href="#/shift-end">3.3 Shift End</a></li>
        <li><a href="#/process-ticket">3.4 Processing a Ticket</a></li>
        <li><a href="#/exceptions">3.5 Processing Exception Tickets</a></li>
        <li><a href="#/insufficient">3.6 Insufficient Funds Transactions</a></li>
        <li><a href="#/towed">3.7 Towed Vehicle Transactions</a></li>
        <li><a href="#/read-ticket">3.8 Reading a Ticket</a></li>
        <li><a href="#/jammed">3.9 Clearing Jammed Tickets</a></li>
        <li><a href="#/manual-cashiering">3.10 Manual Cashiering</a></li>
        <li><a href="#/gate">3.11 Barrier Gate Control</a></li>
        <li><a href="#/intercom">3.12 Intercom</a></li>
      </ul>
    </li>
    <li><a href="#/equipment">4 Equipment (Overview)</a>
      <ul>
        <li><a href="#/cleaning">4.2 Cleaning Card Instructions</a></li>
        <li><a href="#/desktop-coder">4.3 Desktop Coder Tickets</a></li>
        <li><a href="#/parking-column">4.4 Parking Column</a></li>
        <li><a href="#/transaction-panel">4.5 Transaction Panel</a></li>
        <li><a href="#/jammed-column-panel">4.6 Clearing Jammed Tickets (Column/Panel)</a></li>
      </ul>
    </li>
    <li><a href="#/glossary">5 Glossary</a></li>
    <li><a href="#/indexref">6 Index</a></li>
  </ul>
</div>
HTML;

$files['pages/about.html'] = <<<HTML
<div class="article">
  <h1>1 About this Documentation</h1>
  <p>This documentation contains <b>step-by-step instructions</b> for selected procedures required for the SKIDATA Manual Pay Station. It does not claim to be complete.</p>
  <p>The procedures described in this manual do not include troubleshooting. In case of problems, please send an accurate problem description to your SKIDATA Customer Service representative.</p>
  <p>This manual documents features and functions of the SKIDATA Manual Pay Station using the system’s language selection of <b>US English</b>.</p>

  <h2>1.1 Print Layout</h2>
  <p>For optimum printing, set your printer to <b>Color</b> and <b>Double-Sided</b> Printing.</p>

  <h2>1.2 Symbols</h2>
  <p>Important text passages and notes are marked by symbols and special typefaces throughout this Manual.</p>

  <div class="callout danger" data-callout="danger">
    <div><p><b>Danger:</b> Risk of injury.</p></div>
  </div>

  <div class="callout notice" data-callout="notice">
    <div><p><b>Notice:</b> Warns against actions that might cause hardware and/or software damage.</p></div>
  </div>

  <div class="callout hint" data-callout="hint">
    <div><p><b>Hint:</b> Provides explanations on the proper use of the device or software.</p></div>
  </div>

  <div class="callout task" data-callout="task">
    <div><p><b>Task:</b> Specifies particular tasks to be completed.</p></div>
  </div>

  <div class="callout hint" data-callout="hint">
    <div><p><b>Goal:</b> Describes the learning objectives of the chapter or section.</p></div>
  </div>

  <div class="callout hint" data-callout="hint">
    <div><p><b>Example:</b> Describes practical applications to illustrate features, functions, etc.</p></div>
  </div>
</div>
HTML;

$files['pages/intro.html'] = <<<HTML
<div class="article">
  <h1>2 Introduction</h1>

  <h2>2.1 Welcome</h2>
  <p>Welcome to the SKIDATA Parking.Logic / APT450.Logic Parking Access Revenue Control System (PARCS). This manual will introduce you to the system and guide you through the tasks needed to perform cashiering duties.</p>

  <p>The PARCS is made up of several parts. These parts could include:</p>
  <ul>
    <li>Entrance column and barrier gate to dispense tickets, read cards, and prevent unauthorized entry.</li>
    <li>Manual Pay Station where cashiering duties are performed.</li>
    <li>Keyboard and mouse for entering information into the system (optionally touch screen monitor).</li>
    <li>Desktop coder for reading tickets/credit/debit cards, printing tickets, and printing receipts.</li>
    <li>An Automatic Pay Machine if a customer does not need to go to a cashier.</li>
    <li>Exit column or Transaction Panel and barrier gate to collect payment and prevent unauthorized exit.</li>
  </ul>

  <p>All terminal devices will accept:</p>
  <ul>
    <li>Parking Entrance tickets</li>
    <li>Contract Parking Cards</li>
    <li>Discount cards and tickets</li>
    <li>Special tickets</li>
  </ul>

  <h2>2.2 Basic Operation</h2>
  <p>If you have little or no experience in the use of graphical user interface environments, this section contains a brief introduction to some basic features you should be familiar with.</p>

  <h3>2.2.1 Keyboard, Mouse and Touch-screen Controls</h3>
  <p>Most functions are accessible with the keyboard or the mouse, or from touch-screen controls if the system is equipped with a touch screen monitor.</p>

  <h3>Keyboard Controls</h3>
  <ul>
    <li><kbd>Tab</kbd> moves from one field to the next.</li>
    <li><kbd>Enter</kbd> confirms actions and inputs them into the system.</li>
    <li><kbd>Alt</kbd> accesses Function Menus at the top of the program screen.</li>
    <li>Arrow keys move within fields/windows.</li>
  </ul>

  <h3>Mouse Controls</h3>
  <ul>
    <li>Left mouse button selects on-screen items.</li>
    <li>Scroll lists using arrows/scrollbar.</li>
    <li>Some lists allow multi-select using right-click context menu.</li>
    <li>Hover over Action Pad items to see labels.</li>
  </ul>

  <h3>Touch-screen Controls</h3>
  <ul>
    <li>Touch Action Pad keys / menus with fingertip.</li>
    <li>On-screen touch keyboard may appear (Close/Tab/Backspace keys included).</li>
    <li>Enable keyboard via Options Menu → Show Keyboard.</li>
    <li>Touch-screen requires a touch monitor and R20/V3 or higher software.</li>
  </ul>

  <h3>2.2.2 Menu Control</h3>
  <p>Functions and commands are grouped in menus along the menu bar at the top of the screen. Alternatively, open a menu using <kbd>Alt</kbd> + the underlined character.</p>

  <h2>2.3 Pay Station Screen</h2>
  <p>The Pay Station screen is where you perform payment and ticketing procedures.</p>
  <div class="callout hint" data-callout="hint">
    <div>
      <p><b>Pay Station icon:</b> Open by pressing the Pay Station key (<kbd>F2</kbd> by default), selecting Pay Station from the menu, clicking the icon, or touching it on a touch system.</p>
      <p><small>Figure 2.1 placeholder (Pay Station Screen)</small></p>
    </div>
  </div>
</div>
HTML;

$files['pages/tasks.html'] = <<<HTML
<div class="article">
  <h1>3 Tasks</h1>
  <p>This chapter describes standard procedures for the Pay Station and includes the tasks below.</p>

  <h2>3.1 Cashier Procedures (Task Map)</h2>
  <ul>
    <li><b>Start my shift</b> → Log-on to the system (<a href="#/shift-start">Shift Start</a>)</li>
    <li><b>Relief shifts</b> → Start/end a relief shift (<a href="#/shift-start">Shift Start</a>)</li>
    <li><b>Enter float</b> → Enter cash float (<a href="#/shift-start">Shift Start</a>)</li>
    <li><b>End my shift</b> → Outpayment/Total Payment & log off (<a href="#/shift-end">Shift End</a>)</li>
    <li><b>Process a parking ticket</b> → Ticket + payment (<a href="#/process-ticket">Processing a Ticket</a>)</li>
    <li><b>Apply discount</b> → Validation (<a href="#/process-ticket">Manual Validation</a>)</li>
    <li><b>Manual credit card</b> → CCMAN (<a href="#/process-ticket">Manual CC Entry</a>)</li>
    <li><b>Read ticket details</b> → RDTKT (<a href="#/read-ticket">Reading a Ticket</a>)</li>
    <li><b>Lost/damaged/special tickets</b> → Exceptions (<a href="#/exceptions">Exception Tickets</a>)</li>
    <li><b>Clear jammed tickets</b> → Desktop coder jam (<a href="#/jammed">Clearing Jammed Tickets</a>)</li>
    <li><b>Manually raise barrier gate</b> → Control Center (<a href="#/gate">Barrier Gate Control</a>)</li>
  </ul>

  <div class="callout hint" data-callout="hint">
    <div>
      <p>Every parking facility is set up differently. Some options may not apply or may be unavailable (greyed out).</p>
    </div>
  </div>
</div>
HTML;

$files['pages/shift-start.html'] = <<<HTML
<div class="article">
  <h1>3.2 Shift Start</h1>
  <p>To process transactions in the PARCS, you must be logged onto the system. At the beginning of your shift, log on and enter the cash float in the drawer.</p>

  <h2>3.2.1 Logging On to a Main Shift</h2>
  <p>The Log-on screen appears when the system starts, or after Shift Closure. If it does not appear, see your supervisor.</p>

  <h3>How to Log on</h3>
  <ol>
    <li>Select your user name from the <b>Name</b> list.</li>
    <li>Enter the 4–25 digit PIN-code in <b>Password</b> (displayed as asterisks).</li>
    <li>Select <b>Language</b> if not already selected.</li>
    <li>Select <b>Operator</b> if not already selected.</li>
    <li>Complete log on: press <kbd>Enter</kbd> / select <b>OK</b>.</li>
    <li>The Pay Station screen opens, ready for the next transaction.</li>
  </ol>

  <div class="callout notice" data-callout="notice">
    <div><p>Entering an incorrect PIN code for the specified user more than three times will lock you out of the system for one hour.</p></div>
  </div>

  <h2>3.2.2 Main Shift Breaks</h2>
  <p>To take a break and lock the Pay Station, select the Lock icon (top right). The Unlock screen appears.</p>

  <h3>How to Log Off the Main Shift on Break</h3>
  <ol>
    <li>Ensure <b>Action</b> is set to “Log Off”.</li>
    <li>Press <kbd>Enter</kbd> / select <b>OK</b>.</li>
    <li>Log On screen opens for the next operator.</li>
  </ol>

  <h3>How to Unlock the Main Shift on Break</h3>
  <ol>
    <li>Set <b>Action</b> to “Unlock”.</li>
    <li>Enter PIN-code in <b>Password</b>.</li>
    <li>Press <kbd>Enter</kbd> / select <b>OK</b>.</li>
    <li>Pay Station screen opens.</li>
  </ol>

  <div class="callout hint" data-callout="hint">
    <div><p>If Administration overrides are not set up or the user lacks authorization, an error message will appear.</p></div>
  </div>

  <h2>Logging On to a Relief Shift</h2>
  <p>A Relief Cashier can temporarily log on (with a second cash drawer, if configured).</p>
  <ol>
    <li>Set <b>Action</b> to “Change of Shift”.</li>
    <li>Select your name from <b>Name</b> list.</li>
    <li>Enter PIN-code.</li>
    <li>Press <kbd>Enter</kbd> / select <b>OK</b>.</li>
    <li>Pay Station opens ready for transactions.</li>
  </ol>

  <h2>3.2.3 Entering a Shift Float</h2>
  <p>Enter the cash drawer float so totals are accurate.</p>
  <ol>
    <li>Select <b>Inpayment</b> (INPMT).</li>
    <li>Enter float amount in <b>Amount</b>.</li>
    <li>Enter comment in <b>Remarks</b> (e.g., “Float”).</li>
    <li>Choose predefined <b>Action</b> if needed.</li>
    <li>Press <kbd>Enter</kbd> / select <b>OK</b>.</li>
    <li>A receipt may print if configured.</li>
  </ol>

  <div class="callout hint" data-callout="hint">
    <div><p>Leave the Check fields blank. Any number there will change the float amount.</p></div>
  </div>
</div>
HTML;

$files['pages/shift-end.html'] = <<<HTML
<div class="article">
  <h1>3.3 Shift End</h1>
  <p>You may perform an <b>Outpayment</b> (bank deposit/vault drop) during a shift. At the end, perform a <b>Total Payment</b> and log off.</p>

  <h2>3.3.1 Performing an Outpayment</h2>
  <p>An outpayment is a disbursement of cash from the drawer.</p>
  <ol>
    <li>Select <b>Outpayment</b> from Pay Station menu.</li>
    <li>Enter Outpayment amount in <b>Amount</b>.</li>
    <li>Enter total checks amount in <b>Check Amount</b>.</li>
    <li>Enter total number of checks in <b>Checks</b>.</li>
    <li>Enter reason in <b>Remarks</b>.</li>
    <li>Select predefined <b>Action</b> if needed.</li>
    <li>Press <kbd>Enter</kbd> / select <b>OK</b>.</li>
    <li>Receipt may print if configured.</li>
  </ol>

  <h2>3.3.2 Performing a Total Payment</h2>
  <ol>
    <li>Select <b>Total Payment</b>.</li>
    <li>Confirm disburse entire cash balance (Yes).</li>
    <li>System calculates total in register.</li>
    <li>System prints transaction record for shift report (if configured).</li>
  </ol>

  <h2>3.3.3 Logging Off of a Shift</h2>
  <p>Select the Log off icon (top right).</p>
  <ul>
    <li>An Amount dialog may appear: enter amount in drawer and select OK.</li>
    <li>If Relief Shift logs off, Unlock screen appears; if Main Shift logs off, Log On screen appears.</li>
    <li>A shift report prints if configured.</li>
  </ul>
</div>
HTML;

$files['pages/process-ticket.html'] = <<<HTML
<div class="article">
  <h1>3.4 Processing a Ticket</h1>

  <p>If an exit lane has an Exit Column/Transaction Panel, the customer can use it for some transactions. <b>Cash must always be performed by the cashier.</b></p>
  <ul>
    <li>If a transaction starts at the Column/Panel, it must be completed there.</li>
    <li>If a transaction starts at the Pay Station, it must be completed at the Pay Station.</li>
    <li>If the customer can’t complete at the column (e.g., needs cash/manual validation), have them cancel and restart at Pay Station.</li>
  </ul>

  <h2>3.4.1 Normal Ticket Procedure</h2>
  <ol>
    <li>Insert ticket into desktop coder.</li>
    <li>System switches to Pay Station, calculates fee, displays amount due.</li>
    <li>Enter any discounts/validation.</li>
    <li>Accept payment (cash/credit/check).</li>
    <li>Press <kbd>Enter</kbd> to process. Ticket returns; receipt prints if needed; gate opens.</li>
  </ol>

  <h3>Ticket types</h3>
  <ul>
    <li><b>Barcode tickets:</b> barcode up, notched end toward coder.</li>
    <li><b>Magnetic strip:</b> stripe down on the right.</li>
    <li><b>KeyCard:</b> hold over yellow symbol on top of coder.</li>
  </ul>

  <h3>Validation methods</h3>
  <ul>
    <li><b>Validation card:</b> insert voucher to apply discount.</li>
    <li><b>Barcoded stamp:</b> system applies discount automatically.</li>
    <li><b>Other validation:</b> press <kbd>VALDN</kbd> and select, or use designated validation keys.</li>
  </ul>

  <h3>Payment methods</h3>
  <ul>
    <li><b>Cash:</b> press <kbd>Enter</kbd> for exact change; otherwise enter cash amount then <kbd>Enter</kbd> to show change.</li>
    <li><b>Credit card:</b> insert card into coder; if not readable, use manual CC entry.</li>
    <li><b>Check:</b> press Check key, enter amount, press <kbd>Enter</kbd> (must be exact amount).</li>
  </ul>

  <div class="callout hint" data-callout="hint">
    <div>
      <p>If using multiple payment methods, activate <b>Split Payment</b> (<kbd>SPLPA</kbd>) <i>before</i> any payment is made.</p>
    </div>
  </div>

  <h2>3.4.3 Cancelling a Transaction</h2>
  <ol>
    <li>Cancel while transaction is in process.</li>
    <li>If ticket was inserted, it will be returned.</li>
  </ol>

  <h2>3.4.4 Transaction Reversal</h2>
  <p>If transaction completed and vehicle has not yet left the lane, it can be reversed (e.g., validation requested after payment).</p>
  <ol>
    <li>Select Transaction Reversal before vehicle leaves.</li>
    <li>Confirm cancellation (Yes).</li>
    <li>Gate comes down. Re-insert ticket and process again.</li>
  </ol>
  <div class="callout hint" data-callout="hint">
    <div><p>Vehicle must still be on the arming loop to reverse a transaction.</p></div>
  </div>

  <h2>3.4.5 Split Payments</h2>
  <ol>
    <li>After inserting ticket, select Split Payment (<kbd>SPLPA</kbd>).</li>
    <li>Enter amount to charge on credit card for this portion.</li>
    <li>Select OK / press <kbd>Enter</kbd>.</li>
    <li>Insert credit card; remaining balance displays.</li>
    <li>Repeat for additional partial payments if needed.</li>
    <li>Finish remaining balance with cash (<kbd>Enter</kbd>) or credit (insert card without SPLPA icon).</li>
  </ol>

  <h2>3.4.6 Receipts</h2>
  <p>If receipt does not auto-print, you can print one (until 20 seconds after transaction) with the Receipt key. Later receipts use the Belated Receipt function (with or without ticket/card).</p>

  <h2>3.4.7 Manual Validation</h2>
  <p>Apply validation via <kbd>VALDN</kbd> screen or facility-specific validation keys.</p>

  <h2>3.4.8 Manually Entering a Credit Card</h2>
  <ol>
    <li>Open Manual Use of Credit Card screen with <kbd>CCMAN</kbd>.</li>
    <li>Enter credit card number (no dashes) and expiry date.</li>
    <li>Press <kbd>Enter</kbd> / select OK to send CC info.</li>
    <li>Pay Station updates ticket; press <kbd>Enter</kbd> again to complete.</li>
  </ol>
</div>
HTML;

$files['pages/exceptions.html'] = <<<HTML
<div class="article">
  <h1>3.5 Processing Exception Tickets</h1>
  <p>Mistakes happen — customers can lose/damage tickets. These are exception processes.</p>

  <h2>3.5.1 Replacing a Lost Ticket</h2>
  <p>If a customer loses their ticket and you cannot determine duration (e.g., via License Plate Inventory), you can charge a flat fee and print a replacement ticket.</p>
  <ol>
    <li>Create replacement ticket in Article Sale screen.</li>
    <li>Default is Lost Ticket, qty 1. Press <kbd>Enter</kbd> / OK.</li>
    <li>Accept payment.</li>
    <li>Press <kbd>Enter</kbd> / OK to produce ticket.</li>
  </ol>

  <h2>3.5.2 Special Parking Ticket</h2>
  <p>If you can verify parking duration (license plate check, itinerary, records), issue a Special Ticket [ST] (or Short-Term Parking Ticket [SPT] depending on procedures).</p>
  <ol>
    <li>Open replacement flow (<kbd>REP</kbd>), then select <kbd>ST</kbd>.</li>
    <li>Edit Time of Entry fields.</li>
    <li>Choose printing option (Manual/Roll) depending on ticket stock.</li>
    <li>Press <kbd>Enter</kbd> / OK to continue, process payment, and print.</li>
  </ol>

  <h2>3.5.3 Damaged Ticket</h2>
  <p>Damaged tickets can jam and may be unreadable. Print a new ticket using the ticket number.</p>
  <ol>
    <li>Select New Ticket.</li>
    <li>Select Short-Term Parking Ticket (<kbd>SPT</kbd>).</li>
    <li>Enter ticket number from damaged ticket and select OK.</li>
    <li>Verify time/date; press <kbd>Enter</kbd> / OK to print new ticket.</li>
    <li>Accept payment, or press ESC to return ticket.</li>
  </ol>

  <h2>3.5.4 Special Sale</h2>
  <p>Charge money for non-parking transactions (e.g., abandoned change).</p>
  <ol>
    <li>Open Article Sale via <kbd>SSALE</kbd> (or custom key).</li>
    <li>Enter amount (do not change quantity).</li>
    <li>Press <kbd>Enter</kbd> / OK and process normally.</li>
  </ol>

  <h2>3.5.5 Processing a Credit Ticket</h2>
  <p>Credit ticket is processed like normal ticket, but balance is paid out to customer.</p>

  <h2>3.5.6 Single Exit Tickets</h2>
  <p>Single exit ticket is a one-time voucher. Fee at Pay Station will be \$0.00.</p>
  <ol>
    <li>Create Single Exit ticket (<kbd>SE</kbd>) in Article Sale.</li>
    <li>Adjust qty and validity dates if needed.</li>
    <li>Press OK / <kbd>Enter</kbd>, accept payment (may be zero), then print.</li>
  </ol>

  <h2>3.5.7 Nil Payment</h2>
  <p>Allow customer to exit without paying (service technicians, security, staff, etc.). Requires explanation.</p>
  <ol>
    <li>Insert ticket to read/calculate fee.</li>
    <li>Open Nil Ticket screen.</li>
    <li>Type explanation, press OK.</li>
    <li>Amount becomes \$0.00; press <kbd>Enter</kbd> / green arrow to process and open gate.</li>
  </ol>
</div>
HTML;

$files['pages/insufficient.html'] = <<<HTML
<div class="article">
  <h1>3.6 Insufficient Funds Transactions</h1>
  <p>If a patron cannot pay all/part of the fee, your facility may allow an Insufficient Funds transaction (IOU).</p>

  <h2>Issue Insufficient Funds</h2>
  <ol>
    <li>After transaction started and amount due displayed, patron requests IOU.</li>
    <li>If patron pays part, accept that portion first.</li>
    <li>Select Issue Insufficient Funds (<b>ISISF</b>).</li>
    <li>Amount due appears; adjust if needed.</li>
    <li>Enter patron details (required fields have bold borders). Select OK.</li>
    <li>Additional fee may apply.</li>
    <li>System stores final Amount Due with receipt number.</li>
    <li>Complete transaction with green arrow / <kbd>Enter</kbd>; receipt prints with ISF info.</li>
  </ol>

  <h2>Pay Insufficient Funds</h2>
  <ol>
    <li>Patron asks to pay ISF (during another transaction or alone).</li>
    <li>Select Pay Insufficient Funds (<b>PYISF</b>).</li>
    <li>Select transaction via receipt date/number/name and select OK.</li>
    <li>Amount due displays; process as normal.</li>
  </ol>
</div>
HTML;

$files['pages/towed.html'] = <<<HTML
<div class="article">
  <h1>3.7 Towed Vehicle Transactions</h1>
  <p>Process both tickets — tow truck and towed vehicle.</p>
  <ol>
    <li>Before inserting ticket, select Towed Vehicle.</li>
    <li>Tow truck icon appears. Insert first ticket and process; gate remains down.</li>
    <li>Insert second ticket and process; after completion gate goes up and remains up until both vehicles leave the loop.</li>
  </ol>
</div>
HTML;

$files['pages/read-ticket.html'] = <<<HTML
<div class="article">
  <h1>3.8 Reading a Ticket</h1>
  <p>Read ticket/card info without processing it.</p>

  <ol>
    <li>Activate Read Ticket (<b>RDTKT</b>) <i>before</i> inserting ticket.</li>
    <li>Insert ticket/card into desktop coder when prompted.</li>
    <li>System displays Read Ticket screen.</li>
    <li>Scroll through to read details; optionally print details.</li>
  </ol>

  <h2>Rejection Details (examples)</h2>
  <ul>
    <li><b>Ticket invalid</b> — once used at Entrance/Exit, not valid there anymore.</li>
    <li><b>Ticket blocked</b> — customer must contact operator to reinstate card.</li>
    <li><b>Wrong car park</b> — card from another facility.</li>
  </ul>
</div>
HTML;

$files['pages/jammed.html'] = <<<HTML
<div class="article">
  <h1>3.9 Clearing Jammed Tickets</h1>
  <p>Tickets can jam inside the desktop coder.</p>

  <h2>How to Remove a Jammed Ticket</h2>
  <ol>
    <li>Switch off desktop coder using black switch on left side.</li>
    <li>Turn coder on again to cycle mechanism (may clear obstruction).</li>
    <li>If not cleared: turn off again, unlock cover, press button on lower right front, slide cover toward you to remove.</li>
    <li>Lift green lever (front, right side) to open front section; remove obstructions; close and snap lever back.</li>
    <li>Lift green locking lever across top of center section; remove obstructions; close carefully (don’t disturb Eccentric Wheel).</li>
    <li>Lift/remove green upper & lower cover flaps at rear; remove obstructions; replace flaps.</li>
    <li>Replace cover, lock it, turn coder on. If still jammed, call service personnel.</li>
    <li>Wait for “Coder Connection Restored” message; dismiss with <kbd>Enter</kbd> or X.</li>
  </ol>

  <h2>Ways to Jam / Ways NOT to Jam</h2>
  <ul>
    <li>Don’t insert ticket while it is printing a receipt — wait for receipt to finish.</li>
    <li>Don’t insert damp/wet/wrinkled/torn tickets — use Damaged Ticket procedure.</li>
    <li>Clean dust from optic sensors using cleaning card.</li>
    <li>Report worn/damaged equipment; don’t use sharp instruments to remove jammed tickets.</li>
    <li>If system locks up, do not insert tickets.</li>
  </ul>
</div>
HTML;

$files['pages/manual-cashiering.html'] = <<<HTML
<div class="article">
  <h1>3.10 Manual Cashiering</h1>
  <p>Manual cashiering lets you process tickets when the desktop coder is jammed or disconnected.</p>

  <ol>
    <li>Open Manual Cashiering screen (<b>MANCS</b>).</li>
    <li>Enter last six digits of <b>Ticket Number</b>, press <kbd>Enter</kbd> / OK.</li>
    <li>Select the transaction from the list (ticket can have multiple transactions).</li>
    <li>Amount displays in main window; process like normal ticket.</li>
  </ol>

  <div class="callout hint" data-callout="hint">
    <div><p>No receipt can be printed for a Manual Cashiering transaction.</p></div>
  </div>
</div>
HTML;

$files['pages/gate.html'] = <<<HTML
<div class="article">
  <h1>3.11 Barrier Gate Control</h1>
  <p>Open or lock barrier gates without processing a ticket (when required).</p>

  <h2>3.11.1 Opening the Barrier Gate</h2>
  <ol>
    <li>Open Control Center (keyboard key or on-screen icon).</li>
    <li>Select the gate icon you wish to open.</li>
    <li>Use Manual Open (e.g., <kbd>F5</kbd> or Manual Open icon).</li>
    <li>If explanation required: enter reason, select gate device, confirm OK / <kbd>Enter</kbd>.</li>
    <li>Gate opens.</li>
  </ol>

  <div class="callout hint" data-callout="hint">
    <div><p>If Justification is enabled, you must enter an explanation before completing the function.</p></div>
  </div>

  <h2>3.11.2 Locking the Barrier Gate Open</h2>
  <ol>
    <li>Open Control Center.</li>
    <li>Select gate icon.</li>
    <li>Use Keep Open ON (e.g., <kbd>F6</kbd> or icon).</li>
    <li>If prompted, enter explanation and confirm.</li>
    <li>Gate remains open until Keep Open OFF is used.</li>
  </ol>

  <h2>3.11.3 Unlocking (Closing) the Barrier Gate</h2>
  <ol>
    <li>Open Control Center.</li>
    <li>Select gate icon.</li>
    <li>Use Keep Open OFF (e.g., <kbd>F7</kbd> or icon).</li>
    <li>If prompted, enter explanation and confirm.</li>
    <li>Gate closes and returns to normal functionality.</li>
  </ol>
</div>
HTML;

$files['pages/intercom.html'] = <<<HTML
<div class="article">
  <h1>3.12 Intercom</h1>
  <p>Entrance/Exit columns and Automatic Pay station may use a Commend intercom system (your site may differ).</p>

  <h2>3.12.1 Receive Remote Intercom Call</h2>
  <ol>
    <li>When you hear the customer speaking, wait until they have finished.</li>
    <li>Press <b>T</b> on the hand piece and speak (hold while speaking).</li>
    <li>Release <b>T</b> to listen.</li>
    <li>End communication by pressing <b>X</b>.</li>
  </ol>

  <h2>3.12.2 Make Remote Intercom Call</h2>
  <ol>
    <li>If multiple locations, press the button address for the location; otherwise use <b>T</b> to talk.</li>
    <li>Press <b>T</b> while talking.</li>
    <li>Release <b>T</b> to listen.</li>
    <li>Press <b>X</b> to end.</li>
  </ol>
</div>
HTML;

$files['pages/equipment.html'] = <<<HTML
<div class="article">
  <h1>4 Equipment</h1>
  <p>This section describes how to perform standard maintenance on the parking facility equipment.</p>

  <ul>
    <li><b>Clean the ticket/card reader</b> → Cleaning Card Instructions</li>
    <li><b>Refill receipt paper</b> → Desktop Coder</li>
    <li><b>Refill tickets in parking column</b> → Loading Tickets/Receipts into the Parking Column</li>
    <li><b>Empty tickets from parking column</b> → Emptying Parking Column Tickets</li>
    <li><b>Remove jammed tickets (column)</b> → Clearing Jammed Tickets</li>
  </ul>

  <div class="callout hint" data-callout="hint">
    <div><p>Use the left navigation to jump to each equipment task.</p></div>
  </div>
</div>
HTML;

$files['pages/cleaning.html'] = <<<HTML
<div class="article">
  <h1>4.2 Cleaning Card Instructions</h1>

  <ul>
    <li>Coding mechanisms are sensitive to dust/dirt; regular cleaning prevents hardware failures.</li>
    <li>Clean once every week (or more often depending on dirt/frequency).</li>
    <li>Use the correct cleaning kit for your coder mechanisms.</li>
  </ul>

  <h2>How to Clean the Desktop Coder</h2>
  <ol>
    <li>Pour small amount of cleaning fluid on both sides of cleaning card; shake off excess (damp, not dripping).</li>
    <li>On Pay Station screen, select Cleaning Mechanism from Function menu.</li>
    <li>Insert cleaning card; it moves back and forth several times (~45 seconds).</li>
    <li>When card ejects, cycle is finished.</li>
  </ol>

  <h2>How to Clean the Coder Unlimited (columns, panels, APM)</h2>
  <ol>
    <li>Open machine and remove any tickets. If APM, log in and close door. For column/panel, open as per procedures and insert initialization card or push top button.</li>
    <li>Dampen cleaning card (both sides), not dripping.</li>
    <li>Insert cleaning card; it will be drawn back in several times (~2.5 minutes).</li>
    <li>When done, remove card and restore machine to original state.</li>
  </ol>

  <div class="callout hint" data-callout="hint">
    <div><p>If very dirty, repeat using reverse side of card. Use each side only once.</p></div>
  </div>
</div>
HTML;

$files['pages/desktop-coder.html'] = <<<HTML
<div class="article">
  <h1>4.3 Desktop Coder Tickets</h1>
  <p>The Desktop Coder uses blank ticket stock for receipt paper. If printing receipts, you must reload stock.</p>

  <h2>Refill the Receipt Paper</h2>
  <ol>
    <li>At bottom are two intake slots. Rear intake (Intake 1) is used for receipts.</li>
    <li>Insert ticket stock so notch is on bottom left as seen from front. Magnetic stripe (if present) will be on bottom. Tickets draw in automatically.</li>
  </ol>
</div>
HTML;

$files['pages/parking-column.html'] = <<<HTML
<div class="article">
  <h1>4.4 Parking Column</h1>

  <h2>4.4.1 Opening/Closing the Parking Column</h2>
  <h3>How to open</h3>
  <ol>
    <li>Unlock and open the column front door.</li>
    <li>Locate the <i>long</i> locking lever on right side.</li>
    <li>Pull locking lever forward.</li>
    <li>Pull top panel open.</li>
    <li>Lock top section open using prop-up mechanism.</li>
    <li>Pull the <i>short</i> locking lever to open column front panel.</li>
  </ol>

  <h3>How to close</h3>
  <ol>
    <li>Close front panel first, ensure it locks.</li>
    <li>Unlock top cover by pulling up prop-up; close top cover ensuring lips overlap correctly.</li>
    <li>Ensure top and front panel lock into place.</li>
    <li>Lock front door.</li>
  </ol>

  <h2>4.4.2 Loading Tickets/Receipts into the Parking Column</h2>
  <ol>
    <li>Open front door.</li>
    <li>Remove remaining fanfold stock from intake(s) and empty containers (don’t drag across shortage sensor).</li>
    <li>Tear off perforated bottom section on new container and clear it.</li>
    <li>Remove top cover completely.</li>
    <li>Insert new container printable side up (avoid dragging across shortage sensor).</li>
    <li>Load first ticket into rear intake: hold notch down and left; tickets draw in.</li>
    <li>Repeat for second intake if used.</li>
    <li>Close and lock door.</li>
  </ol>

  <div class="callout notice" data-callout="notice">
    <div><p>Do not drag the ticket box across the ticket shortage sensor on the floor panel — it may damage the sensor.</p></div>
  </div>

  <h2>Emptying Parking Column Tickets</h2>
  <h3>Entry column ticket bin</h3>
  <ol>
    <li>Open front door.</li>
    <li>Pull out ticket bin downward.</li>
    <li>Remove tickets.</li>
    <li>Reinstall bin into guide rails and push up until it clicks.</li>
    <li>Close front door.</li>
  </ol>

  <h3>Exit column ticket bin</h3>
  <ol>
    <li>Open front door.</li>
    <li>Remove ticket bin.</li>
    <li>Empty contents.</li>
    <li>Reinstall bin.</li>
    <li>Close door.</li>
  </ol>
</div>
HTML;

$files['pages/transaction-panel.html'] = <<<HTML
<div class="article">
  <h1>4.5 Transaction Panel</h1>
  <p>The Transaction Panel is similar to a Parking Column but mounted into the booth wall.</p>

  <h2>4.5.1 Opening/Closing the Transaction Panel</h2>
  <h3>How to open</h3>
  <ol>
    <li>Unlock rear door (inside booth).</li>
    <li>Press down latch inside panel (right side) to open front panel.</li>
    <li>Pull both pull rings inward to open component drawer.</li>
    <li>From front, open panel and slide drawer out to access coder.</li>
  </ol>

  <h3>How to close</h3>
  <ol>
    <li>Close front panel and ensure lock latches.</li>
    <li>Ensure coder mechanism aligns with ticket throat; push coder plate down until it snaps.</li>
    <li>Slide drawer closed until it latches.</li>
    <li>Lock rear door.</li>
  </ol>

  <h2>Load/Empty tickets</h2>
  <ol>
    <li>Unlock ticket basket under panel; slide out to empty.</li>
    <li>Slide ticket stock box toward rear to remove.</li>
    <li>Remove unused tickets.</li>
    <li>Place new ticket box inside stock box.</li>
    <li>Load tickets into coder through ticket throat under unit.</li>
    <li>Reinstall stock box by aligning tabs/notches, slide basket forward.</li>
    <li>Install used ticket basket by aligning tabs, slide to right.</li>
    <li>Lock used ticket basket in place.</li>
  </ol>
</div>
HTML;

$files['pages/jammed-column-panel.html'] = <<<HTML
<div class="article">
  <h1>4.6 Clearing Jammed Tickets (Column & Transaction Panel)</h1>
  <p>Tickets can jam inside the coder mechanism of columns/panels.</p>

  <ol>
    <li>Open front door and switch off unit using black switch (right side inside machine).</li>
    <li>Turn unit on again to cycle (may clear obstruction).</li>
    <li>If not: turn off again.</li>
    <li>Open access panels (column: front door/top cover/front panel; panel: front panel/component drawer).</li>
    <li>Lift green lever (front, right) to open mechanism front section; remove obstructions; close and snap lever.</li>
    <li>Lift green locking lever across top of center section; remove obstructions; close carefully (don’t disturb Eccentric Wheel).</li>
    <li>Lift/remove green upper and lower cover flaps at rear; remove obstructions; replace flaps.</li>
    <li>Turn unit on. If still jammed, call service personnel. Control Center error will clear after unit is recognized.</li>
  </ol>
</div>
HTML;

$files['pages/glossary.html'] = <<<HTML
<div class="article">
  <h1>5 Glossary</h1>
  <ul>
    <li><b>Abandoned charge</b>: See Special Sale</li>
    <li><b>APM</b>: Automatic Payment Machine</li>
    <li><b>Automatic Payment Machine</b>: Central machine for patrons to pay before returning to vehicle</li>
    <li><b>Exception ticket</b>: Ticket not processed in usual way (lost/damaged/special/single exit)</li>
    <li><b>Exit grace period</b>: Time allowed after paying before using exit lane</li>
    <li><b>Insufficient funds transaction</b>: IOU process (Issue ISF and Pay ISF)</li>
    <li><b>License Plate Recognition (LPR)</b>: Identifies vehicles by plates; can help establish lost ticket charge</li>
    <li><b>Main shift</b>: Cashier’s scheduled shift</li>
    <li><b>MPS</b>: Manual Pay Station</li>
    <li><b>Nil payment</b>: Allow exit without paying (with explanation)</li>
    <li><b>Outpayment</b>: Disbursement of cash from drawer (e.g., deposit, refill APM)</li>
    <li><b>Relief shift</b>: Worked in place of cashier on break/absent</li>
    <li><b>Shift float</b>: Cash in register at shift start; required for balancing</li>
    <li><b>Short Term Parking Ticket (SPT)</b>: Normal entrance ticket</li>
    <li><b>Special parking ticket</b>: Issued when ticket lost but duration can be verified</li>
    <li><b>Special Sale</b>: Non-parking transaction (e.g., abandoned change)</li>
    <li><b>Total payment</b>: Complete disbursement at end of shift</li>
    <li><b>Validation ticket</b>: Applies discount; can be created by cashier manager</li>
  </ul>
</div>
HTML;

$files['pages/index.html'] = <<<HTML
<div class="article">
  <h1>6 Index</h1>
  <p>This is a direct text carry-over of the manual’s index (abridged formatting, same terms).</p>
  <div class="code">
Amount paid screen — 24<br/>
applying a discount (with custom key) — 27<br/>
Article Sale (ABC) screen — 34<br/>
Article Sale screen — 31, 36<br/>
barrier gate (closing/opening/keep open) — 47–50<br/>
belated receipt — 25<br/>
cancel transaction — 23<br/>
clean coder / cleaning card — 53<br/>
clear jammed ticket — 41, 63<br/>
desktop coder (refill receipt paper) — 54<br/>
discounting a parking fee — 26<br/>
entering shift float amount — 16<br/>
insufficient funds — 37–38<br/>
intercom (responding/making) — 51<br/>
logging on (main/relief) — 8, 14<br/>
manual cashiering — 45–46<br/>
manual validation — 26–27<br/>
manually enter credit card — 28<br/>
New Ticket screen — 32<br/>
nil payment — 29<br/>
opening/closing parking column — 55–57<br/>
opening/closing transaction panel — 61–62<br/>
outpayment — 18–19<br/>
processing a ticket — 21–24<br/>
read ticket / rejection details — 39–40<br/>
single exit ticket — 34–35<br/>
special ticket / replacement ticket — 30<br/>
split payments — 24<br/>
total payment — 20<br/>
towed vehicle — 38<br/>
  </div>
</div>
HTML;

// -------------------------------------------------------------------------------------
// Write all files
// -------------------------------------------------------------------------------------
try {
    // Ensure base folders
    ensure_dir($APP_DIR);
    ensure_dir($APP_DIR . '/assets/css');
    ensure_dir($APP_DIR . '/assets/js');
    ensure_dir($APP_DIR . '/assets/data');
    ensure_dir($APP_DIR . '/assets/icons');
    ensure_dir($APP_DIR . '/pages');

    foreach ($files as $rel => $content) {
        $full = $APP_DIR . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $rel);

        // Binary vs text detection: if content is not a string, skip
        if (is_string($content)) {
            write_file($full, $content);
        } else {
            // assume binary
            ensure_dir(dirname($full));
            if (file_put_contents($full, $content) === false) {
                throw new Exception("Failed to write binary file: $full");
            }
        }
    }

    // Done output
    $baseUrl = rtrim((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . dirname($_SERVER['REQUEST_URI']), '/\\');
    $appUrl = $baseUrl . '/skidata-manual-app/index.html';

    header('Content-Type: text/html; charset=utf-8');
    echo "<!doctype html><meta charset='utf-8'><title>Unpacked</title>";
    echo "<style>body{font-family:system-ui,Segoe UI,Arial;margin:24px}a{font-weight:700}</style>";
    echo "<h2>✅ SKIDATA manual app created</h2>";
    echo "<p>Open: <a href='".h($appUrl)."'>".h($appUrl)."</a></p>";
    echo "<p>Folder: <code>".h($APP_DIR)."</code></p>";
    echo "<p>You can delete <code>unpack.php</code> after confirming it works.</p>";

} catch (Exception $e) {
    http_response_code(500);
    header('Content-Type: text/plain; charset=utf-8');
    echo "ERROR: " . $e->getMessage() . "\n";
    echo "TIP: Check folder permissions for: " . $APP_DIR . "\n";
}