<?php
/**
 * installer.php
 * Creates:
 *  - index.html
 *  - api.php
 *  - /data folder + json files (main/general/security/parking/admin/personal)
 *
 * Upload installer.php to your folder, open it in browser once, click Install.
 * It will overwrite existing files (with backups).
 */

declare(strict_types=1);

$root = __DIR__;
$dataDir = $root . DIRECTORY_SEPARATOR . 'data';

$files = [
  'index.html' => $root . DIRECTORY_SEPARATOR . 'index.html',
  'api.php'    => $root . DIRECTORY_SEPARATOR . 'api.php',
];

$jsonFiles = [
  'main.json',
  'general.json',
  'security.json',
  'parking.json',
  'admin.json',
  'personal.json',
];

function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }

function backup_file(string $path): void {
  if (is_file($path)) {
    @copy($path, $path . '.bak-' . date('Ymd-His'));
  }
}

function write_file(string $path, string $content): bool {
  backup_file($path);
  return file_put_contents($path, $content, LOCK_EX) !== false;
}

function write_json(string $path, array $data): bool {
  backup_file($path);
  $json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
  if ($json === false) return false;
  $json .= "\n";
  return file_put_contents($path, $json, LOCK_EX) !== false;
}

/* ---------------------------
   GENERATED api.php
---------------------------- */
$apiPhp = <<<'PHP'
<?php
declare(strict_types=1);

/**
 * api.php
 * GET  ?file=main.json
 * POST JSON: {"action":"save","file":"main.json","data":{...}}
 *
 * Returns JSON.
 */

header('Content-Type: application/json; charset=utf-8');

$root = __DIR__;
$dataDir = $root . DIRECTORY_SEPARATOR . 'data';

$allowed = [
  'main.json'     => 'Main',
  'general.json'  => 'General',
  'security.json' => 'Security',
  'parking.json'  => 'Parking',
  'admin.json'    => 'Admin',
  'personal.json' => 'Personal',
];

function respond($ok, $payload=[], $code=200){
  http_response_code($code);
  echo json_encode(array_merge(['ok'=>$ok], $payload), JSON_UNESCAPED_SLASHES);
  exit;
}

function safe_file(string $file, array $allowed): string {
  $file = basename($file);
  if (!isset($allowed[$file])) respond(false, ['error'=>'File not allowed'], 403);
  return $file;
}

function backup_file(string $path): void {
  if (is_file($path)) @copy($path, $path . '.bak-' . date('Ymd-His'));
}

function normalize_payload(array $data): array {
  // Ensure structure: {"grid":{"rows":..,"cols":..}, "apps":[...]}
  if (!isset($data['grid']) || !is_array($data['grid'])) $data['grid'] = [];
  $rows = (int)($data['grid']['rows'] ?? 0);
  $cols = (int)($data['grid']['cols'] ?? 0);
  if ($rows < 1) $rows = 1;
  if ($cols < 1) $cols = 4;
  if ($rows > 20) $rows = 20;
  if ($cols > 20) $cols = 20;
  $data['grid'] = ['rows'=>$rows, 'cols'=>$cols];

  if (!isset($data['apps']) || !is_array($data['apps'])) $data['apps'] = [];
  $apps = [];
  foreach ($data['apps'] as $a){
    if (!is_array($a)) continue;
    $apps[] = [
      'id'     => trim((string)($a['id'] ?? '')),
      'name'   => trim((string)($a['name'] ?? '')),
      'url'    => trim((string)($a['url'] ?? '')),
      'icon'   => trim((string)($a['icon'] ?? '')),
      'tint'   => trim((string)($a['tint'] ?? '')),
      'hint'   => trim((string)($a['hint'] ?? '')),
      'target' => in_array(($a['target'] ?? '_blank'), ['_blank','_self','_top','_parent'], true) ? $a['target'] : '_blank',
    ];
  }
  $data['apps'] = $apps;
  return $data;
}

if (!is_dir($dataDir)) @mkdir($dataDir, 0755, true);

$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';

if ($method === 'GET') {
  $file = safe_file((string)($_GET['file'] ?? 'main.json'), $allowed);
  $path = $dataDir . DIRECTORY_SEPARATOR . $file;

  if (!is_file($path)) {
    // default if missing
    respond(true, ['file'=>$file, 'data'=>['grid'=>['rows'=>1,'cols'=>4], 'apps'=>[]]]);
  }
  $raw = file_get_contents($path);
  if ($raw === false) respond(false, ['error'=>'Failed to read file'], 500);

  $data = json_decode($raw, true);
  if (!is_array($data)) $data = ['grid'=>['rows'=>1,'cols'=>4], 'apps'=>[]];
  $data = normalize_payload($data);
  respond(true, ['file'=>$file, 'data'=>$data]);
}

if ($method === 'POST') {
  $raw = file_get_contents('php://input');
  $body = json_decode($raw ?: '', true);
  if (!is_array($body)) $body = $_POST;

  $action = (string)($body['action'] ?? '');
  if ($action !== 'save') respond(false, ['error'=>'Invalid action'], 400);

  $file = safe_file((string)($body['file'] ?? ''), $allowed);
  $path = $dataDir . DIRECTORY_SEPARATOR . $file;

  $data = $body['data'] ?? null;
  if (!is_array($data)) respond(false, ['error'=>'Invalid data'], 400);

  $data = normalize_payload($data);

  backup_file($path);
  $json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
  if ($json === false) respond(false, ['error'=>'JSON encode failed'], 500);

  if (file_put_contents($path, $json . "\n", LOCK_EX) === false) {
    respond(false, ['error'=>'Write failed (permissions?)'], 500);
  }
  respond(true, ['file'=>$file, 'data'=>$data]);
}

respond(false, ['error'=>'Method not allowed'], 405);
PHP;

/* ---------------------------
   GENERATED index.html
---------------------------- */
$indexHtml = <<<'HTML'
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" />
  <title>Work Desktop</title>
  <style>
    :root{
      --bg1:#1b1e24; --bg2:#0e1014;
      --stroke: rgba(255,255,255,.12);
      --shadow: 0 18px 40px rgba(0,0,0,.45);
      --shadow2: 0 10px 22px rgba(0,0,0,.35);
      --radius: 14px;
      --pad: 10px;
      --gap: 10px;
      --icon: 68px;          /* large icon size */
      --font: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
      --editbarH: 44px;
    }

    html,body{height:100%; margin:0; font-family:var(--font); color:#e9eef7; background:
      radial-gradient(1200px 700px at 30% 10%, #2c3342, transparent 60%),
      radial-gradient(900px 600px at 85% 15%, #243b55, transparent 55%),
      linear-gradient(145deg, var(--bg1), var(--bg2));
      overflow:hidden;
    }
    *{box-sizing:border-box;}
    body::before{
      content:"";
      position:fixed; inset:0;
      background:
        linear-gradient(rgba(255,255,255,.06), rgba(255,255,255,.03)),
        repeating-linear-gradient(0deg, rgba(255,255,255,.03) 0, rgba(255,255,255,.03) 2px, transparent 2px, transparent 90px),
        repeating-linear-gradient(90deg, rgba(255,255,255,.02) 0, rgba(255,255,255,.02) 2px, transparent 2px, transparent 120px);
      mix-blend-mode: overlay;
      opacity:.35;
      pointer-events:none;
    }

    /* top edit bar */
    .editbar{
      position:fixed; top:12px; right:12px; z-index:50;
      display:flex; align-items:center; gap:10px;
      padding:8px 10px;
      background: rgba(0,0,0,.35);
      border:1px solid rgba(255,255,255,.14);
      border-radius: 14px;
      box-shadow: var(--shadow2);
      backdrop-filter: blur(6px);
    }
    .editbar .tag{
      font-size:12px; opacity:.8; padding:4px 8px;
      border-radius: 10px;
      border:1px solid rgba(255,255,255,.12);
      background: rgba(255,255,255,.06);
      white-space:nowrap;
    }
    .btn-ico{
      width:36px;height:36px;border-radius:12px;
      border:1px solid rgba(255,255,255,.18);
      background: rgba(255,255,255,.10);
      color:#e9eef7;
      display:grid; place-items:center;
      cursor:pointer;
      touch-action: manipulation;
      user-select:none;
    }
    .btn-ico:active{ transform: translateY(1px); }
    .btn-ico.on{ background: rgba(125,255,178,.12); border-color: rgba(125,255,178,.35); }

    /* layout */
    .wrap{
      height:100%;
      display:grid;
      grid-template-rows:auto 1fr;
      gap:12px;
      padding:16px;
    }

    /* MAIN launcher (top) */
    .launcher{
      max-width: 1100px;
      margin: 0 auto;
      width: 100%;
    }

    .main{
      display:grid;
      grid-template-columns: 1.5fr 1fr;
      gap:12px;
      min-height:0;
    }

    .left-col{
      display:grid;
      grid-template-rows: 1fr auto;
      gap:12px;
      min-height:0;
    }

    .right-col{
      display:grid;
      grid-template-rows: auto auto 1fr;
      gap:12px;
      min-height:0;
    }

    .bottom-row{
      display:grid;
      grid-template-columns: 1fr 1fr;
      gap:12px;
    }

    /* window/panel */
    .win{
      position:relative;
      border-radius: var(--radius);
      border: 1px solid var(--stroke);
      background: linear-gradient(180deg, rgba(255,255,255,.09), rgba(255,255,255,.04));
      box-shadow: var(--shadow2);
      overflow:hidden;
      min-height:0;
    }

    .titlebar{
      display:flex;
      align-items:center;
      justify-content:space-between;
      gap:10px;
      padding:8px 10px;
      font-weight:800;
      letter-spacing:.2px;
      text-shadow: 0 1px 0 rgba(0,0,0,.55);
      border-bottom: 1px solid rgba(0,0,0,.35);
      user-select:none;
    }
    .titlebar .left{
      display:flex; align-items:center; gap:10px; min-width:0;
    }
    .badge{
      width:18px; height:18px; border-radius:4px;
      box-shadow: inset 0 1px 0 rgba(255,255,255,.35), 0 3px 10px rgba(0,0,0,.35);
      border:1px solid rgba(255,255,255,.18);
      flex:0 0 auto;
    }
    .title{ white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
    .titlebar .right{
      display:flex; align-items:center; gap:8px; flex:0 0 auto;
    }
    .mini-ico{
      width:22px; height:22px; border-radius:8px;
      border:1px solid rgba(255,255,255,.16);
      background: rgba(0,0,0,.20);
      display:grid; place-items:center;
      opacity:.95;
      cursor:pointer;
      user-select:none;
    }
    .mini-ico:hover{ border-color: rgba(255,255,255,.28); }
    .mini-ico.hidden{ display:none; }

    .content{
      padding: var(--pad);
      min-height:0;
      height:100%;
      overflow:auto;
    }

    /* app grid - driven by rows/cols */
    .icons{
      display:grid;
      gap: var(--gap);
      align-items:start;
    }
    .icons.dock{
      grid-auto-flow: column;
      grid-auto-columns: minmax(86px, 1fr);
      overflow-x:auto;
      padding-bottom: 6px;
      scrollbar-width: thin;
    }

    .app{
      position:relative;
      display:flex;
      flex-direction:column;
      align-items:center;
      justify-content:flex-start;
      gap:8px;
      padding:9px 8px;
      border-radius: 14px;
      border: 1px solid rgba(255,255,255,.12);
      background: rgba(0,0,0,.22);
      box-shadow: inset 0 1px 0 rgba(255,255,255,.12);
      text-decoration:none;
      color:inherit;
      min-height: calc(var(--icon) + 40px);
      touch-action: manipulation;
      -webkit-tap-highlight-color: transparent;
      user-select:none;
    }
    .app:active{ transform: translateY(1px); }
    .app:hover{ border-color: rgba(255,255,255,.22); }

    .app .ico{
      width: var(--icon);
      height: var(--icon);
      border-radius: 16px;
      display:grid;
      place-items:center;
      font-weight:900;
      font-size: 22px;
      background: linear-gradient(180deg, rgba(255,255,255,.22), rgba(255,255,255,.06));
      border: 1px solid rgba(255,255,255,.18);
      box-shadow: 0 10px 18px rgba(0,0,0,.45), inset 0 1px 0 rgba(255,255,255,.35);
      overflow:hidden;
    }
    .app img{ width:100%; height:100%; object-fit:cover; display:block; }
    .app .lbl{
      font-size: 12px;
      text-align:center;
      line-height:1.15;
      opacity:.95;
      word-break:break-word;
    }

    /* edit overlays on apps */
    .edit-mode .app{ cursor:grab; }
    .edit-mode .app:active{ cursor:grabbing; }
    .app .edit-chip{
      position:absolute; top:8px; right:8px;
      width:22px; height:22px; border-radius:8px;
      border:1px solid rgba(255,255,255,.20);
      background: rgba(0,0,0,.35);
      display:none;
      place-items:center;
      font-size:12px;
    }
    .edit-mode .app .edit-chip{ display:grid; }
    .app .drag-handle{
      position:absolute; top:8px; left:8px;
      width:22px; height:22px; border-radius:8px;
      border:1px solid rgba(255,255,255,.18);
      background: rgba(0,0,0,.28);
      display:none;
      place-items:center;
      font-size:12px;
      opacity:.95;
    }
    .edit-mode .app .drag-handle{ display:grid; }

    /* themes */
    .theme-general .titlebar{ background: linear-gradient(180deg, #d5d8dd, #aeb3ba); color:#0c0f14; text-shadow:none; }
    .theme-general .badge{ background: linear-gradient(180deg, #2f69ff, #1b3aa9); }

    .theme-security .titlebar{ background: linear-gradient(180deg, #2a71ff, #0d3fb6); }
    .theme-security .badge{ background: linear-gradient(180deg, #ffcb3a, #b87400); }

    .theme-parking .titlebar{ background: linear-gradient(180deg, #1bd66b, #0b7a3b); }
    .theme-parking .badge{ background: linear-gradient(180deg, #22ff77, #0a8b3e); }

    .theme-admin .titlebar{ background: linear-gradient(180deg, #2b83ff, #0f4bb3); }
    .theme-admin .badge{ background: linear-gradient(180deg, #6b4bff, #3820b8); }

    .theme-personal .titlebar{ background: linear-gradient(180deg, #ff3a3a, #a10d0d); }
    .theme-personal .badge{ background: linear-gradient(180deg, #ff6a6a, #b40c0c); }

    .theme-main .titlebar{ background: linear-gradient(180deg, #3b4352, #232a36); }
    .theme-main .badge{ background: linear-gradient(180deg, #cdd3df, #67708a); }

    /* modal editor (compact) */
    .modal-back{
      position:fixed; inset:0; z-index:60;
      background: rgba(0,0,0,.55);
      display:none;
      place-items:center;
      padding:12px;
    }
    .modal-back.show{ display:grid; }
    .modal{
      width:min(720px, 96vw);
      border-radius: 16px;
      border:1px solid rgba(255,255,255,.14);
      background: rgba(18,20,26,.92);
      box-shadow: var(--shadow);
      backdrop-filter: blur(10px);
      overflow:hidden;
    }
    .modal .mbar{
      display:flex; justify-content:space-between; align-items:center;
      padding:10px 12px;
      border-bottom: 1px solid rgba(255,255,255,.10);
      font-weight:900;
    }
    .modal .mcontent{
      padding:10px 12px;
      display:grid;
      grid-template-columns: 1fr 1fr;
      gap:8px;
    }
    .field{
      display:flex; flex-direction:column; gap:4px;
      font-size:11px; opacity:.95;
    }
    .field input, .field select{
      height:32px;
      border-radius: 10px;
      border:1px solid rgba(255,255,255,.16);
      background: rgba(0,0,0,.35);
      color:#e9eef7;
      padding:0 10px;
      outline:none;
      font-size:12px;
    }
    .modal .mactions{
      display:flex; gap:8px; justify-content:flex-end; flex-wrap:wrap;
      padding:10px 12px;
      border-top: 1px solid rgba(255,255,255,.10);
    }
    .btn{
      height:34px;
      border-radius: 12px;
      border:1px solid rgba(255,255,255,.16);
      background: rgba(255,255,255,.10);
      color:#e9eef7;
      padding:0 12px;
      font-weight:900;
      cursor:pointer;
      user-select:none;
      touch-action: manipulation;
      font-size:12px;
    }
    .btn.secondary{ background: rgba(0,0,0,.28); }
    .btn.danger{ background: rgba(255,0,0,.12); border-color: rgba(255,0,0,.25); }
    .btn:active{ transform: translateY(1px); }

    /* Personal notepad (no apps) */
    .notepad-wrap{ display:flex; flex-direction:column; gap:8px; }
    .np-toolbar{
      display:flex; flex-wrap:wrap; gap:6px;
      padding:8px;
      border-radius: 12px;
      border:1px solid rgba(255,255,255,.14);
      background: rgba(0,0,0,.22);
      align-items:center;
    }
    .np-toolbar .group{
      display:flex; align-items:center; gap:6px;
      padding:4px 6px;
      border-radius: 10px;
      border:1px solid rgba(255,255,255,.10);
      background: rgba(255,255,255,.04);
    }
    .np-toolbar label{ font-size:11px; opacity:.85; }
    .np-toolbar input[type="color"]{ width:30px; height:24px; border:0; background:transparent; padding:0; }
    .np-toolbar input[type="range"]{ width:90px; }
    .np-toolbar input[type="number"], .np-toolbar input[type="text"]{
      height:28px; border-radius:10px; border:1px solid rgba(255,255,255,.14);
      background: rgba(0,0,0,.30); color:#e9eef7; padding:0 8px; outline:none; font-size:12px;
    }
    .canvas-shell{
      border-radius: 14px;
      border:1px solid rgba(255,255,255,.14);
      background: rgba(0,0,0,.22);
      overflow:hidden;
      box-shadow: inset 0 1px 0 rgba(255,255,255,.10);
    }
    canvas{
      display:block;
      width:100%;
      height: clamp(240px, 32vh, 420px);
      touch-action: none;
      cursor: crosshair;
      background: linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.02));
    }
    .muted{ opacity:.75; font-size:12px; }

    @media (max-width: 980px){
      body{ overflow:auto; }
      .wrap{ height:auto; overflow:visible; }
      .main{ grid-template-columns: 1fr; }
      .right-col{ grid-template-rows: auto auto auto; }
      .bottom-row{ grid-template-columns: 1fr; }
      .editbar{ position:sticky; top:10px; margin-left:auto; width:max-content; }
    }
  </style>
</head>

<body>
  <!-- EDIT TOGGLE -->
  <div class="editbar" id="editbar">
    <div class="tag" id="saveState">Ready</div>
    <div class="btn-ico" id="btnEdit" title="Edit mode">✎</div>
  </div>

  <div class="wrap">
    <!-- MAIN launcher / quick actions bar -->
    <section class="win theme-main launcher" id="win-main" data-section="main">
      <div class="titlebar">
        <div class="left">
          <div class="badge"></div>
          <div class="title">Main</div>
        </div>
        <div class="right">
          <div class="mini-ico hidden" data-action="grid" title="Grid">⚙</div>
          <div class="mini-ico hidden" data-action="add" title="Add">＋</div>
        </div>
      </div>
      <div class="content">
        <div class="icons dock" id="icons-main"></div>
      </div>
    </section>

    <section class="main">
      <div class="left-col">
        <div class="win theme-general" id="win-general" data-section="general">
          <div class="titlebar">
            <div class="left">
              <div class="badge"></div>
              <div class="title">General</div>
            </div>
            <div class="right">
              <div class="mini-ico hidden" data-action="grid" title="Grid">⚙</div>
              <div class="mini-ico hidden" data-action="add" title="Add">＋</div>
            </div>
          </div>
          <div class="content">
            <div class="icons" id="icons-general"></div>
          </div>
        </div>

        <div class="bottom-row">
          <div class="win theme-parking" id="win-parking" data-section="parking">
            <div class="titlebar">
              <div class="left">
                <div class="badge"></div>
                <div class="title">Parking</div>
              </div>
              <div class="right">
                <div class="mini-ico hidden" data-action="grid" title="Grid">⚙</div>
                <div class="mini-ico hidden" data-action="add" title="Add">＋</div>
              </div>
            </div>
            <div class="content">
              <div class="icons" id="icons-parking"></div>
            </div>
          </div>

          <div class="win theme-admin" id="win-admin" data-section="admin">
            <div class="titlebar">
              <div class="left">
                <div class="badge"></div>
                <div class="title">Admin</div>
              </div>
              <div class="right">
                <div class="mini-ico hidden" data-action="grid" title="Grid">⚙</div>
                <div class="mini-ico hidden" data-action="add" title="Add">＋</div>
              </div>
            </div>
            <div class="content">
              <div class="icons" id="icons-admin"></div>
            </div>
          </div>
        </div>
      </div>

      <div class="right-col">
        <div class="win theme-security" id="win-security" data-section="security">
          <div class="titlebar">
            <div class="left">
              <div class="badge"></div>
              <div class="title">Security</div>
            </div>
            <div class="right">
              <div class="mini-ico hidden" data-action="grid" title="Grid">⚙</div>
              <div class="mini-ico hidden" data-action="add" title="Add">＋</div>
            </div>
          </div>
          <div class="content">
            <div class="icons" id="icons-security"></div>
          </div>
        </div>

        <!-- Personal: NOTEPAD ONLY -->
        <div class="win theme-personal" id="win-personal" data-section="personal">
          <div class="titlebar">
            <div class="left">
              <div class="badge"></div>
              <div class="title">Personal</div>
            </div>
            <div class="right">
              <!-- no add/grid here -->
            </div>
          </div>
          <div class="content">
            <div class="notepad-wrap">
              <div class="np-toolbar">
                <div class="group">
                  <button class="btn secondary" id="tool-brush" type="button" style="height:28px;padding:0 10px;">Brush</button>
                  <label>Size</label>
                  <input id="brush-size" type="range" min="1" max="40" value="8" />
                  <input id="brush-color" type="color" value="#ffffff" title="Brush colour" />
                </div>

                <div class="group">
                  <button class="btn secondary" id="tool-eraser" type="button" style="height:28px;padding:0 10px;">Eraser</button>
                  <label>Size</label>
                  <input id="eraser-size" type="range" min="4" max="80" value="24" />
                </div>

                <div class="group">
                  <button class="btn secondary" id="tool-text" type="button" style="height:28px;padding:0 10px;">Text</button>
                  <label>Size</label>
                  <input id="text-size" type="number" min="10" max="120" value="26" style="width:64px" />
                  <input id="text-color" type="color" value="#ffffff" title="Text colour" />
                  <input id="text-input" type="text" placeholder="Type…" style="width:140px" />
                </div>

                <div class="group">
                  <button class="btn secondary" id="undo" type="button" style="height:28px;padding:0 10px;">↶</button>
                  <button class="btn secondary" id="redo" type="button" style="height:28px;padding:0 10px;">↷</button>
                  <button class="btn secondary" id="save" type="button" style="height:28px;padding:0 10px;">PNG</button>
                  <button class="btn danger" id="clear" type="button" style="height:28px;padding:0 10px;">Clear</button>
                </div>
              </div>

              <div class="canvas-shell">
                <canvas id="pad"></canvas>
              </div>

              <div class="muted" id="pad-status">Notes auto-save locally in your browser.</div>
            </div>
          </div>
        </div>

        <div class="win theme-main" style="opacity:.92">
          <div class="titlebar">
            <div class="left">
              <div class="badge"></div>
              <div class="title">Status</div>
            </div>
            <div class="right"></div>
          </div>
          <div class="content">
            <div class="muted" id="statusText">Loading…</div>
          </div>
        </div>
      </div>
    </section>
  </div>

  <!-- COMPACT MODAL -->
  <div class="modal-back" id="modalBack" aria-hidden="true">
    <div class="modal" role="dialog" aria-modal="true">
      <div class="mbar">
        <div id="modalTitle">Edit</div>
        <button class="btn secondary" id="modalClose" type="button">Close</button>
      </div>

      <div class="mcontent" id="modalContent"></div>

      <div class="mactions" id="modalActions"></div>
    </div>
  </div>

<script>
(() => {
  const API = 'api.php';
  const saveStateEl = document.getElementById('saveState');
  const statusText = document.getElementById('statusText');
  const btnEdit = document.getElementById('btnEdit');
  const modalBack = document.getElementById('modalBack');
  const modalTitle = document.getElementById('modalTitle');
  const modalContent = document.getElementById('modalContent');
  const modalActions = document.getElementById('modalActions');
  const modalClose = document.getElementById('modalClose');

  const SECTION = {
    main:     { file: 'main.json',     mount: 'icons-main',     dock:true,  defaultGrid:{rows:1, cols:6} },
    general:  { file: 'general.json',  mount: 'icons-general',  dock:false, defaultGrid:{rows:2, cols:5} },
    security: { file: 'security.json', mount: 'icons-security', dock:false, defaultGrid:{rows:1, cols:4} },
    parking:  { file: 'parking.json',  mount: 'icons-parking',  dock:false, defaultGrid:{rows:1, cols:4} },
    admin:    { file: 'admin.json',    mount: 'icons-admin',    dock:false, defaultGrid:{rows:1, cols:4} },
  };

  let editMode = false;
  let dataCache = {}; // sectionKey -> {grid, apps}
  let saveTimer = null;
  let saving = 0;

  const uid = () => (crypto?.randomUUID ? crypto.randomUUID() : ('id-' + Math.random().toString(16).slice(2) + Date.now()));
  const clamp = (n,min,max) => Math.max(min, Math.min(max, n));

  function setSaveState(txt){
    saveStateEl.textContent = txt;
  }

  function setEditMode(on){
    editMode = !!on;
    document.body.classList.toggle('edit-mode', editMode);
    btnEdit.classList.toggle('on', editMode);
    // show/hide mini icons in titlebars
    document.querySelectorAll('.mini-ico').forEach(el => {
      el.classList.toggle('hidden', !editMode);
    });
    setSaveState(editMode ? 'Edit ON' : 'Ready');
  }

  btnEdit.addEventListener('click', () => setEditMode(!editMode));

  function iconFallbackLetter(name){
    const s = (name || '').trim();
    return (s[0] ? s[0].toUpperCase() : '•');
  }

  function makeAppEl(sectionKey, app){
    const a = document.createElement('a');
    a.className = 'app';
    a.href = app.url || '#';
    a.target = app.target || '_blank';
    a.rel = 'noopener noreferrer';
    a.dataset.id = app.id;
    a.dataset.section = sectionKey;
    if (app.hint) a.title = app.hint;

    const dragHandle = document.createElement('div');
    dragHandle.className = 'drag-handle';
    dragHandle.textContent = '≡';

    const editChip = document.createElement('div');
    editChip.className = 'edit-chip';
    editChip.textContent = '⚙';

    const ico = document.createElement('div');
    ico.className = 'ico';
    if (app.tint) ico.style.background = app.tint;

    if (app.icon && app.icon.trim()){
      const img = document.createElement('img');
      img.alt = app.name || 'App';
      img.src = app.icon;
      img.loading = 'lazy';
      img.onerror = () => {
        img.remove();
        ico.textContent = iconFallbackLetter(app.name);
      };
      ico.appendChild(img);
    } else {
      ico.textContent = iconFallbackLetter(app.name);
    }

    const lbl = document.createElement('div');
    lbl.className = 'lbl';
    lbl.textContent = app.name || 'App';

    a.appendChild(dragHandle);
    a.appendChild(editChip);
    a.appendChild(ico);
    a.appendChild(lbl);

    // Edit click: when in edit mode, open modal instead of navigating
    a.addEventListener('click', (e) => {
      if (!editMode) return;
      e.preventDefault();
      openAppEditor(sectionKey, app.id);
    });

    // Desktop drag/drop support (still handy)
    a.draggable = true;
    a.addEventListener('dragstart', (e) => {
      if (!editMode) { e.preventDefault(); return; }
      e.dataTransfer.setData('text/plain', JSON.stringify({section: sectionKey, id: app.id}));
      e.dataTransfer.effectAllowed = 'move';
    });

    return a;
  }

  function applyGrid(sectionKey){
    const cfg = SECTION[sectionKey];
    const mount = document.getElementById(cfg.mount);
    const d = dataCache[sectionKey] || {grid: cfg.defaultGrid, apps: []};
    const rows = clamp(parseInt(d.grid?.rows || cfg.defaultGrid.rows, 10), 1, 20);
    const cols = clamp(parseInt(d.grid?.cols || cfg.defaultGrid.cols, 10), 1, 20);

    if (cfg.dock) {
      // main is a dock / quick actions bar
      mount.style.gridAutoFlow = 'column';
      mount.style.gridAutoColumns = `minmax(86px, 1fr)`;
      mount.style.gridTemplateColumns = `repeat(${Math.max(cols, 4)}, minmax(86px, 1fr))`;
      mount.style.gridTemplateRows = `repeat(${rows}, auto)`;
    } else {
      mount.style.gridTemplateColumns = `repeat(${cols}, minmax(0, 1fr))`;
      mount.style.gridAutoRows = 'auto';
      mount.style.gridTemplateRows = `repeat(${rows}, auto)`;
    }
    mount.dataset.rows = rows;
    mount.dataset.cols = cols;
  }

  function renderSection(sectionKey){
    const cfg = SECTION[sectionKey];
    const mount = document.getElementById(cfg.mount);
    const d = dataCache[sectionKey] || {grid: cfg.defaultGrid, apps: []};

    // ensure ids
    d.apps.forEach(a => { if (!a.id) a.id = uid(); });

    // enforce max cells (rows*cols) for non-dock; for dock allow overflow scroll but still keep a soft cap
    const rows = clamp(parseInt(d.grid?.rows || cfg.defaultGrid.rows, 10), 1, 20);
    const cols = clamp(parseInt(d.grid?.cols || cfg.defaultGrid.cols, 10), 1, 20);
    const maxCells = rows * cols;

    if (!cfg.dock && d.apps.length > maxCells) {
      d.apps = d.apps.slice(0, maxCells);
    }

    dataCache[sectionKey] = d;

    applyGrid(sectionKey);

    mount.innerHTML = '';
    d.apps.forEach(app => mount.appendChild(makeAppEl(sectionKey, app)));

    // enable drop reorder
    mount.addEventListener('dragover', (e) => {
      if (!editMode) return;
      e.preventDefault();
      e.dataTransfer.dropEffect = 'move';
    });

    mount.addEventListener('drop', (e) => {
      if (!editMode) return;
      e.preventDefault();
      const payload = (() => {
        try { return JSON.parse(e.dataTransfer.getData('text/plain')); } catch { return null; }
      })();
      if (!payload || payload.section !== sectionKey) return;
      const id = payload.id;

      const target = e.target.closest('.app');
      if (!target) return;

      const targetId = target.dataset.id;
      if (!targetId || targetId === id) return;

      reorderApps(sectionKey, id, targetId);
    });

    // Pointer/touch reorder (long press on handle)
    mount.querySelectorAll('.app .drag-handle').forEach(handle => {
      handle.addEventListener('pointerdown', (e) => {
        if (!editMode) return;
        e.preventDefault();
        e.stopPropagation();
        startPointerDrag(e, handle.closest('.app'), sectionKey);
      });
    });
  }

  function reorderApps(sectionKey, movedId, beforeId){
    const d = dataCache[sectionKey];
    const apps = d.apps.slice();
    const from = apps.findIndex(a => a.id === movedId);
    const to = apps.findIndex(a => a.id === beforeId);
    if (from < 0 || to < 0) return;

    const [moved] = apps.splice(from, 1);
    apps.splice(to, 0, moved);
    d.apps = apps;
    dataCache[sectionKey] = d;
    renderSection(sectionKey);
    queueSave(sectionKey);
  }

  // pointer drag (touch friendly)
  let drag = null;
  function startPointerDrag(ev, appEl, sectionKey){
    const id = appEl.dataset.id;
    const mount = document.getElementById(SECTION[sectionKey].mount);
    const rect = appEl.getBoundingClientRect();

    const ghost = appEl.cloneNode(true);
    ghost.style.position = 'fixed';
    ghost.style.left = rect.left + 'px';
    ghost.style.top = rect.top + 'px';
    ghost.style.width = rect.width + 'px';
    ghost.style.height = rect.height + 'px';
    ghost.style.opacity = '0.85';
    ghost.style.zIndex = '9999';
    ghost.style.pointerEvents = 'none';
    ghost.style.transform = 'scale(1.03)';
    document.body.appendChild(ghost);

    drag = { sectionKey, id, ghost, offsetX: ev.clientX - rect.left, offsetY: ev.clientY - rect.top };

    const onMove = (e) => {
      if (!drag) return;
      drag.ghost.style.left = (e.clientX - drag.offsetX) + 'px';
      drag.ghost.style.top  = (e.clientY - drag.offsetY) + 'px';

      const el = document.elementFromPoint(e.clientX, e.clientY);
      const target = el && el.closest && el.closest('#' + mount.id + ' .app');
      if (target && target.dataset.id && target.dataset.id !== drag.id) {
        reorderApps(sectionKey, drag.id, target.dataset.id);
      }
    };
    const onUp = () => {
      if (!drag) return;
      drag.ghost.remove();
      drag = null;
      window.removeEventListener('pointermove', onMove, {passive:false});
      window.removeEventListener('pointerup', onUp);
    };

    window.addEventListener('pointermove', onMove, {passive:false});
    window.addEventListener('pointerup', onUp);
  }

  async function loadSection(sectionKey){
    const cfg = SECTION[sectionKey];
    try{
      const res = await fetch(`${API}?file=${encodeURIComponent(cfg.file)}`, {cache:'no-store'});
      const js = await res.json();
      if (!js.ok) throw new Error(js.error || 'Load failed');
      dataCache[sectionKey] = js.data || {grid: cfg.defaultGrid, apps: []};
    }catch(e){
      dataCache[sectionKey] = {grid: cfg.defaultGrid, apps: []};
    }
    renderSection(sectionKey);
  }

  function queueSave(sectionKey){
    setSaveState('Saving…');
    statusText.textContent = `Saving ${sectionKey}…`;

    clearTimeout(saveTimer);
    saveTimer = setTimeout(() => saveSection(sectionKey), 350);
  }

  async function saveSection(sectionKey){
    const cfg = SECTION[sectionKey];
    const payload = {
      action: 'save',
      file: cfg.file,
      data: dataCache[sectionKey]
    };

    saving++;
    try{
      const res = await fetch(API, {
        method: 'POST',
        headers: {'Content-Type':'application/json'},
        body: JSON.stringify(payload)
      });
      const js = await res.json();
      if (!js.ok) throw new Error(js.error || 'Save failed');
      dataCache[sectionKey] = js.data;
      setSaveState('Saved');
      statusText.textContent = `Saved ${sectionKey}.`;
    }catch(e){
      setSaveState('Save failed');
      statusText.textContent = `Save failed (${sectionKey}).`;
    }finally{
      saving--;
    }
  }

  /* ---------- EDIT UI ---------- */

  function openModal(title, fieldsHtml, actionsHtml){
    modalTitle.textContent = title;
    modalContent.innerHTML = fieldsHtml;
    modalActions.innerHTML = actionsHtml;
    modalBack.classList.add('show');
    modalBack.setAttribute('aria-hidden','false');
  }
  function closeModal(){
    modalBack.classList.remove('show');
    modalBack.setAttribute('aria-hidden','true');
  }
  modalClose.addEventListener('click', closeModal);
  modalBack.addEventListener('click', (e) => { if (e.target === modalBack) closeModal(); });

  function openAppEditor(sectionKey, appId){
    const d = dataCache[sectionKey];
    const app = d.apps.find(a => a.id === appId);
    if (!app) return;

    const f = (label, id, value, type='text') => `
      <div class="field">
        <div>${label}</div>
        <input type="${type}" id="${id}" value="${String(value ?? '').replaceAll('"','&quot;')}" />
      </div>
    `;
    const s = (label, id, value) => `
      <div class="field">
        <div>${label}</div>
        <select id="${id}">
          ${['_blank','_self','_top','_parent'].map(v => `<option value="${v}" ${v===value?'selected':''}>${v}</option>`).join('')}
        </select>
      </div>
    `;

    openModal(
      `Edit: ${app.name || 'App'}`,
      [
        f('Name','e_name',app.name),
        f('URL','e_url',app.url),
        f('Icon URL','e_icon',app.icon),
        f('Tint (CSS)','e_tint',app.tint),
        f('Hint','e_hint',app.hint),
        s('Target','e_target',app.target || '_blank'),
      ].join(''),
      `
        <button class="btn danger" id="e_delete" type="button">Delete</button>
        <button class="btn secondary" id="e_cancel" type="button">Cancel</button>
        <button class="btn" id="e_save" type="button">Save</button>
      `
    );

    document.getElementById('e_cancel').onclick = closeModal;
    document.getElementById('e_delete').onclick = () => {
      d.apps = d.apps.filter(a => a.id !== appId);
      renderSection(sectionKey);
      queueSave(sectionKey);
      closeModal();
    };
    document.getElementById('e_save').onclick = () => {
      app.name   = document.getElementById('e_name').value.trim();
      app.url    = document.getElementById('e_url').value.trim();
      app.icon   = document.getElementById('e_icon').value.trim();
      app.tint   = document.getElementById('e_tint').value.trim();
      app.hint   = document.getElementById('e_hint').value.trim();
      app.target = document.getElementById('e_target').value;
      renderSection(sectionKey);
      queueSave(sectionKey);
      closeModal();
    };
  }

  function openAddApp(sectionKey){
    const d = dataCache[sectionKey];
    const app = { id: uid(), name:'', url:'', icon:'', tint:'', hint:'', target:'_blank' };

    openModal(
      `Add app to ${sectionKey}`,
      `
        <div class="field"><div>Name</div><input id="a_name" value=""></div>
        <div class="field"><div>URL</div><input id="a_url" value=""></div>
        <div class="field"><div>Icon URL</div><input id="a_icon" value=""></div>
        <div class="field"><div>Tint (CSS)</div><input id="a_tint" value=""></div>
        <div class="field"><div>Hint</div><input id="a_hint" value=""></div>
        <div class="field">
          <div>Target</div>
          <select id="a_target">
            <option value="_blank" selected>_blank</option>
            <option value="_self">_self</option>
            <option value="_top">_top</option>
            <option value="_parent">_parent</option>
          </select>
        </div>
      `,
      `
        <button class="btn secondary" id="a_cancel" type="button">Cancel</button>
        <button class="btn" id="a_add" type="button">Add</button>
      `
    );

    document.getElementById('a_cancel').onclick = closeModal;
    document.getElementById('a_add').onclick = () => {
      app.name   = document.getElementById('a_name').value.trim();
      app.url    = document.getElementById('a_url').value.trim();
      app.icon   = document.getElementById('a_icon').value.trim();
      app.tint   = document.getElementById('a_tint').value.trim();
      app.hint   = document.getElementById('a_hint').value.trim();
      app.target = document.getElementById('a_target').value;

      // if user didn't set icon, give a random-ish placeholder (works fine)
      if (!app.icon) {
        const seed = encodeURIComponent((app.name || 'app') + '-' + Date.now());
        app.icon = `https://picsum.photos/seed/${seed}/128/128`;
      }

      d.apps.push(app);
      renderSection(sectionKey);
      queueSave(sectionKey);
      closeModal();
    };
  }

  function openGridEditor(sectionKey){
    const d = dataCache[sectionKey];
    const g = d.grid || SECTION[sectionKey].defaultGrid;

    openModal(
      `Grid: ${sectionKey}`,
      `
        <div class="field">
          <div>Rows</div>
          <input id="g_rows" type="number" min="1" max="20" value="${g.rows}">
        </div>
        <div class="field">
          <div>Cols</div>
          <input id="g_cols" type="number" min="1" max="20" value="${g.cols}">
        </div>
        <div class="field" style="grid-column:1/-1; opacity:.85;">
          <div>Examples</div>
          <div style="font-size:12px; line-height:1.3; opacity:.8;">
            1x4 = 4 slots, 2x4 = 8 slots, 4x8 = 32 slots.
            Non-dock sections cap apps to rows*cols (extra gets trimmed).
            Main (dock) can overflow and scroll.
          </div>
        </div>
      `,
      `
        <button class="btn secondary" id="g_cancel" type="button">Cancel</button>
        <button class="btn" id="g_save" type="button">Save</button>
      `
    );

    document.getElementById('g_cancel').onclick = closeModal;
    document.getElementById('g_save').onclick = () => {
      const rows = clamp(parseInt(document.getElementById('g_rows').value || '1', 10), 1, 20);
      const cols = clamp(parseInt(document.getElementById('g_cols').value || '4', 10), 1, 20);
      d.grid = {rows, cols};
      renderSection(sectionKey);
      queueSave(sectionKey);
      closeModal();
    };
  }

  // Titlebar buttons
  document.querySelectorAll('.win[data-section]').forEach(win => {
    const sectionKey = win.dataset.section;
    if (!SECTION[sectionKey]) return;

    win.querySelectorAll('.mini-ico').forEach(btn => {
      btn.addEventListener('click', (e) => {
        e.stopPropagation();
        if (!editMode) return;

        const action = btn.dataset.action;
        if (action === 'add') openAddApp(sectionKey);
        if (action === 'grid') openGridEditor(sectionKey);
      });
    });
  });

  /* ---------- INIT LOAD ---------- */
  async function init(){
    statusText.textContent = 'Loading sections…';
    for (const key of Object.keys(SECTION)){
      await loadSection(key);
    }
    statusText.textContent = 'Loaded.';
    setEditMode(false);
  }
  init();

  /* ---------- NOTEPAD ---------- */
  const canvas = document.getElementById('pad');
  const ctx = canvas.getContext('2d', { willReadFrequently:true });

  const toolBrush  = document.getElementById('tool-brush');
  const toolEraser = document.getElementById('tool-eraser');
  const toolText   = document.getElementById('tool-text');
  const brushSize  = document.getElementById('brush-size');
  const brushColor = document.getElementById('brush-color');
  const eraserSize = document.getElementById('eraser-size');
  const textSize   = document.getElementById('text-size');
  const textColor  = document.getElementById('text-color');
  const textInput  = document.getElementById('text-input');
  const btnUndo    = document.getElementById('undo');
  const btnRedo    = document.getElementById('redo');
  const btnSave    = document.getElementById('save');
  const btnClear   = document.getElementById('clear');
  const padStatus  = document.getElementById('pad-status');

  let mode = 'brush';
  let drawing = false;
  let last = null;
  const UNDO_LIMIT = 40;
  let undoStack = [];
  let redoStack = [];
  const SAVE_KEY = 'workdesktop.personal.pad.png';

  function setTool(m){
    mode = m;
    [toolBrush, toolEraser, toolText].forEach(b => b.classList.remove('on'));
    padStatus.textContent = (mode === 'text') ? 'Text tool: tap canvas to place text.' : 'Notes auto-save locally in your browser.';
  }
  toolBrush.onclick  = () => setTool('brush');
  toolEraser.onclick = () => setTool('eraser');
  toolText.onclick   = () => setTool('text');

  function fitCanvas(){
    const rect = canvas.getBoundingClientRect();
    const dpr = Math.max(1, window.devicePixelRatio || 1);
    const w = Math.floor(rect.width * dpr);
    const h = Math.floor(rect.height * dpr);
    if (canvas.width !== w || canvas.height !== h){
      const snap = canvas.toDataURL('image/png');
      canvas.width = w; canvas.height = h;
      const img = new Image();
      img.onload = () => { ctx.clearRect(0,0,w,h); ctx.drawImage(img,0,0,w,h); };
      img.src = snap;
    }
  }

  function autosave(){
    try{ localStorage.setItem(SAVE_KEY, canvas.toDataURL('image/png')); }catch(e){}
  }
  function loadSaved(){
    try{
      const data = localStorage.getItem(SAVE_KEY);
      if(!data) return;
      const img = new Image();
      img.onload = () => { ctx.clearRect(0,0,canvas.width,canvas.height); ctx.drawImage(img,0,0,canvas.width,canvas.height); };
      img.src = data;
    }catch(e){}
  }

  function pushUndo(){
    try{
      undoStack.push(ctx.getImageData(0,0,canvas.width,canvas.height));
      if (undoStack.length > UNDO_LIMIT) undoStack.shift();
      redoStack = [];
      btnUndo.disabled = undoStack.length === 0;
      btnRedo.disabled = redoStack.length === 0;
    }catch(e){}
  }

  function getPos(evt){
    const rect = canvas.getBoundingClientRect();
    return {
      x: (evt.clientX - rect.left) * (canvas.width / rect.width),
      y: (evt.clientY - rect.top)  * (canvas.height / rect.height)
    };
  }

  function stroke(a,b){
    ctx.lineCap='round';
    ctx.lineJoin='round';
    if (mode === 'eraser'){
      ctx.globalCompositeOperation = 'destination-out';
      ctx.strokeStyle = 'rgba(0,0,0,1)';
      ctx.lineWidth = parseFloat(eraserSize.value);
    }else{
      ctx.globalCompositeOperation = 'source-over';
      ctx.strokeStyle = brushColor.value;
      ctx.lineWidth = parseFloat(brushSize.value);
    }
    ctx.beginPath();
    ctx.moveTo(a.x,a.y);
    ctx.lineTo(b.x,b.y);
    ctx.stroke();
  }

  function placeText(pos){
    const txt = (textInput.value || '').trim();
    if(!txt) return;
    pushUndo();
    ctx.globalCompositeOperation = 'source-over';
    ctx.fillStyle = textColor.value;
    const size = clamp(parseInt(textSize.value || '26', 10), 10, 120);
    ctx.font = `800 ${size}px ${getComputedStyle(document.body).fontFamily}`;
    ctx.textBaseline = 'top';
    ctx.shadowColor = 'rgba(0,0,0,.55)';
    ctx.shadowBlur = 6;
    ctx.shadowOffsetY = 2;
    ctx.fillText(txt, pos.x, pos.y);
    ctx.shadowColor = 'transparent';
    autosave();
  }

  canvas.addEventListener('pointerdown', (e) => {
    const p = getPos(e);
    if (mode === 'text'){ placeText(p); return; }
    drawing = true;
    last = p;
    pushUndo();
  });
  canvas.addEventListener('pointermove', (e) => {
    if(!drawing) return;
    const p = getPos(e);
    stroke(last, p);
    last = p;
  });
  const end = () => { if(!drawing) return; drawing=false; last=null; ctx.globalCompositeOperation='source-over'; autosave(); };
  canvas.addEventListener('pointerup', end);
  canvas.addEventListener('pointercancel', end);
  canvas.addEventListener('pointerleave', end);

  btnUndo.onclick = () => {
    if(!undoStack.length) return;
    redoStack.push(ctx.getImageData(0,0,canvas.width,canvas.height));
    const prev = undoStack.pop();
    ctx.putImageData(prev,0,0);
    btnUndo.disabled = undoStack.length === 0;
    btnRedo.disabled = redoStack.length === 0;
    autosave();
  };
  btnRedo.onclick = () => {
    if(!redoStack.length) return;
    undoStack.push(ctx.getImageData(0,0,canvas.width,canvas.height));
    const next = redoStack.pop();
    ctx.putImageData(next,0,0);
    btnUndo.disabled = undoStack.length === 0;
    btnRedo.disabled = redoStack.length === 0;
    autosave();
  };
  btnClear.onclick = () => { pushUndo(); ctx.clearRect(0,0,canvas.width,canvas.height); autosave(); };
  btnSave.onclick = () => {
    const a = document.createElement('a');
    a.download = 'personal-notes.png';
    a.href = canvas.toDataURL('image/png');
    a.click();
  };

  const ro = new ResizeObserver(() => { fitCanvas(); loadSaved(); });
  ro.observe(canvas);
  window.addEventListener('resize', () => { fitCanvas(); loadSaved(); });

  setTool('brush');
  setTimeout(() => { fitCanvas(); loadSaved(); }, 50);
})();
</script>
</body>
</html>
HTML;

/* ---------------------------
   DEFAULT JSON DATA (with your requested apps)
---------------------------- */

function iconUrl(string $label, string $colorHex): string {
  // stable placeholder icon
  return "https://via.placeholder.com/128/{$colorHex}/ffffff?text=" . rawurlencode($label);
}

$defaults = [
  'main.json' => [
    'grid' => ['rows'=>1, 'cols'=>6],
    'apps' => [
      ['id'=>'','name'=>'The Hub','url'=>'#','icon'=>iconUrl('HUB','2f69ff'),'tint'=>'','hint'=>'','target'=>'_blank'],
      ['id'=>'','name'=>'Landside Ops','url'=>'#','icon'=>iconUrl('OPS','003366'),'tint'=>'','hint'=>'','target'=>'_blank'],
      ['id'=>'','name'=>'Shift Report','url'=>'#','icon'=>iconUrl('SHIFT','4455aa'),'tint'=>'','hint'=>'','target'=>'_blank'],
    ]
  ],
  'general.json' => [
    'grid' => ['rows'=>2, 'cols'=>5],
    'apps' => [
      ['id'=>'','name'=>'The Hub','url'=>'#','icon'=>iconUrl('HUB','2f69ff'),'tint'=>'','hint'=>'','target'=>'_blank'],
      ['id'=>'','name'=>'Landside Operations','url'=>'#','icon'=>iconUrl('OPS','003366'),'tint'=>'','hint'=>'','target'=>'_blank'],
      ['id'=>'','name'=>'Shift Report','url'=>'#','icon'=>iconUrl('SHIFT','4455aa'),'tint'=>'','hint'=>'','target'=>'_blank'],
      ['id'=>'','name'=>'ProjectsZ','url'=>'#','icon'=>iconUrl('PZ','663399'),'tint'=>'','hint'=>'','target'=>'_blank'],
      ['id'=>'','name'=>'Project Manager','url'=>'#','icon'=>iconUrl('PM','336666'),'tint'=>'','hint'=>'','target'=>'_blank'],
    ]
  ],
  'security.json' => [
    'grid' => ['rows'=>1, 'cols'=>4],
    'apps' => [
      ['id'=>'','name'=>'BA365','url'=>'#','icon'=>iconUrl('BA','0066cc'),'tint'=>'','hint'=>'','target'=>'_blank'],
      ['id'=>'','name'=>'Swiped On','url'=>'#','icon'=>iconUrl('SO','004488'),'tint'=>'','hint'=>'','target'=>'_blank'],
      ['id'=>'','name'=>'AIC Activation','url'=>'#','icon'=>iconUrl('AIC','228833'),'tint'=>'','hint'=>'','target'=>'_blank'],
      ['id'=>'','name'=>'CCTV Cloud','url'=>'#','icon'=>iconUrl('CCTV','cc8800'),'tint'=>'','hint'=>'','target'=>'_blank'],
    ]
  ],
  'parking.json' => [
    'grid' => ['rows'=>1, 'cols'=>4],
    'apps' => [
      ['id'=>'','name'=>'Skidata Registry','url'=>'#','icon'=>iconUrl('SKI','1fa64a'),'tint'=>'','hint'=>'','target'=>'_blank'],
      ['id'=>'','name'=>'Coder Registry','url'=>'#','icon'=>iconUrl('COD','0c7c59'),'tint'=>'','hint'=>'','target'=>'_blank'],
      ['id'=>'','name'=>'SALTO Faults','url'=>'#','icon'=>iconUrl('SAL','aa2222'),'tint'=>'','hint'=>'','target'=>'_blank'],
      ['id'=>'','name'=>'CSO Forms','url'=>'#','icon'=>iconUrl('CSO','2255aa'),'tint'=>'','hint'=>'','target'=>'_blank'],
    ]
  ],
  'admin.json' => [
    'grid' => ['rows'=>1, 'cols'=>4],
    'apps' => [
      ['id'=>'','name'=>'Lost & Found','url'=>'#','icon'=>iconUrl('LF','555555'),'tint'=>'','hint'=>'','target'=>'_blank'],
      ['id'=>'','name'=>'Contractor Key Registry','url'=>'#','icon'=>iconUrl('CK','4444aa'),'tint'=>'','hint'=>'','target'=>'_blank'],
      ['id'=>'','name'=>'Customer Keybox','url'=>'#','icon'=>iconUrl('KB','884400'),'tint'=>'','hint'=>'','target'=>'_blank'],
      ['id'=>'','name'=>'AVSEC Registry','url'=>'#','icon'=>iconUrl('AV','880088'),'tint'=>'','hint'=>'','target'=>'_blank'],
    ]
  ],
  'personal.json' => [
    'grid' => ['rows'=>1, 'cols'=>4],
    'apps' => []
  ],
];

// ensure IDs get generated on client if blank; keep blank here to stay readable

/* ---------------------------
   INSTALL ACTION
---------------------------- */
$did = false;
$log = [];

if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['do_install'] ?? '') === '1') {
  if (!is_dir($dataDir)) {
    if (@mkdir($dataDir, 0755, true)) $log[] = "Created /data";
    else $log[] = "FAILED to create /data (permissions?)";
  } else {
    $log[] = "/data exists";
  }

  // write api.php + index.html
  if (write_file($files['api.php'], $apiPhp)) $log[] = "Wrote api.php";
  else $log[] = "FAILED to write api.php (permissions?)";

  if (write_file($files['index.html'], $indexHtml)) $log[] = "Wrote index.html";
  else $log[] = "FAILED to write index.html (permissions?)";

  // write json files
  foreach ($defaults as $fn => $data) {
    $path = $dataDir . DIRECTORY_SEPARATOR . $fn;
    if (write_json($path, $data)) $log[] = "Wrote data/$fn";
    else $log[] = "FAILED to write data/$fn (permissions?)";
  }

  $did = true;
}
?>
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Installer</title>
  <style>
    body{margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial;background:#0e1014;color:#e9eef7;min-height:100vh;display:grid;place-items:center;padding:16px;}
    .card{width:min(820px,95vw);background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);border-radius:16px;box-shadow:0 18px 40px rgba(0,0,0,.45);padding:16px;}
    h1{margin:0 0 10px;font-size:18px;}
    .muted{opacity:.75;font-size:12px;line-height:1.4}
    .btn{height:44px;border-radius:14px;border:1px solid rgba(255,255,255,.16);background:rgba(255,255,255,.10);color:#e9eef7;padding:0 16px;font-weight:900;cursor:pointer;}
    .btn:active{transform:translateY(1px)}
    code{background:rgba(0,0,0,.25);padding:2px 6px;border-radius:8px;border:1px solid rgba(255,255,255,.10)}
    ul{margin:10px 0 0; padding-left:18px;}
    li{margin:6px 0; font-size:13px;}
    .ok{color:#7dffb2}
    .warn{color:#ffd37d}
  </style>
</head>
<body>
  <div class="card">
    <h1>Work Desktop Installer</h1>
    <div class="muted">
      This will create/overwrite: <code>index.html</code>, <code>api.php</code>, and <code>/data/*.json</code><br>
      Backups are created as <code>.bak-YYYYMMDD-HHMMSS</code>.
    </div>

    <?php if ($did): ?>
      <div style="height:12px"></div>
      <div class="muted"><span class="ok">✅ Installed.</span> Open <code>index.html</code> in the same folder.</div>
      <ul>
        <?php foreach ($log as $line): ?>
          <li><?=h($line)?></li>
        <?php endforeach; ?>
      </ul>
      <div style="height:12px"></div>
      <div class="muted warn">Security tip: delete <code>installer.php</code> after install.</div>
    <?php else: ?>
      <form method="post" style="margin-top:14px;">
        <input type="hidden" name="do_install" value="1">
        <button class="btn" type="submit">Install / Overwrite Now</button>
      </form>
    <?php endif; ?>
  </div>
</body