{
  "config": {
    "configFile": "/var/www/360lm/playwright.config.js",
    "rootDir": "/var/www/360lm/tests",
    "forbidOnly": false,
    "fullyParallel": false,
    "globalSetup": null,
    "globalTeardown": null,
    "globalTimeout": 0,
    "grep": {},
    "grepInvert": null,
    "maxFailures": 0,
    "metadata": {
      "actualWorkers": 1
    },
    "preserveOutput": "always",
    "projects": [
      {
        "outputDir": "/var/www/360lm/test-results",
        "repeatEach": 1,
        "retries": 1,
        "metadata": {
          "actualWorkers": 1
        },
        "id": "android-chrome",
        "name": "android-chrome",
        "testDir": "/var/www/360lm/tests",
        "testIgnore": [],
        "testMatch": [
          "**/*.@(spec|test).?(c|m)[jt]s?(x)"
        ],
        "timeout": 30000
      },
      {
        "outputDir": "/var/www/360lm/test-results",
        "repeatEach": 1,
        "retries": 1,
        "metadata": {
          "actualWorkers": 1
        },
        "id": "desktop-chrome",
        "name": "desktop-chrome",
        "testDir": "/var/www/360lm/tests",
        "testIgnore": [],
        "testMatch": [
          "**/*.@(spec|test).?(c|m)[jt]s?(x)"
        ],
        "timeout": 30000
      }
    ],
    "quiet": false,
    "reporter": [
      [
        "json"
      ]
    ],
    "reportSlowTests": {
      "max": 5,
      "threshold": 300000
    },
    "shard": null,
    "tags": [],
    "updateSnapshots": "missing",
    "updateSourceMethod": "patch",
    "version": "1.60.0",
    "workers": 1,
    "webServer": null
  },
  "suites": [
    {
      "title": "recce.spec.js",
      "file": "recce.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Branding Recce — Page Load",
          "file": "recce.spec.js",
          "line": 24,
          "column": 6,
          "specs": [
            {
              "title": "no session — page either restricts or loads without crash",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 798,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:21:47.197Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-1753c18a0885d52c9a8b",
              "file": "recce.spec.js",
              "line": 25,
              "column": 3
            },
            {
              "title": "with hub session — home view renders",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 774,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:21:48.234Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-4e2ec7a3c80b44ada57c",
              "file": "recce.spec.js",
              "line": 33,
              "column": 3
            },
            {
              "title": "no session — page either restricts or loads without crash",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1482,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:22:54.944Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-a7868269ce4d683dca11",
              "file": "recce.spec.js",
              "line": 25,
              "column": 3
            },
            {
              "title": "with hub session — home view renders",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1105,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:22:56.692Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-266a5a3329e21b0c0eb9",
              "file": "recce.spec.js",
              "line": 33,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — Hero Section",
          "file": "recce.spec.js",
          "line": 43,
          "column": 6,
          "specs": [
            {
              "title": "hero Hub button exists and navigates to /hub/",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1134,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:21:49.052Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-d0678eca61955c559273",
              "file": "recce.spec.js",
              "line": 48,
              "column": 3
            },
            {
              "title": "hero shows today-date populated (init complete)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 805,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:21:50.258Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-ce30c36ecd7a8bce1b47",
              "file": "recce.spec.js",
              "line": 58,
              "column": 3
            },
            {
              "title": "hero Hub button exists and navigates to /hub/",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1411,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:22:57.849Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-28ba7b0f3b4e7160480d",
              "file": "recce.spec.js",
              "line": 48,
              "column": 3
            },
            {
              "title": "hero shows today-date populated (init complete)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1187,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:22:59.309Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-b2b4a5b858c6f28097a1",
              "file": "recce.spec.js",
              "line": 58,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — Settings Screen",
          "file": "recce.spec.js",
          "line": 67,
          "column": 6,
          "specs": [
            {
              "title": "settings screen opens via gear icon",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1132,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:21:51.151Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-7cfdb05507781d19e4c1",
              "file": "recce.spec.js",
              "line": 72,
              "column": 3
            },
            {
              "title": "settings hdr has Hub button (injected by injectHubButtons)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1148,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:21:52.373Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-43d147e7ce8304f4d248",
              "file": "recce.spec.js",
              "line": 81,
              "column": 3
            },
            {
              "title": "settings Hub button navigates to /hub/",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1042,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:21:53.546Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-bb4a3763afdab1fd351c",
              "file": "recce.spec.js",
              "line": 91,
              "column": 3
            },
            {
              "title": "settings screen opens via gear icon",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2771,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:00.529Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-44ed18ecb42e594c0b85",
              "file": "recce.spec.js",
              "line": 72,
              "column": 3
            },
            {
              "title": "settings hdr has Hub button (injected by injectHubButtons)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1864,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:03.470Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-a591eb06fe80a692ed9e",
              "file": "recce.spec.js",
              "line": 81,
              "column": 3
            },
            {
              "title": "settings Hub button navigates to /hub/",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1716,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:05.430Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-3a3c6537d3e93ee9628a",
              "file": "recce.spec.js",
              "line": 91,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — Existing Functionality Unchanged",
          "file": "recce.spec.js",
          "line": 103,
          "column": 6,
          "specs": [
            {
              "title": "stats cards visible on home",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1071,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:21:54.657Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-583c8d69d0547dbfb56d",
              "file": "recce.spec.js",
              "line": 108,
              "column": 3
            },
            {
              "title": "New Recce button exists in DOM (hidden for admin users, visible for field agents)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 931,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:21:55.755Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-ec42f7c5c6dd7545fd67",
              "file": "recce.spec.js",
              "line": 115,
              "column": 3
            },
            {
              "title": "language toggle button visible in hero",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1143,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:21:56.742Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-d82c8374086879bd627f",
              "file": "recce.spec.js",
              "line": 123,
              "column": 3
            },
            {
              "title": "existing Hub button in hero still works",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 997,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:21:57.931Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-4a8ea58eef21687bc344",
              "file": "recce.spec.js",
              "line": 130,
              "column": 3
            },
            {
              "title": "stats cards visible on home",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1072,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:07.184Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-b8d172515e7502575bed",
              "file": "recce.spec.js",
              "line": 108,
              "column": 3
            },
            {
              "title": "New Recce button exists in DOM (hidden for admin users, visible for field agents)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1454,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:08.309Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-286cffb21bd655860a5c",
              "file": "recce.spec.js",
              "line": 115,
              "column": 3
            },
            {
              "title": "language toggle button visible in hero",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1182,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:09.857Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-d422b9473e63f3982a2e",
              "file": "recce.spec.js",
              "line": 123,
              "column": 3
            },
            {
              "title": "existing Hub button in hero still works",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1823,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:11.116Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-192ed33ae9f0ae180ff9",
              "file": "recce.spec.js",
              "line": 130,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — v9 Delete Modal (B2/B3/B4 — I1/I3)",
          "file": "recce.spec.js",
          "line": 150,
          "column": 6,
          "specs": [
            {
              "title": "I1: delete toggle button shows \"View Deleted\" (not \"🗑 Deleted\")",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 979,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:21:58.962Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-ffb47deb7915088dce02",
              "file": "recce.spec.js",
              "line": 155,
              "column": 3
            },
            {
              "title": "I1: toggle label flips to \"Hide Deleted\" when clicked",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3926,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:21:59.977Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-7ffcdd277c5c49ea70b0",
              "file": "recce.spec.js",
              "line": 164,
              "column": 3
            },
            {
              "title": "delete modal opens on 🗑 button click",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1393,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:22:04.009Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-74b7d917d5e58853863e",
              "file": "recce.spec.js",
              "line": 174,
              "column": 3
            },
            {
              "title": "B2: confirm button has no-reason class (grey) on modal open",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 930,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:22:05.521Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-d164c8cfa7c4ae7dc12d",
              "file": "recce.spec.js",
              "line": 184,
              "column": 3
            },
            {
              "title": "B2: no-reason class removed when reason is selected",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2438,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:22:06.482Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-f28ceb5b4817a96370cd",
              "file": "recce.spec.js",
              "line": 197,
              "column": 3
            },
            {
              "title": "B3: toast fires when Delete clicked without selecting a reason",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2533,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:22:08.985Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-8d432e3c43ec933e624f",
              "file": "recce.spec.js",
              "line": 209,
              "column": 3
            },
            {
              "title": "B4: delete modal bdialog has max-height:85vh and overflow-y:auto",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1213,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:22:11.622Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-092aaa8f73d34c439292",
              "file": "recce.spec.js",
              "line": 222,
              "column": 3
            },
            {
              "title": "I3: Test Recce reason option has danger class",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1744,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:22:12.893Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-343e94f579d36f7a757e",
              "file": "recce.spec.js",
              "line": 229,
              "column": 3
            },
            {
              "title": "I3: Test Recce option shows ⚠️ icon",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 965,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:22:14.746Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-fe3ed23b6e3d4d7bf390",
              "file": "recce.spec.js",
              "line": 234,
              "column": 3
            },
            {
              "title": "cancel button closes delete modal cleanly",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2498,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:22:15.754Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-596c740377755ffd23af",
              "file": "recce.spec.js",
              "line": 239,
              "column": 3
            },
            {
              "title": "I1: delete toggle button shows \"View Deleted\" (not \"🗑 Deleted\")",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2222,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:13.005Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-adbba25ffb365b2f9f89",
              "file": "recce.spec.js",
              "line": 155,
              "column": 3
            },
            {
              "title": "I1: toggle label flips to \"Hide Deleted\" when clicked",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2072,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:15.452Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-b832889f85d6527d6aab",
              "file": "recce.spec.js",
              "line": 164,
              "column": 3
            },
            {
              "title": "delete modal opens on 🗑 button click",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1438,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:17.568Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-566aaf8dada24ba0adec",
              "file": "recce.spec.js",
              "line": 174,
              "column": 3
            },
            {
              "title": "B2: confirm button has no-reason class (grey) on modal open",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2194,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:19.084Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-02c0045840bc7dd39db5",
              "file": "recce.spec.js",
              "line": 184,
              "column": 3
            },
            {
              "title": "B2: no-reason class removed when reason is selected",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1738,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:21.315Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-68089c0e18f7fec862dc",
              "file": "recce.spec.js",
              "line": 197,
              "column": 3
            },
            {
              "title": "B3: toast fires when Delete clicked without selecting a reason",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2296,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:23.142Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-d97019103c7b86a9f55c",
              "file": "recce.spec.js",
              "line": 209,
              "column": 3
            },
            {
              "title": "B4: delete modal bdialog has max-height:85vh and overflow-y:auto",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1096,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:25.504Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-62f7a7730d892c776d77",
              "file": "recce.spec.js",
              "line": 222,
              "column": 3
            },
            {
              "title": "I3: Test Recce reason option has danger class",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1098,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:26.623Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-2c22af5cd25674047383",
              "file": "recce.spec.js",
              "line": 229,
              "column": 3
            },
            {
              "title": "I3: Test Recce option shows ⚠️ icon",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1152,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:27.779Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-2626ce0fbf43e023a673",
              "file": "recce.spec.js",
              "line": 234,
              "column": 3
            },
            {
              "title": "cancel button closes delete modal cleanly",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1608,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:28.979Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-a0f042117f9f304f199a",
              "file": "recce.spec.js",
              "line": 239,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — v10 Inside View max 5 photos",
          "file": "recce.spec.js",
          "line": 286,
          "column": 6,
          "specs": [
            {
              "title": "inside view label shows \"(optional, max 5)\"",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 11082,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:22:18.313Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-e5a037bf3a572788f8a5",
              "file": "recce.spec.js",
              "line": 287,
              "column": 3
            },
            {
              "title": "front view label still shows \"(min 1, max 3)\"",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 10887,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:22:29.484Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-2ae6a84e5315a3352ec1",
              "file": "recce.spec.js",
              "line": 301,
              "column": 3
            },
            {
              "title": "add-inside button hidden after 3 photos (before limit) — regression guard",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 749,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:22:40.406Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-57db3c51e2e91116dd5a",
              "file": "recce.spec.js",
              "line": 313,
              "column": 3
            },
            {
              "title": "SW version is recce-v10",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 1283,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"recce-v10\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html><html lang=\\\"en\\\"><head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#FF6B35\\\">\u001b[39m\n\u001b[31m<meta name=\\\"mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-status-bar-style\\\" content=\\\"black-translucent\\\">\u001b[39m\n\u001b[31m<title>360LM</title>\u001b[39m\n\u001b[31m<meta name=\\\"description\\\" content=\\\"360LM Operations Hub — employee tools\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/hub/manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"icon\\\" href=\\\"/hub/icons/icon-192.png\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--surface:#161616;--card:#1e1e1e;--border:#2a2a2a;\u001b[39m\n\u001b[31m  --accent:#FF6B35;--accent2:#FFB800;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --danger:#e74c3c;--success:#2ecc71;--info:#3498db;\u001b[39m\n\u001b[31m  --radius:12px;--font:'Segoe UI',system-ui,sans-serif;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:var(--font);min-height:100vh;overflow-x:hidden}\u001b[39m\n\u001b[31m.screen{display:none;min-height:100vh;flex-direction:column}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── HEADER ── */\u001b[39m\n\u001b[31m.header{background:var(--surface);border-bottom:1px solid var(--border);padding:14px 16px;\u001b[39m\n\u001b[31m  display:flex;align-items:center;gap:12px;position:sticky;top:0;z-index:10;flex-shrink:0}\u001b[39m\n\u001b[31m.header h1{font-size:18px;font-weight:700;color:var(--accent);flex:1}\u001b[39m\n\u001b[31m.header .sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.btn-icon{background:none;border:none;color:var(--muted);font-size:22px;cursor:pointer;\u001b[39m\n\u001b[31m  padding:4px;border-radius:8px;line-height:1;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m.btn-icon:active{background:var(--border)}·\u001b[39m\n\u001b[31m/* ── BODY ── */\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:16px}·\u001b[39m\n\u001b[31m/* ── SPLASH ── */\u001b[39m\n\u001b[31m#screen-splash{align-items:center;justify-content:center;gap:8px}\u001b[39m\n\u001b[31m.splash-logo{font-size:72px;font-weight:900;color:var(--accent);letter-spacing:-2px;line-height:1}\u001b[39m\n\u001b[31m.splash-sub{font-size:15px;color:var(--muted);letter-spacing:2px;text-transform:uppercase}\u001b[39m\n\u001b[31m.splash-dot{width:8px;height:8px;border-radius:50%;background:var(--accent);\u001b[39m\n\u001b[31m  animation:pulse 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes pulse{0%,100%{opacity:.3;transform:scale(.8)}50%{opacity:1;transform:scale(1)}}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  min-height:100dvh;padding:24px;gap:20px;overflow-y:auto}\u001b[39m\n\u001b[31m.login-logo{font-size:44px;font-weight:900;color:var(--accent);letter-spacing:-1px}\u001b[39m\n\u001b[31m.login-sub{font-size:13px;color:var(--muted);text-transform:uppercase;letter-spacing:2px}\u001b[39m\n\u001b[31m.form-group{width:100%;max-width:360px;display:flex;flex-direction:column;gap:8px}\u001b[39m\n\u001b[31m.form-group label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px}\u001b[39m\n\u001b[31mselect{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31mselect:focus{border-color:var(--accent)}·\u001b[39m\n\u001b[31m/* ── PIN PAD ── */\u001b[39m\n\u001b[31m.pin-display{display:flex;gap:14px;justify-content:center;margin:4px 0}\u001b[39m\n\u001b[31m.pin-dot{width:16px;height:16px;border-radius:50%;border:2px solid var(--muted);background:transparent;transition:.15s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.pin-pad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;max-width:260px;margin:0 auto;width:100%}\u001b[39m\n\u001b[31m.pin-key{background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  font-size:22px;font-weight:600;padding:16px;border-radius:var(--radius);cursor:pointer;\u001b[39m\n\u001b[31m  text-align:center;line-height:1;user-select:none}\u001b[39m\n\u001b[31m.pin-key:active{background:var(--border)}\u001b[39m\n\u001b[31m.pin-key.clr{color:var(--danger);font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.pin-key.back{color:var(--accent2)}\u001b[39m\n\u001b[31m.login-err{font-size:13px;color:var(--danger);text-align:center;min-height:18px}\u001b[39m\n\u001b[31m@keyframes shake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-6px)}40%,80%{transform:translateX(6px)}}\u001b[39m\n\u001b[31m.pin-display.shake{animation:shake .4s ease}·\u001b[39m\n\u001b[31m/* ── KPI CARDS ── */\u001b[39m\n\u001b[31m.kpi-row{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\u001b[39m\n\u001b[31m.kpi-4col{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m#home-body{gap:10px}\u001b[39m\n\u001b[31m.kpi-card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:10px 8px;text-align:center;border-left:3px solid var(--accent);cursor:default}\u001b[39m\n\u001b[31m.kpi-card.alert{border-left-color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-card.warn{border-left-color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-card.ok{border-left-color:var(--success)}\u001b[39m\n\u001b[31m.kpi-card.info{border-left-color:var(--info)}\u001b[39m\n\u001b[31m.kpi-val{font-size:18px;font-weight:900;color:var(--accent);line-height:1.2}\u001b[39m\n\u001b[31m.kpi-val.red{color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-val.amber{color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-val.green{color:var(--success)}\u001b[39m\n\u001b[31m.kpi-val.blue{color:var(--info)}\u001b[39m\n\u001b[31m.kpi-lbl{font-size:10px;color:var(--muted);margin-top:4px;text-transform:uppercase;letter-spacing:.4px;line-height:1.3}\u001b[39m\n\u001b[31m.kpi-skel{background:var(--border);border-radius:4px;height:24px;margin-bottom:4px;\u001b[39m\n\u001b[31m  animation:shimmer 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes shimmer{0%,100%{opacity:.5}50%{opacity:1}}·\u001b[39m\n\u001b[31m/* ── SECTION TITLE ── */\u001b[39m\n\u001b[31m.section-title{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.8px}·\u001b[39m\n\u001b[31m/* ── MGMT HOME: MAIN TILES ── */\u001b[39m\n\u001b[31m.hub-main-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px}\u001b[39m\n\u001b[31m.hub-main-tile{background:var(--card);border:2px solid var(--border);border-radius:18px;\u001b[39m\n\u001b[31m  padding:36px 12px 28px;text-align:center;cursor:pointer;transition:background .15s,border-color .15s}\u001b[39m\n\u001b[31m.hub-main-tile:active{background:#1a2a1a;border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-main-icon{font-size:40px;line-height:1;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-main-label{font-size:15px;font-weight:800;color:var(--text)}\u001b[39m\n\u001b[31m/* ── MGMT HOME: QUICK ACTIONS ── */\u001b[39m\n\u001b[31m.hub-qa-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-qa-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(88px,1fr));gap:10px}\u001b[39m\n\u001b[31m.hub-qa-tile{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px 8px;text-align:center;cursor:pointer;display:flex;flex-direction:column;\u001b[39m\n\u001b[31m  align-items:center;gap:6px;transition:background .15s}\u001b[39m\n\u001b[31m.hub-qa-tile:active{background:var(--border)}\u001b[39m\n\u001b[31m.hub-qa-icon{font-size:26px;line-height:1}\u001b[39m\n\u001b[31m.hub-qa-label{font-size:11px;font-weight:700;color:var(--text);line-height:1.3}\u001b[39m\n\u001b[31m.hub-qa-item{display:flex;align-items:center;gap:12px;padding:10px 0;\u001b[39m\n\u001b[31m  border-bottom:1px solid var(--border);cursor:pointer}\u001b[39m\n\u001b[31m.hub-qa-item:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.hub-qa-item-icon{font-size:22px;width:32px;text-align:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item-label{flex:1;font-size:14px;font-weight:600}\u001b[39m\n\u001b[31m.hub-qa-item-toggle{width:20px;height:20px;border-radius:4px;border:2px solid var(--border);\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle::after{content:'✓';color:#fff;font-weight:700;font-size:12px}·\u001b[39m\n\u001b[31m/* ── TILE GRID ── */\u001b[39m\n\u001b[31m.tile-grid{display:grid;gap:12px}\u001b[39m\n\u001b[31m.tile-grid.mgmt{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m.tile-grid.field{grid-template-columns:repeat(2,1fr)}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:18px 8px;display:flex;flex-direction:column;align-items:center;gap:8px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s;position:relative}\u001b[39m\n\u001b[31m.tile:active{background:var(--border);transform:scale(.97)}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px;line-height:1}\u001b[39m\n\u001b[31m.tile-label{font-size:11px;font-weight:700;color:var(--text);text-align:center;text-transform:uppercase;letter-spacing:.4px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-icon{font-size:24px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-label{font-size:10px}\u001b[39m\n\u001b[31m.tile.coming{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.tile.coming:active{transform:none;background:var(--card)}\u001b[39m\n\u001b[31m.soon-badge{position:absolute;top:6px;right:6px;background:var(--accent2);color:#000;\u001b[39m\n\u001b[31m  font-size:8px;font-weight:800;padding:2px 5px;border-radius:20px;text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── USER BADGE ── */\u001b[39m\n\u001b[31m.user-badge{display:flex;align-items:center;gap:12px}\u001b[39m\n\u001b[31m.avatar{width:44px;height:44px;background:var(--accent);border-radius:50%;\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:800;\u001b[39m\n\u001b[31m  color:#fff;flex-shrink:0;text-transform:uppercase}\u001b[39m\n\u001b[31m.user-name{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.user-role{font-size:12px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── TEAM PINS ── */\u001b[39m\n\u001b[31m.emp-row{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px}\u001b[39m\n\u001b[31m.btn{display:flex;align-items:center;justify-content:center;gap:6px;border:none;\u001b[39m\n\u001b[31m  border-radius:var(--radius);font-size:15px;font-weight:700;padding:12px 18px;\u001b[39m\n\u001b[31m  cursor:pointer;width:100%;transition:.15s;font-family:var(--font)}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--card);border:1px solid var(--border);color:var(--text)}\u001b[39m\n\u001b[31m.btn-sm{padding:7px 12px;font-size:12px;font-weight:700;width:auto;flex-shrink:0}\u001b[39m\n\u001b[31m.info-note{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;font-size:13px;color:var(--muted);line-height:1.5}·\u001b[39m\n\u001b[31m/* ── MODAL ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:200;\u001b[39m\n\u001b[31m  align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;\u001b[39m\n\u001b[31m  padding:24px;width:100%;max-width:480px;display:flex;flex-direction:column;gap:14px;\u001b[39m\n\u001b[31m  max-height:90dvh;overflow-y:auto;-webkit-overflow-scrolling:touch}\u001b[39m\n\u001b[31m.modal-title{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.modal-box input[type=password]{\u001b[39m\n\u001b[31m  width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31m.modal-box input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.action-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px}·\u001b[39m\n\u001b[31m/* ── SHARE PICKER ── */\u001b[39m\n\u001b[31m#screen-share .body{align-items:center;justify-content:center;gap:24px}\u001b[39m\n\u001b[31m.share-prompt{font-size:15px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.share-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;width:100%;max-width:360px}\u001b[39m\n\u001b[31m.share-tile{background:var(--card);border:2px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:24px 8px;display:flex;flex-direction:column;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s}\u001b[39m\n\u001b[31m.share-tile:active{background:var(--border);transform:scale(.97);border-color:var(--accent)}\u001b[39m\n\u001b[31m.share-tile-icon{font-size:36px;line-height:1}\u001b[39m\n\u001b[31m.share-tile-label{font-size:12px;font-weight:700;color:var(--text);text-align:center;\u001b[39m\n\u001b[31m  text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── INSTALL BANNER ── */\u001b[39m\n\u001b[31m#install-banner{display:none;position:fixed;bottom:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:linear-gradient(135deg,#1a1a1a,#0d2010);\u001b[39m\n\u001b[31m  border-top:1px solid var(--accent);padding:14px 16px;\u001b[39m\n\u001b[31m  align-items:center;gap:12px;box-shadow:0 -4px 24px rgba(0,0,0,.7)}\u001b[39m\n\u001b[31m#install-banner.show{display:flex}\u001b[39m\n\u001b[31m#install-banner .ib-icon{font-size:30px;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-text{flex:1}\u001b[39m\n\u001b[31m#install-banner .ib-title{font-size:14px;font-weight:700;color:var(--text)}\u001b[39m\n\u001b[31m#install-banner .ib-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#install-banner .ib-btn{background:var(--accent);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-close{background:none;border:none;color:var(--muted);font-size:20px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── UPDATE BANNER ── */\u001b[39m\n\u001b[31m#update-banner{display:none;position:fixed;top:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:#1a2a1a;border-bottom:2px solid var(--success);\u001b[39m\n\u001b[31m  padding:12px 16px;align-items:center;gap:12px}\u001b[39m\n\u001b[31m#update-banner.show{display:flex}\u001b[39m\n\u001b[31m#update-banner .ub-text{flex:1;font-size:14px;font-weight:600;color:var(--text)}\u001b[39m\n\u001b[31m#update-banner .ub-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#update-banner .ub-btn{background:var(--success);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:8px 14px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#update-banner .ub-close{background:none;border:none;color:var(--muted);font-size:18px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#222;color:#fff;padding:10px 20px;border-radius:20px;font-size:14px;font-weight:600;\u001b[39m\n\u001b[31m  opacity:0;transition:.3s;pointer-events:none;white-space:nowrap;z-index:500}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  padding:40px;color:var(--muted);font-size:14px;gap:12px}\u001b[39m\n\u001b[31m.spinner{width:36px;height:36px;border:3px solid var(--border);border-top-color:var(--accent);\u001b[39m\n\u001b[31m  border-radius:50%;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* ── CARD (jobs / campaign screens) ── */\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius)}\u001b[39m\n\u001b[31m.card.tappable:active{border-color:var(--accent);opacity:.85}·\u001b[39m\n\u001b[31m/* ── STATUS CHIPS (tour / campaign) ── */\u001b[39m\n\u001b[31m.status-chip{font-size:11px;padding:4px 11px;border-radius:8px;cursor:pointer;\u001b[39m\n\u001b[31m  background:var(--card);color:var(--text);border:1px solid var(--border);font-weight:600;\u001b[39m\n\u001b[31m  font-family:var(--font);transition:.15s}\u001b[39m\n\u001b[31m.status-chip.sel{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.status-chip:active{opacity:.8}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SPLASH\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-splash\\\" class=\\\"screen active\\\">\u001b[39m\n\u001b[31m  <div class=\\\"splash-logo\\\">360LM</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-sub\\\">Operations Hub</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-dot\\\" style=\\\"margin-top:16px\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     LOGIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-login\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-wrap\\\">\u001b[39m\n\u001b[31m    <div class=\\\"login-logo\\\">360LM</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">Operations Hub</div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Select Your Name</label>\u001b[39m\n\u001b[31m      <select id=\\\"login-name\\\"><option value=\\\"\\\">— Choose employee —</option><option value=\\\"amit\\\">Amit</option><option value=\\\"ashok\\\">Ashok</option><option value=\\\"gurpreet\\\">Gurpreet</option><option value=\\\"harish\\\">Harish Kumar Lal</option><option value=\\\"mukesh\\\">Mukesh</option><option value=\\\"narender\\\">Narender</option><option value=\\\"pramod\\\">Pramod Narang</option><option value=\\\"rakesh\\\">Rakesh</option><option value=\\\"sachin\\\">Sachin</option><option value=\\\"shambhu\\\">Shambhu</option><option value=\\\"umashankar\\\">Umashankar</option></select>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m      <label style=\\\"text-align:center\\\">Enter PIN</label>\u001b[39m\n\u001b[31m      <div class=\\\"pin-display\\\" id=\\\"pin-display\\\">\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd0\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd1\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd2\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd3\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"login-err\\\" id=\\\"login-err\\\"></div>\u001b[39m\n\u001b[31m      <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-1\\\" data-k=\\\"1\\\" aria-label=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-2\\\" data-k=\\\"2\\\" aria-label=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-3\\\" data-k=\\\"3\\\" aria-label=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-4\\\" data-k=\\\"4\\\" aria-label=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-5\\\" data-k=\\\"5\\\" aria-label=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-6\\\" data-k=\\\"6\\\" aria-label=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-7\\\" data-k=\\\"7\\\" aria-label=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-8\\\" data-k=\\\"8\\\" aria-label=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-9\\\" data-k=\\\"9\\\" aria-label=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key clr\\\" id=\\\"pk-C\\\" data-k=\\\"C\\\" aria-label=\\\"Clear\\\">CLR</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-0\\\" data-k=\\\"0\\\" aria-label=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key back\\\" id=\\\"pk-B\\\" data-k=\\\"B\\\" aria-label=\\\"Backspace\\\">⌫</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     HOME  (built dynamically)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\" id=\\\"home-header\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"home-body\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SHARE PICKER\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-share\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>Share Image To</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">Choose where to send this photo</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"share-prompt\\\">Which tool should receive this image?</div>\u001b[39m\n\u001b[31m    <div class=\\\"share-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-expense\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🧾</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Expenses</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-recce\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">📍</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Recce</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-installation\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🔧</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Installation</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     TEAM PINs  (admin only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-team\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-back\\\" style=\\\"font-size:20px\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Team PINs</h1><div class=\\\"sub\\\">Set employee login PINs</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-refresh\\\" title=\\\"Refresh\\\">🔄</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"info-note\\\">PINs are stored encrypted — current PIN cannot be viewed. You can set a new PIN for any employee below.</div>\u001b[39m\n\u001b[31m    <div id=\\\"team-list\\\"></div>·\u001b[39m\n\u001b[31m    <!-- Icon Admin — Harish & Pramod only -->\u001b[39m\n\u001b[31m    <div id=\\\"icon-admin-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:8px\\\">🎨 PWA Icons (Admin)</div>\u001b[39m\n\u001b[31m      <div class=\\\"info-note\\\" style=\\\"margin-bottom:10px\\\">Upload a new icon for any PWA. Upload both 192×192 and 512×512 sizes. PNG/JPG/WEBP accepted — auto-resized on server.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;flex-direction:column;gap:10px\\\">·\u001b[39m\n\u001b[31m        <!-- Secret management -->\u001b[39m\n\u001b[31m        <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:12px;padding:14px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px\\\">🔑 Admin Secret</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:center;gap:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"font-family:monospace;font-size:13px;color:var(--accent);background:var(--bg);\u001b[39m\n\u001b[31m              border-radius:6px;padding:8px 12px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\" id=\\\"icon-secret-display\\\">—</div>\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"toggleSecretVisible()\\\" id=\\\"btn-secret-show\\\">Show</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:10px\\\">Change secret (min 16 chars):</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m            <input id=\\\"icon-new-secret\\\" type=\\\"text\\\" placeholder=\\\"Enter new secret…\\\" style=\\\"flex:1;background:var(--bg);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m              padding:10px 12px;border-radius:8px;font-size:13px;font-family:monospace;outline:none\\\">\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"changeIconSecret()\\\" style=\\\"white-space:nowrap\\\">Update</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div id=\\\"icon-secret-err\\\" style=\\\"font-size:11px;color:var(--danger);min-height:14px;margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>·\u001b[39m\n\u001b[31m        <!-- Upload form -->\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Select PWA</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-pwa-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"hub\\\">Hub</option>\u001b[39m\n\u001b[31m            <option value=\\\"expense\\\">Expenses</option>\u001b[39m\n\u001b[31m            <option value=\\\"finance\\\">Finance</option>\u001b[39m\n\u001b[31m            <option value=\\\"installation\\\">Installation</option>\u001b[39m\n\u001b[31m            <option value=\\\"recce\\\">Recce</option>\u001b[39m\n\u001b[31m            <option value=\\\"dispatch\\\">Dispatch</option>\u001b[39m\n\u001b[31m            <option value=\\\"production\\\">Production</option>\u001b[39m\n\u001b[31m            <option value=\\\"vehicle\\\">Vehicle Log</option>\u001b[39m\n\u001b[31m            <option value=\\\"vrs\\\">Vehicle Request</option>\u001b[39m\n\u001b[31m            <option value=\\\"sales\\\">Sales</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Icon Size</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-size-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"192\\\">192 × 192 (standard)</option>\u001b[39m\n\u001b[31m            <option value=\\\"512\\\">512 × 512 (large)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Choose Image File</label>\u001b[39m\n\u001b[31m          <input type=\\\"file\\\" id=\\\"icon-file-input\\\" accept=\\\"image/png,image/jpeg,image/webp\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:14px;width:100%\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-preview-wrap\\\" style=\\\"display:none;text-align:center\\\">\u001b[39m\n\u001b[31m          <img id=\\\"icon-preview-img\\\" style=\\\"width:96px;height:96px;border-radius:16px;border:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-upload-err\\\" style=\\\"font-size:12px;color:var(--danger);min-height:16px\\\"></div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" onclick=\\\"uploadIcon()\\\">Upload Icon</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     MODAL: Admin set PIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-setpin\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🔑 Set PIN — <span id=\\\"setpin-target-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>New PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Enter new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-confirm-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Confirm New PIN</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-confirm\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Repeat new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-auth-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Your own current PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"setpin-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-setpin-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-setpin-save\\\">Set PIN</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     JOBS — Campaign list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-jobs\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Jobs</h1><div class=\\\"sub\\\">Campaigns &amp; Tours</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh jobs\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-campaign\\\" title=\\\"New Campaign\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"jobs-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     CAMPAIGN DETAIL — Tour list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-campaign\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 id=\\\"campaign-hdr-title\\\">Campaign</h1><div class=\\\"sub\\\" id=\\\"campaign-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh campaign\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-tour\\\" title=\\\"New Tour\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"campaign-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Campaign -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-campaign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">📋 New Campaign</div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Year</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-year\\\" min=\\\"2024\\\" max=\\\"2030\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Phase #</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-phase\\\" value=\\\"1\\\" min=\\\"1\\\" max=\\\"99\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Brand <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-brand\\\" placeholder=\\\"e.g. Lenovo\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Client <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-client\\\" placeholder=\\\"e.g. Lenovo India\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Campaign Name <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-campaign\\\" placeholder=\\\"e.g. Chandigarh BTL\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Generated Campaign ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nc-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nc-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nc-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nc-save\\\">Create</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Tour -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-tour\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px;max-height:88vh;overflow-y:auto\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🗺️ New Tour</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Tour Title</div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nt-title\\\" placeholder=\\\"e.g. Chandigarh Week 1\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Start Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-start\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">End Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-end\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:6px\\\">Assign Employees <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-employees\\\" style=\\\"display:flex;flex-direction:column;gap:6px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"color:var(--muted);font-size:13px\\\">Loading…</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Notes</div>\u001b[39m\n\u001b[31m      <textarea id=\\\"nt-notes\\\" rows=\\\"2\\\" placeholder=\\\"Optional instructions or remarks\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px;font-family:inherit;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Tour ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-id-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nt-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nt-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nt-save\\\">Create Tour</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- UPDATE BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"update-banner\\\">\u001b[39m\n\u001b[31m  <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ub-text\\\">App update available</div>\u001b[39m\n\u001b[31m    <div class=\\\"ub-sub\\\">Tap to refresh and get the latest version</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ub-btn\\\" id=\\\"btn-update\\\">Update now</button>\u001b[39m\n\u001b[31m  <button class=\\\"ub-close\\\" id=\\\"btn-update-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- INSTALL BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"install-banner\\\">\u001b[39m\n\u001b[31m  <div class=\\\"ib-icon\\\">📲</div>\u001b[39m\n\u001b[31m  <div class=\\\"ib-text\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ib-title\\\">Install 360LM Hub</div>\u001b[39m\n\u001b[31m    <div class=\\\"ib-sub\\\">Add to home screen for the best experience</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ib-btn\\\" id=\\\"btn-install\\\">Install</button>\u001b[39m\n\u001b[31m  <button class=\\\"ib-close\\\" id=\\\"btn-install-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: AI Config (mgmt only) -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-ai\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🤖 AI Config — Screenshot Extraction</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Tesseract OCR</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-tesseract\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Extraction Fallback</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-enabled\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Model</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-model\\\">\u001b[39m\n\u001b[31m        <option value=\\\"haiku\\\">Haiku (fast, cost-effective)</option>\u001b[39m\n\u001b[31m        <option value=\\\"sonnet\\\">Sonnet (higher accuracy)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-ai-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-ai-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"border-top:1px solid var(--border);padding-top:12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">API Key Health</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px;margin-bottom:10px\\\">Live check — result is always the same for all users</div>\u001b[39m\n\u001b[31m      <div id=\\\"api-key-status-wrap\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-check-keys\\\" style=\\\"margin-top:10px;width:100%\\\">Check API Keys</button>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:14px;border-top:1px solid var(--border);padding-top:12px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">View Usage &amp; Billing</div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Anthropic</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/usage\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Google Maps</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/google/maps-apis/api-list\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: Hub Quick-Action Config -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-qa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">⚡ Quick Actions</div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-config-list\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-cfg-err\\\" style=\\\"color:var(--danger);font-size:13px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-qa-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-qa-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SCRIPT\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31m/* ── CONFIG ── */\u001b[39m\n\u001b[31mconst CFG = {\u001b[39m\n\u001b[31m  api:        '/db',\u001b[39m\n\u001b[31m  apiHeaders: { 'Content-Type': 'application/json', 'Accept-Profile': 'expense' },\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31m/* ── SERVER ACCESS MAP (fetched from hub-access.json, overrides ACCESS for non-mgmt) ── */\u001b[39m\n\u001b[31mlet _serverAccess = {};\u001b[39m\n\u001b[31masync function loadHubAccess() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/hub/hub-access.json?t=' + Date.now());\u001b[39m\n\u001b[31m    if (r.ok) _serverAccess = await r.json();\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── ROLE ACCESS MAP (hardcoded fallback) ── */\u001b[39m\n\u001b[31mconst ACCESS = {\u001b[39m\n\u001b[31m  harish:     ['expense','installation','finance','recce','dispatch','production','jobs','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  pramod:     ['expense','installation','finance','recce','dispatch','production','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  rakesh:     ['installation','expense','production','jobs','vehicle','vrs','sales','hr'],\u001b[39m\n\u001b[31m  ashok:      ['expense','production','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  shambhu:    ['finance','expense','vehicle','vrs','stores','activity','hr'],\u001b[39m\n\u001b[31m  gurpreet:   ['expense','dispatch','vehicle','vrs','stores','hr'],\u001b[39m\n\u001b[31m  sachin:     ['installation','expense','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  mukesh:     ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  umashankar: ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  narender:   ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  amit:       ['vehicle','vrs','hr'],\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst TILES = [\u001b[39m\n\u001b[31m  { id:'expense',      label:'Expenses',     icon:'🧾', url:'/expense/',               live:true  },\u001b[39m\n\u001b[31m  { id:'installation', label:'Installation', icon:'🔧', url:'/installation/current/',  live:true  },\u001b[39m\n\u001b[31m  { id:'finance',      label:'Finance',      icon:'💰', url:'/finance/',               live:true  },\u001b[39m\n\u001b[31m  { id:'recce',        label:'Recce',        icon:'📍', url:'/recce/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'dispatch',     label:'Dispatch',     icon:'🚚', url:'/dispatch/',              live:true  },\u001b[39m\n\u001b[31m  { id:'production',   label:'Production',   icon:'⚙️', url:'/production/',            live:true  },\u001b[39m\n\u001b[31m  { id:'jobs',         label:'Jobs',         icon:'📋', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'vehicle',      label:'Vehicle Log',  icon:'🚐', url:'/vehicle/',               live:true  },\u001b[39m\n\u001b[31m  { id:'vrs',          label:'Veh. Request', icon:'📋', url:'/vrs/',                   live:true  },\u001b[39m\n\u001b[31m  { id:'sales',        label:'Sales',        icon:'💼', url:'/sales/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'stores',       label:'Stores',       icon:'📦', url:'/stores/',                live:true  },\u001b[39m\n\u001b[31m  { id:'client',       label:'Client',       icon:'👤', url:'/client/',                live:true  },\u001b[39m\n\u001b[31m  { id:'team-pins',    label:'Team PINs',    icon:'🔑', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'admin',        label:'Admin',        icon:'🛠️', url:'/admin/',                  live:true  },\u001b[39m\n\u001b[31m  { id:'activity',     label:'Activity',     icon:'🎯', url:'/activity/',               live:true  },\u001b[39m\n\u001b[31m  { id:'hr',           label:'HR',           icon:'👥', url:'/hr/',                     live:true  },\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31mconst MGMT = ['harish', 'pramod'];·\u001b[39m\n\u001b[31m/* ── STATE ── */\u001b[39m\n\u001b[31mlet currentUser     = null;\u001b[39m\n\u001b[31mlet pinBuffer       = '';\u001b[39m\n\u001b[31mlet setPinTarget    = null;\u001b[39m\n\u001b[31mlet pendingShare    = false;\u001b[39m\n\u001b[31mlet _loginAttempts  = 0;\u001b[39m\n\u001b[31mlet _lockoutUntil   = 0;\u001b[39m\n\u001b[31mlet _loginInFlight  = false;·\u001b[39m\n\u001b[31m/* ── UTILS ── */\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => { s.classList.remove('active'); s.style.display = ''; });\u001b[39m\n\u001b[31m  document.getElementById('screen-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet toastTimer;\u001b[39m\n\u001b[31mfunction toast(msg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.classList.add('show');\u001b[39m\n\u001b[31m  clearTimeout(toastTimer);\u001b[39m\n\u001b[31m  toastTimer = setTimeout(() => el.classList.remove('show'), 2800);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fmtINR(n) {\u001b[39m\n\u001b[31m  if (n >= 100000) return '₹' + (n / 100000).toFixed(1) + 'L';\u001b[39m\n\u001b[31m  if (n >= 1000)   return '₹' + (n / 1000).toFixed(1) + 'K';\u001b[39m\n\u001b[31m  return '₹' + Math.round(n);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SESSION ── */\u001b[39m\n\u001b[31mconst SESSION_KEY  = 'lm360-session';\u001b[39m\n\u001b[31mconst EXP_SES_KEY  = '360exp-session';\u001b[39m\n\u001b[31mconst SESSION_TTL  = 12 * 3600 * 1000;·\u001b[39m\n\u001b[31mfunction saveSession(user) {\u001b[39m\n\u001b[31m  const payload = { empId: user.id, name: user.name, role: user.role, loginAt: new Date().toISOString(), source: 'hub' };\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m  localStorage.setItem(EXP_SES_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearSession() {\u001b[39m\n\u001b[31m  localStorage.removeItem(SESSION_KEY);\u001b[39m\n\u001b[31m  localStorage.removeItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const raw = localStorage.getItem(SESSION_KEY) || localStorage.getItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m    if (!raw) return null;\u001b[39m\n\u001b[31m    const s = JSON.parse(raw);\u001b[39m\n\u001b[31m    if (!s.empId || !s.name || !s.role) return null;\u001b[39m\n\u001b[31m    if (Date.now() - new Date(s.loginAt).getTime() > SESSION_TTL) { clearSession(); return null; }\u001b[39m\n\u001b[31m    return s;\u001b[39m\n\u001b[31m  } catch (_) { return null; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGIN FLOW ── */\u001b[39m\n\u001b[31masync function initLogin() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('login-name');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— Choose employee —</option>'; // clear before repopulating\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    emps.forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    [\u001b[39m\n\u001b[31m      { id:'amit',        name:'Amit'             },\u001b[39m\n\u001b[31m      { id:'ashok',       name:'Ashok'            },\u001b[39m\n\u001b[31m      { id:'gurpreet',    name:'Gurpreet'         },\u001b[39m\n\u001b[31m      { id:'harish',      name:'Harish Kumar Lal' },\u001b[39m\n\u001b[31m      { id:'mukesh',      name:'Mukesh'           },\u001b[39m\n\u001b[31m      { id:'narender',    name:'Narender'         },\u001b[39m\n\u001b[31m      { id:'pramod',      name:'Pramod Narang'    },\u001b[39m\n\u001b[31m      { id:'rakesh',      name:'Rakesh'           },\u001b[39m\n\u001b[31m      { id:'sachin',      name:'Sachin'           },\u001b[39m\n\u001b[31m      { id:'shambhu',     name:'Shambhu'          },\u001b[39m\n\u001b[31m      { id:'umashankar',  name:'Umashankar'       },\u001b[39m\n\u001b[31m    ].forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  document.querySelectorAll('.pin-key').forEach(btn =>\u001b[39m\n\u001b[31m    btn.addEventListener('click', () => handlePinKey(btn.dataset.k))\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handlePinKey(k) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (_loginInFlight) return;\u001b[39m\n\u001b[31m  // Lockout guard\u001b[39m\n\u001b[31m  if (_lockoutUntil && Date.now() < _lockoutUntil) {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    errEl.textContent = `Too many attempts — wait ${secs}s`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (_lockoutUntil) { _lockoutUntil = 0; _loginAttempts = 0; }\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (k === 'C') { pinBuffer = ''; }\u001b[39m\n\u001b[31m  else if (k === 'B') { pinBuffer = pinBuffer.slice(0, -1); }\u001b[39m\n\u001b[31m  else {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (!empId) { toast('Select your name first'); return; }\u001b[39m\n\u001b[31m    if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  if (pinBuffer.length === 4) {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (empId) attemptLogin(empId, pinBuffer);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updatePinDisplay() {\u001b[39m\n\u001b[31m  for (let i = 0; i < 4; i++)\u001b[39m\n\u001b[31m    document.getElementById('pd' + i).classList.toggle('filled', i < pinBuffer.length);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _shakePin() {\u001b[39m\n\u001b[31m  const pd = document.getElementById('pin-display');\u001b[39m\n\u001b[31m  pd.classList.add('shake');\u001b[39m\n\u001b[31m  setTimeout(() => pd.classList.remove('shake'), 400);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _startLockout() {\u001b[39m\n\u001b[31m  _lockoutUntil = Date.now() + 30000;\u001b[39m\n\u001b[31m  _loginAttempts = 0;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  const tick = () => {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    if (secs > 0) { errEl.textContent = `Too many attempts — wait ${secs}s`; setTimeout(tick, 500); }\u001b[39m\n\u001b[31m    else { _lockoutUntil = 0; errEl.textContent = ''; }\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  tick();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function attemptLogin(empId, pin) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  _loginInFlight = true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pin }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      _loginAttempts++;\u001b[39m\n\u001b[31m      _shakePin();\u001b[39m\n\u001b[31m      if (_loginAttempts >= 5) { _startLockout(); return; }\u001b[39m\n\u001b[31m      errEl.textContent = `Wrong PIN — ${5 - _loginAttempts} attempt${5 - _loginAttempts !== 1 ? 's' : ''} left`;\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    currentUser = { id: user.id, name: user.name, role: user.role };\u001b[39m\n\u001b[31m    saveSession(currentUser);\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — check connection';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    _loginInFlight = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── HOME SCREEN ── */\u001b[39m\n\u001b[31masync function buildHome() {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const body   = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  // Management users always use hardcoded ACCESS; others prefer server-side hub-access.json\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const tiles  = accessIds.map(id => TILES.find(t => t.id === id)).filter(Boolean);·\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"avatar\\\">${initials}</div>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1 style=\\\"font-size:16px\\\">${currentUser.name}</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">${currentUser.role}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${isMgmt ? '<button class=\\\"btn-icon\\\" id=\\\"btn-hub-ai\\\" title=\\\"AI Config\\\" style=\\\"font-size:18px\\\">🤖</button>' : ''}\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m  if (isMgmt) document.getElementById('btn-hub-ai').addEventListener('click', openHubAiConfig);·\u001b[39m\n\u001b[31m  if (isMgmt) {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"hub-main-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-dash\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">📊</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">Dashboard</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-tools\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">🧰</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">All Tools</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-header\\\">\u001b[39m\n\u001b[31m        <div class=\\\"section-title\\\" style=\\\"margin:0\\\">Quick Actions</div>\u001b[39m\n\u001b[31m        <button class=\\\"btn-icon\\\" id=\\\"btn-hub-qa-cfg\\\" title=\\\"Configure Quick Actions\\\" style=\\\"font-size:18px;padding:8px;min-width:44px;min-height:44px\\\">⚙️</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-grid\\\" id=\\\"hub-qa-grid\\\"></div>`;\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-dash').addEventListener('click',  () => enterHubDash(tiles));\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-tools').addEventListener('click', () => enterHubTools(tiles));\u001b[39m\n\u001b[31m    document.getElementById('btn-hub-qa-cfg').addEventListener('click', openHubQaConfig);\u001b[39m\n\u001b[31m    renderHubQA();\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"tile-grid field\\\" id=\\\"tile-grid\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"text-align:center;font-size:12px;color:var(--muted);padding-top:8px\\\">Tap a tile to open your tool</div>`;\u001b[39m\n\u001b[31m    renderTiles(tiles, 'field');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderTiles(tiles, mode) {\u001b[39m\n\u001b[31m  const grid = document.getElementById('tile-grid');\u001b[39m\n\u001b[31m  tiles.forEach(tile => {\u001b[39m\n\u001b[31m    const div = document.createElement('div');\u001b[39m\n\u001b[31m    div.className = 'tile ' + mode + (tile.live ? '' : ' coming');\u001b[39m\n\u001b[31m    div.innerHTML = `<div class=\\\"tile-icon\\\">${tile.icon}</div><div class=\\\"tile-label\\\">${tile.label}</div>` +\u001b[39m\n\u001b[31m      (tile.live ? '' : '<div class=\\\"soon-badge\\\">Soon</div>');\u001b[39m\n\u001b[31m    div.addEventListener('click', () => handleTile(tile));\u001b[39m\n\u001b[31m    grid.appendChild(div);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handleTile(tile) {\u001b[39m\n\u001b[31m  if (!tile.live) { toast('Coming soon'); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'team-pins') { enterTeamPins(); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'jobs')      { enterJobs();     return; }\u001b[39m\n\u001b[31m  window.location.href = tile.url;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT SUB-VIEWS ── */\u001b[39m\n\u001b[31mfunction _mgmtHeader(title) {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-subview-back\\\" style=\\\"font-size:20px;font-weight:900\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 style=\\\"font-size:16px\\\">${title}</h1></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-subview-back').addEventListener('click', () => buildHome());\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubDash(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('Dashboard');\u001b[39m\n\u001b[31m  const hdr = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const refreshBtn = document.createElement('button');\u001b[39m\n\u001b[31m  refreshBtn.className = 'btn-icon';\u001b[39m\n\u001b[31m  refreshBtn.title = 'Refresh dashboard';\u001b[39m\n\u001b[31m  refreshBtn.setAttribute('aria-label', 'Refresh dashboard');\u001b[39m\n\u001b[31m  refreshBtn.style.cssText = 'font-size:18px';\u001b[39m\n\u001b[31m  refreshBtn.textContent = '🔄';\u001b[39m\n\u001b[31m  refreshBtn.addEventListener('click', loadKPIs);\u001b[39m\n\u001b[31m  hdr.insertBefore(refreshBtn, hdr.querySelector('#btn-logout'));\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Actions Required</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-actions\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Operations</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row kpi-4col\\\" id=\\\"kpi-ops\\\">${skel}${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Production</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-prod\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Installation</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-inst\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">HR</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-hr\\\">${skel}${skel}${skel}</div>`;\u001b[39m\n\u001b[31m  loadKPIs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubTools(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('All Tools');\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  body.innerHTML = `<div class=\\\"tile-grid mgmt\\\" id=\\\"tile-grid\\\"></div>`;\u001b[39m\n\u001b[31m  renderTiles(tiles, 'mgmt');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT AI CONFIG ── */\u001b[39m\n\u001b[31mconst HUB_FIN_HDR = { 'Content-Type': 'application/json', 'Accept-Profile': 'finance' };·\u001b[39m\n\u001b[31masync function openHubAiConfig() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r    = await fetch(`${CFG.api}/upi_config`, { headers: HUB_FIN_HDR });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const get  = key => rows.find(x => x.key === key)?.value;\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-tesseract').value = get('tesseract_enabled') || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-enabled').value   = get('ai_enabled')        || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-model').value     = get('ai_model')           || 'haiku';\u001b[39m\n\u001b[31m  } catch(_) { toast('Could not load AI config'); return; }\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-ai').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveHubAiConfig() {\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-hub-ai-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  const pairs = [\u001b[39m\n\u001b[31m    { key:'tesseract_enabled', value: document.getElementById('hub-ai-tesseract').value },\u001b[39m\n\u001b[31m    { key:'ai_enabled',        value: document.getElementById('hub-ai-enabled').value },\u001b[39m\n\u001b[31m    { key:'ai_model',          value: document.getElementById('hub-ai-model').value },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const results = await Promise.all(pairs.map(p =>\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/upi_config?key=eq.${p.key}`, {\u001b[39m\n\u001b[31m        method: 'PATCH',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'finance', 'Prefer': 'return=minimal' },\u001b[39m\n\u001b[31m        body: JSON.stringify({ value: p.value }),\u001b[39m\n\u001b[31m      })\u001b[39m\n\u001b[31m    ));\u001b[39m\n\u001b[31m    if (results.some(r => !r.ok)) throw new Error('partial failure');\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show');\u001b[39m\n\u001b[31m    toast('✅ AI config saved');\u001b[39m\n\u001b[31m  } catch(_) { toast('Save failed'); }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Save'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function checkApiKeyStatus() {\u001b[39m\n\u001b[31m  const btn  = document.getElementById('btn-check-keys');\u001b[39m\n\u001b[31m  const wrap = document.getElementById('api-key-status-wrap');\u001b[39m\n\u001b[31m  btn.textContent = 'Checking…';\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  wrap.innerHTML = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/tour-ai/key-status');\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Proxy error ' + r.status);\u001b[39m\n\u001b[31m    const d = await r.json();\u001b[39m\n\u001b[31m    function mkBadge(info, label, link) {\u001b[39m\n\u001b[31m      const col  = info.ok ? 'var(--success)' : 'var(--danger)';\u001b[39m\n\u001b[31m      const icon = info.ok ? '✅' : '❌';\u001b[39m\n\u001b[31m      const note = info.ok ? (info.model || 'Valid') : (info.error || 'Error');\u001b[39m\n\u001b[31m      const hint = info.hint ? ' · key ' + info.hint : '';\u001b[39m\n\u001b[31m      return `<div style=\\\"background:var(--card);border:1px solid var(--border);border-left:3px solid ${col};border-radius:8px;padding:10px 12px;display:flex;align-items:center;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:13px;font-weight:600;color:var(--text)\\\">${icon} ${label}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${note}${hint}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <a href=\\\"${link}\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:11px;color:var(--info);text-decoration:none;white-space:nowrap\\\">Billing ↗</a>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    wrap.innerHTML =\u001b[39m\n\u001b[31m      mkBadge(d.anthropic, 'Anthropic API',    'https://console.anthropic.com/settings/billing') +\u001b[39m\n\u001b[31m      mkBadge(d.maps,      'Google Maps API',  'https://console.cloud.google.com/billing');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    wrap.innerHTML = `<div style=\\\"font-size:12px;color:var(--danger)\\\">Error: ${e.message}</div>`;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.textContent = 'Check Again';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT QUICK ACTIONS ── */\u001b[39m\n\u001b[31mconst HUB_QA_DEFS = [\u001b[39m\n\u001b[31m  { id:'offer',       icon:'📋', label:'New Offer',    accessId:'sales',        fn:() => { window.location.href='/sales/'; } },\u001b[39m\n\u001b[31m  { id:'paytm',       icon:'📱', label:'UPI Pay',       accessId:'finance',      fn:() => { window.location.href='/finance/?qa=upi'; } },\u001b[39m\n\u001b[31m  { id:'expense',     icon:'🧾', label:'Expenses',      accessId:'expense',      fn:() => { window.location.href='/expense/'; } },\u001b[39m\n\u001b[31m  { id:'finance',     icon:'💰', label:'Finance',       accessId:'finance',      fn:() => { window.location.href='/finance/'; } },\u001b[39m\n\u001b[31m  { id:'dispatch',    icon:'🚚', label:'Dispatch',      accessId:'dispatch',     fn:() => { window.location.href='/dispatch/'; } },\u001b[39m\n\u001b[31m  { id:'stores',      icon:'📦', label:'Stores',        accessId:'stores',       fn:() => { window.location.href='/stores/'; } },\u001b[39m\n\u001b[31m  { id:'recce',       icon:'📍', label:'Recce',         accessId:'recce',        fn:() => { window.location.href='/recce/'; } },\u001b[39m\n\u001b[31m  { id:'production',  icon:'⚙️', label:'Production',   accessId:'production',   fn:() => { window.location.href='/production/'; } },\u001b[39m\n\u001b[31m  { id:'tours',       icon:'🗺️', label:'Tour Plan',   accessId:'installation', fn:() => { window.location.href='/tour-planner/'; } },\u001b[39m\n\u001b[31m  { id:'installation',icon:'🔧', label:'Installation',  accessId:'installation', fn:() => { window.location.href='/installation/current/'; } },\u001b[39m\n\u001b[31m  { id:'activity',    icon:'🎯', label:'Activity',      accessId:'activity',     fn:() => { window.location.href='/activity/'; } },\u001b[39m\n\u001b[31m];\u001b[39m\n\u001b[31mconst HUB_QA_DEFAULT = ['offer', 'paytm'];\u001b[39m\n\u001b[31mconst HUB_QA_STORE   = '360lm-hub-qa';\u001b[39m\n\u001b[31mlet   _hubQaTempSel  = [];·\u001b[39m\n\u001b[31mfunction getHubQA() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(HUB_QA_STORE));\u001b[39m\n\u001b[31m    if (Array.isArray(s) && s.length) return s;\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m  return [...HUB_QA_DEFAULT];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHubQA() {\u001b[39m\n\u001b[31m  const ids  = getHubQA();\u001b[39m\n\u001b[31m  const grid = document.getElementById('hub-qa-grid');\u001b[39m\n\u001b[31m  if (!grid) return;\u001b[39m\n\u001b[31m  if (!ids.length) {\u001b[39m\n\u001b[31m    grid.innerHTML = `<div style=\\\"color:var(--muted);font-size:13px;grid-column:1/-1;\u001b[39m\n\u001b[31m      text-align:center;padding:16px\\\">Tap ⚙️ to add quick actions</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  grid.innerHTML = ids.map(id => {\u001b[39m\n\u001b[31m    const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!d) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-tile\\\" onclick=\\\"hubQaRun('${id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction hubQaRun(id) {\u001b[39m\n\u001b[31m  const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m  if (d) d.fn();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openHubQaConfig() {\u001b[39m\n\u001b[31m  _hubQaTempSel = [...getHubQA()];\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const visibleDefs = HUB_QA_DEFS.filter(d => accessIds.includes(d.accessId));\u001b[39m\n\u001b[31m  const list = document.getElementById('hub-qa-config-list');\u001b[39m\n\u001b[31m  list.innerHTML = visibleDefs.map(d => {\u001b[39m\n\u001b[31m    const sel = _hubQaTempSel.includes(d.id);\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-item${sel?' selected':''}\\\" id=\\\"hub-qa-item-${d.id}\\\"\u001b[39m\n\u001b[31m        onclick=\\\"toggleHubQaItem('${d.id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-toggle\\\"></div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleHubQaItem(id) {\u001b[39m\n\u001b[31m  const idx = _hubQaTempSel.indexOf(id);\u001b[39m\n\u001b[31m  if (idx >= 0) {\u001b[39m\n\u001b[31m    _hubQaTempSel.splice(idx, 1);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    if (_hubQaTempSel.length >= 6) {\u001b[39m\n\u001b[31m      document.getElementById('hub-qa-cfg-err').textContent = 'Maximum 6 quick actions';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _hubQaTempSel.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  const el = document.getElementById('hub-qa-item-' + id);\u001b[39m\n\u001b[31m  if (el) el.className = 'hub-qa-item' + (_hubQaTempSel.includes(id) ? ' selected' : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction saveHubQaConfig() {\u001b[39m\n\u001b[31m  localStorage.setItem(HUB_QA_STORE, JSON.stringify(_hubQaTempSel));\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.remove('show');\u001b[39m\n\u001b[31m  renderHubQA();\u001b[39m\n\u001b[31m  toast('✅ Quick actions updated');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── KPI FETCH ── */\u001b[39m\n\u001b[31mfunction _hdr(schema) {\u001b[39m\n\u001b[31m  return { 'Content-Type': 'application/json', 'Accept-Profile': schema };\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _safe(p) { return p.catch(() => null); }·\u001b[39m\n\u001b[31masync function loadKPIs() {\u001b[39m\n\u001b[31m  const mon = firstOfMonth();\u001b[39m\n\u001b[31m  const curMonth = new Date().toISOString().slice(0, 7); // YYYY-MM\u001b[39m\n\u001b[31m  const todayIST = (() => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; })();·\u001b[39m\n\u001b[31m  // Reset sections to loading skeletons before fetching (makes refresh visible)\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  [['kpi-actions',3],['kpi-ops',4],['kpi-prod',3],['kpi-inst',3],['kpi-hr',3]].forEach(([id,n]) => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    if (el) el.innerHTML = skel.repeat(n);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  const [\u001b[39m\n\u001b[31m    sheetsRes, claimsRes, salaryRes,\u001b[39m\n\u001b[31m    cashRes,   dispRes,   recceRes,  prodRes,\u001b[39m\n\u001b[31m    instJobsRes, instCtrsRes, storeItemsRes,\u001b[39m\n\u001b[31m    hrAttRes, hrEmpRes, hrConcernRes, hrSlipsRes\u001b[39m\n\u001b[31m  ] = await Promise.all([\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/sheets?status=in.(submitted,escalated)&select=sheet_id`, { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/approved_pending_reimbursement?select=sheet_id`,         { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_register?status=in.(pending,partial)&month=eq.${curMonth}&select=record_id`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/accounts?account_id=eq.petty-cash&select=current_balance`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/dispatches?status=eq.active&select=dispatch_id`,         { headers: _hdr('dispatch') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/submissions?submitted_at=gte.${mon}&select=sub_id`,      { headers: _hdr('recce') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/orders?status=not.in.(dispatched)&select=order_id,status`, { headers: _hdr('production') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/jobs?select=job_id,status`,                              { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/counters?select=counter_id,status`,                      { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/items?active=eq.true&select=id,current_stock,min_stock`, { headers: _hdr('stores') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance?date=eq.${todayIST}&status=eq.present&select=attendance_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/employee_list?select=id`,                               { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance_concerns?status=eq.open&select=concern_id`,  { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_slips?month=eq.${curMonth}&status=eq.finalized&select=slip_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m  ]);·\u001b[39m\n\u001b[31m  const sheets    = sheetsRes?.ok    ? await sheetsRes.json()    : [];\u001b[39m\n\u001b[31m  const claims    = claimsRes?.ok    ? await claimsRes.json()    : [];\u001b[39m\n\u001b[31m  const salary    = salaryRes?.ok    ? await salaryRes.json()    : [];\u001b[39m\n\u001b[31m  const cashArr   = cashRes?.ok      ? await cashRes.json()      : [];\u001b[39m\n\u001b[31m  const disps     = dispRes?.ok      ? await dispRes.json()      : [];\u001b[39m\n\u001b[31m  const recces    = recceRes?.ok     ? await recceRes.json()     : [];\u001b[39m\n\u001b[31m  const prods     = prodRes?.ok      ? await prodRes.json()      : [];\u001b[39m\n\u001b[31m  const instJobs  = instJobsRes?.ok  ? await instJobsRes.json()  : [];\u001b[39m\n\u001b[31m  const instCtrs  = instCtrsRes?.ok  ? await instCtrsRes.json()  : [];\u001b[39m\n\u001b[31m  const storeItems = storeItemsRes?.ok ? await storeItemsRes.json() : [];\u001b[39m\n\u001b[31m  const hrAtt     = hrAttRes?.ok     ? await hrAttRes.json()     : [];\u001b[39m\n\u001b[31m  const hrEmp     = hrEmpRes?.ok     ? await hrEmpRes.json()     : [];\u001b[39m\n\u001b[31m  const hrConcern = hrConcernRes?.ok ? await hrConcernRes.json() : [];\u001b[39m\n\u001b[31m  const hrSlips   = hrSlipsRes?.ok   ? await hrSlipsRes.json()   : [];·\u001b[39m\n\u001b[31m  const cash = Number(cashArr[0]?.current_balance || 0);·\u001b[39m\n\u001b[31m  const actionsEl = document.getElementById('kpi-actions');\u001b[39m\n\u001b[31m  const opsEl     = document.getElementById('kpi-ops');\u001b[39m\n\u001b[31m  const prodEl    = document.getElementById('kpi-prod');\u001b[39m\n\u001b[31m  const instEl    = document.getElementById('kpi-inst');\u001b[39m\n\u001b[31m  const hrEl      = document.getElementById('kpi-hr');\u001b[39m\n\u001b[31m  if (!actionsEl || !opsEl || !prodEl || !instEl) return;·\u001b[39m\n\u001b[31m  // ── Actions Required ────────────────────────────────────────────\u001b[39m\n\u001b[31m  actionsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${sheets.length ? 'alert' : 'ok'}\\\" data-href=\\\"/expense/?tab=approvals\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${sheets.length ? 'red' : 'green'}\\\">${sheets.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Expense Approvals</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${claims.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=claims\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${claims.length ? 'amber' : 'green'}\\\">${claims.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Claims to Reimburse</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${salary.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=salary\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${salary.length ? 'amber' : 'green'}\\\">${salary.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Salary Due This Month</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Operations ──────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const cashColor = cash < 2000 ? 'red' : cash < 5000 ? 'amber' : 'green';\u001b[39m\n\u001b[31m  const cashCard  = cash < 2000 ? 'alert' : cash < 5000 ? 'warn' : 'ok';\u001b[39m\n\u001b[31m  const lowStock = storeItems.filter(i => i.current_stock < i.min_stock).length;\u001b[39m\n\u001b[31m  opsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${cashCard}\\\" data-href=\\\"/finance/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${cashColor}\\\">${fmtINR(cash)}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">${cash < 0 ? '⚠️ ' : ''}Petty Cash</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/dispatch/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${disps.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Dispatches</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/recce/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${recces.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Recce This Month</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${lowStock > 0 ? 'alert' : 'ok'}\\\" data-href=\\\"/stores/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${lowStock > 0 ? 'red' : 'green'}\\\">${lowStock}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Low Stock Items</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Production ───────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const prodPending = prods.filter(o => o.status === 'pending').length;\u001b[39m\n\u001b[31m  const prodActive  = prods.filter(o => o.status === 'in_production' || o.status === 'quality_check').length;\u001b[39m\n\u001b[31m  const prodReady   = prods.filter(o => o.status === 'ready').length;\u001b[39m\n\u001b[31m  prodEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodPending ? 'warn' : 'ok'}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodPending ? 'amber' : 'green'}\\\">${prodPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Pending Orders</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${prodActive}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">In Production</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodReady ? 'ok' : ''}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodReady ? 'green' : ''}\\\">${prodReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Ready to Dispatch</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Installation ─────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const activeJobs  = instJobs.filter(j => j.status === 'active').length;\u001b[39m\n\u001b[31m  const ctrsPending = instCtrs.filter(c => c.status === 'recce_pending' || c.status === 'mockup_pending' || c.status === 'unclear').length;\u001b[39m\n\u001b[31m  const ctrsReady   = instCtrs.filter(c => c.status === 'ready').length;\u001b[39m\n\u001b[31m  instEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${activeJobs ? 'info' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${activeJobs ? 'blue' : 'green'}\\\">${activeJobs}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Jobs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsPending ? 'warn' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsPending ? 'amber' : 'green'}\\\">${ctrsPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Pending</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsReady ? 'ok' : ''}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsReady ? 'green' : ''}\\\">${ctrsReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Ready</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── HR ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31m  if (hrEl) {\u001b[39m\n\u001b[31m    const totalEmp   = hrEmp.length || 0;\u001b[39m\n\u001b[31m    const presentCnt = hrAtt.length;\u001b[39m\n\u001b[31m    const attPct     = totalEmp > 0 ? Math.round(presentCnt / totalEmp * 100) : 0;\u001b[39m\n\u001b[31m    const attColor   = attPct >= 80 ? 'green' : attPct >= 50 ? 'amber' : 'red';\u001b[39m\n\u001b[31m    const attCard    = attPct >= 80 ? 'ok' : attPct >= 50 ? 'warn' : 'alert';\u001b[39m\n\u001b[31m    const concernCnt = hrConcern.length;\u001b[39m\n\u001b[31m    const slipsDue   = hrSlips.length;\u001b[39m\n\u001b[31m    hrEl.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${attCard}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${attColor}\\\">${attPct}%</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Attendance Today</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${concernCnt ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${concernCnt ? 'amber' : 'green'}\\\">${concernCnt}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Open Concerns</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${slipsDue ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${slipsDue ? 'amber' : 'green'}\\\">${slipsDue}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Slips to Pay</div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Wire tap-to-navigate on all KPI cards\u001b[39m\n\u001b[31m  document.querySelectorAll('.kpi-card[data-href]').forEach(card => {\u001b[39m\n\u001b[31m    card.addEventListener('click', () => window.location.href = card.dataset.href);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction firstOfMonth() {\u001b[39m\n\u001b[31m  const d = new Date();\u001b[39m\n\u001b[31m  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGOUT ── */\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  pinBuffer   = '';\u001b[39m\n\u001b[31m  pendingShare = false;\u001b[39m\n\u001b[31m  _loginAttempts = 0; _lockoutUntil = 0; _loginInFlight = false;\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  document.getElementById('login-name').value = '';\u001b[39m\n\u001b[31m  document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m  showScreen('login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── TEAM PINs ── */\u001b[39m\n\u001b[31masync function enterTeamPins() {\u001b[39m\n\u001b[31m  showScreen('team');\u001b[39m\n\u001b[31m  await loadTeamPins();\u001b[39m\n\u001b[31m  // Show icon admin section for harish/pramod\u001b[39m\n\u001b[31m  const sec = document.getElementById('icon-admin-section');\u001b[39m\n\u001b[31m  if (sec) {\u001b[39m\n\u001b[31m    sec.style.display = MGMT.includes(currentUser.id) ? 'block' : 'none';\u001b[39m\n\u001b[31m    if (MGMT.includes(currentUser.id)) refreshSecretDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── ICON ADMIN ────────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst ICON_SECRET_KEY    = 'lm360_icon_secret';\u001b[39m\n\u001b[31mconst ICON_SECRET_INITIAL = 'nlr8Zsqw6XsD0rEXPQ_RfpYiOeNhH-9VglYKoIsElHE';·\u001b[39m\n\u001b[31mfunction getIconSecret() {\u001b[39m\n\u001b[31m  return localStorage.getItem(ICON_SECRET_KEY) || ICON_SECRET_INITIAL;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction setIconSecret(s) {\u001b[39m\n\u001b[31m  localStorage.setItem(ICON_SECRET_KEY, s);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet secretVisible = false;\u001b[39m\n\u001b[31mfunction refreshSecretDisplay() {\u001b[39m\n\u001b[31m  const el = document.getElementById('icon-secret-display');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  const s = getIconSecret();\u001b[39m\n\u001b[31m  el.textContent = secretVisible ? s : s.slice(0,4) + '••••••••••••••••' + s.slice(-4);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleSecretVisible() {\u001b[39m\n\u001b[31m  secretVisible = !secretVisible;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-secret-show');\u001b[39m\n\u001b[31m  if (btn) btn.textContent = secretVisible ? 'Hide' : 'Show';\u001b[39m\n\u001b[31m  refreshSecretDisplay();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function changeIconSecret() {\u001b[39m\n\u001b[31m  const newSecret = document.getElementById('icon-new-secret').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-secret-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (newSecret.length < 16) { errEl.textContent = 'Minimum 16 characters'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/change-secret', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret(), 'X-New-Secret': newSecret }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Failed');\u001b[39m\n\u001b[31m    setIconSecret(newSecret);\u001b[39m\n\u001b[31m    document.getElementById('icon-new-secret').value = '';\u001b[39m\n\u001b[31m    refreshSecretDisplay();\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = '✓ Secret updated';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mdocument.addEventListener('DOMContentLoaded', () => {\u001b[39m\n\u001b[31m  const fileInput = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  if (fileInput) {\u001b[39m\n\u001b[31m    fileInput.addEventListener('change', function() {\u001b[39m\n\u001b[31m      const wrap = document.getElementById('icon-preview-wrap');\u001b[39m\n\u001b[31m      const img  = document.getElementById('icon-preview-img');\u001b[39m\n\u001b[31m      if (this.files[0]) { img.src = URL.createObjectURL(this.files[0]); wrap.style.display = 'block'; }\u001b[39m\n\u001b[31m      else { wrap.style.display = 'none'; }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function uploadIcon() {\u001b[39m\n\u001b[31m  const pwa   = document.getElementById('icon-pwa-select').value;\u001b[39m\n\u001b[31m  const size  = document.getElementById('icon-size-select').value;\u001b[39m\n\u001b[31m  const input = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-upload-err');\u001b[39m\n\u001b[31m  errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (!input.files[0]) { errEl.textContent = 'Choose an image file first'; return; }\u001b[39m\n\u001b[31m  const formData = new FormData();\u001b[39m\n\u001b[31m  formData.append('pwa', pwa);\u001b[39m\n\u001b[31m  formData.append('size', size);\u001b[39m\n\u001b[31m  formData.append('icon', input.files[0]);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret() },\u001b[39m\n\u001b[31m      body: formData\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Upload failed');\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = `✓ Icon updated for ${pwa} (${size}px). Clear browser cache / reinstall to see new icon.`;\u001b[39m\n\u001b[31m    input.value = '';\u001b[39m\n\u001b[31m    document.getElementById('icon-preview-wrap').style.display = 'none';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadTeamPins() {\u001b[39m\n\u001b[31m  const list = document.getElementById('team-list');\u001b[39m\n\u001b[31m  list.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    list.innerHTML = '';\u001b[39m\n\u001b[31m    emps.forEach(emp => {\u001b[39m\n\u001b[31m      const row = document.createElement('div');\u001b[39m\n\u001b[31m      row.className = 'emp-row';\u001b[39m\n\u001b[31m      row.innerHTML = `\u001b[39m\n\u001b[31m        <div style=\\\"min-width:0;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:15px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.name}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.role}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-secondary btn-sm\\\" data-id=\\\"${emp.id}\\\" data-name=\\\"${emp.name}\\\">Set PIN</button>`;\u001b[39m\n\u001b[31m      row.querySelector('button').addEventListener('click', () => openSetPin(emp.id, emp.name));\u001b[39m\n\u001b[31m      list.appendChild(row);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    list.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openSetPin(targetId, targetName) {\u001b[39m\n\u001b[31m  setPinTarget = targetId;\u001b[39m\n\u001b[31m  document.getElementById('setpin-target-name').textContent = targetName;\u001b[39m\n\u001b[31m  ['setpin-new','setpin-confirm','setpin-auth'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-setpin').classList.add('show');\u001b[39m\n\u001b[31m  setTimeout(() => document.getElementById('setpin-new').focus(), 50);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function doSetPin() {\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('setpin-new').value.trim();\u001b[39m\n\u001b[31m  const conf    = document.getElementById('setpin-confirm').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('setpin-auth').value.trim();\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('setpin-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  if (!newPin || !conf || !authPin) { errEl.textContent = 'All fields are required'; return; }\u001b[39m\n\u001b[31m  if (newPin !== conf)              { errEl.textContent = 'New PINs do not match'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))     { errEl.textContent = 'PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-setpin-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_admin_id:  currentUser.id,\u001b[39m\n\u001b[31m        p_admin_pin: authPin,\u001b[39m\n\u001b[31m        p_target_id: setPinTarget,\u001b[39m\n\u001b[31m        p_new_pin:   newPin,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const err = await resp.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Failed to set PIN';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m    toast('✅ PIN updated for ' + document.getElementById('setpin-target-name').textContent);\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Set PIN';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ═══════════════════════════════════════════\u001b[39m\n\u001b[31m   JOBS — Campaign + Tour management\u001b[39m\n\u001b[31m   Access: harish, pramod (campaigns + tours)\u001b[39m\n\u001b[31m           rakesh (tours only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ */\u001b[39m\n\u001b[31mconst INST_RD = { 'Accept-Profile': 'installation' };\u001b[39m\n\u001b[31mconst INST_WR = { 'Content-Type':'application/json','Accept-Profile':'installation','Content-Profile':'installation' };·\u001b[39m\n\u001b[31mlet _currentCampaignId = null;\u001b[39m\n\u001b[31mlet _allEmployees      = [];\u001b[39m\n\u001b[31mlet _editTourId        = null;  // null = create mode, string = edit mode·\u001b[39m\n\u001b[31m// ── Helpers ──────────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _slugify(s) {\u001b[39m\n\u001b[31m  return s.toUpperCase().trim()\u001b[39m\n\u001b[31m    .replace(/[^A-Z0-9]+/g, '_')\u001b[39m\n\u001b[31m    .replace(/^_|_$/g, '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _buildCampaignId(year, brand, client, campaign, phase) {\u001b[39m\n\u001b[31m  const nn = String(phase || 1).padStart(2, '0');\u001b[39m\n\u001b[31m  return `${year}-${_slugify(brand)}-${_slugify(client)}-${_slugify(campaign)}-${nn}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _campStatusBadge(s) {\u001b[39m\n\u001b[31m  const map = { active:'ok', completed:'', on_hold:'warn' };\u001b[39m\n\u001b[31m  const col = { active:'green', completed:'', on_hold:'amber' };\u001b[39m\n\u001b[31m  return `<span class=\\\"kpi-val ${col[s]||''}\\\" style=\\\"font-size:12px;font-weight:700\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _tourStatusBadge(s) {\u001b[39m\n\u001b[31m  const colors = { active:'var(--success)', planned:'var(--info)', completed:'var(--muted)', cancelled:'var(--danger)' };\u001b[39m\n\u001b[31m  return `<span style=\\\"font-size:11px;font-weight:700;padding:2px 8px;border-radius:10px;\u001b[39m\n\u001b[31m    background:var(--card);color:${colors[s]||'var(--muted)'}\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _canCreateCampaign() { return ['harish','pramod'].includes(currentUser?.id); }\u001b[39m\n\u001b[31mfunction _canCreateTour()     { return ['harish','pramod','rakesh'].includes(currentUser?.id); }·\u001b[39m\n\u001b[31m// ── Jobs screen ───────────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterJobs() {\u001b[39m\n\u001b[31m  showScreen('jobs');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').style.display = _canCreateCampaign() ? '' : 'none';\u001b[39m\n\u001b[31m  await loadJobs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadJobs() {\u001b[39m\n\u001b[31m  const el = document.getElementById('jobs-body');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?order=created_at.desc`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?select=tour_id,campaign_id,status`, { headers: INST_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const campaigns = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours     = tResp.ok ? await tResp.json() : [];·\u001b[39m\n\u001b[31m    if (!campaigns.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty\\\"><div style=\\\"font-size:32px\\\">📋</div><div>No campaigns yet</div></div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    el.innerHTML = campaigns.map(c => {\u001b[39m\n\u001b[31m      const cTours  = tours.filter(t => t.campaign_id === c.campaign_id);\u001b[39m\n\u001b[31m      const active  = cTours.filter(t => t.status === 'active').length;\u001b[39m\n\u001b[31m      const total   = cTours.length;\u001b[39m\n\u001b[31m      const statusColors = { active:'var(--success)', on_hold:'var(--accent2)', completed:'var(--muted)' };\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card tappable\\\" style=\\\"padding:14px\\\" onclick=\\\"enterCampaign('${c.campaign_id}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:flex-start;gap:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:15px;font-weight:700\\\">${c.brand} — ${c.campaign_name}\u001b[39m\n\u001b[31m                ${c.phase > 1 ? `<span style=\\\"font-size:11px;color:var(--muted)\\\"> Phase ${c.phase}</span>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:2px\\\">${c.client} · ${c.year}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:4px\\\">\u001b[39m\n\u001b[31m                ${total} tour${total!==1?'s':''} · ${active} active\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;align-items:flex-end;gap:6px\\\">\u001b[39m\n\u001b[31m              <span style=\\\"font-size:11px;font-weight:700;color:${statusColors[c.status]||'var(--muted)'}\\\">\u001b[39m\n\u001b[31m                ${(c.status||'').replace('_',' ')}\u001b[39m\n\u001b[31m              </span>\u001b[39m\n\u001b[31m              <span style=\\\"font-size:18px;color:var(--muted)\\\">›</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-top:8px;word-break:break-all\\\">${c.campaign_id}</div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign detail ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterCampaign(campaignId) {\u001b[39m\n\u001b[31m  _currentCampaignId = campaignId;\u001b[39m\n\u001b[31m  showScreen('campaign');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').style.display = _canCreateTour() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-sub').textContent   = '';\u001b[39m\n\u001b[31m  await loadCampaign(campaignId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCampaign(campaignId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('campaign-body');\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp, empResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(campaignId)}&order=start_date`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const camps = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours = tResp.ok ? await tResp.json() : [];\u001b[39m\n\u001b[31m    _allEmployees = empResp.ok ? await empResp.json() : [];·\u001b[39m\n\u001b[31m    if (!camps.length) { body.innerHTML = '<div class=\\\"empty\\\">Campaign not found</div>'; return; }\u001b[39m\n\u001b[31m    const c = camps[0];·\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-title').textContent = `${c.brand} — ${c.campaign_name}`;\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-sub').textContent   = `${c.client} · ${c.year}${c.phase>1?' · Phase '+c.phase:''}`;·\u001b[39m\n\u001b[31m    const empMap = {};\u001b[39m\n\u001b[31m    _allEmployees.forEach(e => empMap[e.id] = e.name);·\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"padding:12px 14px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Campaign ID</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:10px;color:var(--muted);word-break:break-all;text-align:right;max-width:65%\\\">${c.campaign_id}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Client</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:13px;font-weight:600\\\">${c.client}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${_canCreateCampaign() ? `\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div class=\\\"section-title\\\" style=\\\"margin-bottom:6px\\\">Campaign Status</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m            ${['active','on_hold','completed'].map(s =>\u001b[39m\n\u001b[31m              `<button class=\\\"status-chip${c.status===s?' sel':''}\\\" onclick=\\\"updateCampaignStatus('${c.campaign_id}','${s}')\\\">\u001b[39m\n\u001b[31m                ${s.replace('_',' ')}\u001b[39m\n\u001b[31m              </button>`).join('')}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>` : ''}\u001b[39m\n\u001b[31m      </div>·\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:4px\\\">Tours (${tours.length})</div>\u001b[39m\n\u001b[31m      ${tours.length === 0 ? `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\">\u001b[39m\n\u001b[31m        <div>No tours yet</div>\u001b[39m\n\u001b[31m        ${_canCreateTour() ? '<div style=\\\"font-size:12px\\\">Tap ＋ to add the first tour</div>' : ''}\u001b[39m\n\u001b[31m      </div>` :\u001b[39m\n\u001b[31m      tours.map(t => {\u001b[39m\n\u001b[31m        const empNames = (t.assigned_employees || []).map(id => empMap[id] || id).join(', ') || 'No one assigned';\u001b[39m\n\u001b[31m        const dates    = t.start_date ? `${t.start_date} → ${t.end_date||'?'}` : 'Dates not set';\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m          <div class=\\\"card\\\" style=\\\"padding:14px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;align-items:flex-start;gap:8px\\\">\u001b[39m\n\u001b[31m              <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m                <div style=\\\"font-size:14px;font-weight:700\\\">${t.title || t.tour_id}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">📅 ${dates}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">👥 ${empNames}</div>\u001b[39m\n\u001b[31m                ${t.notes ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">📝 ${t.notes}</div>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              ${_tourStatusBadge(t.status)}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            ${_canCreateTour() ? `\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;gap:6px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m              ${['planned','active','completed'].map(s =>\u001b[39m\n\u001b[31m                `<button class=\\\"status-chip${t.status===s?' sel':''}\\\"\u001b[39m\n\u001b[31m                  onclick=\\\"updateTourStatus('${t.tour_id}','${s}')\\\">${s}</button>`).join('')}\u001b[39m\n\u001b[31m              <button class=\\\"status-chip\\\" onclick=\\\"openEditTour('${t.tour_id}')\\\"\u001b[39m\n\u001b[31m                style=\\\"margin-left:auto;background:var(--surface);border-color:#3a3a6e;color:#8888ff\\\">\u001b[39m\n\u001b[31m                ✏️ Edit\u001b[39m\n\u001b[31m              </button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m            <div style=\\\"font-size:10px;color:var(--muted);margin-top:6px\\\">${t.tour_id}</div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m      <div style=\\\"height:20px\\\"></div>`;\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateTourStatus(tourId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Tour marked ${status}`);\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateCampaignStatus(campaignId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Campaign marked ${status.replace('_',' ')}`);\u001b[39m\n\u001b[31m    loadCampaign(campaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Campaign modal ────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewCampaign() {\u001b[39m\n\u001b[31m  if (!_canCreateCampaign()) { toast('Only Harish or Pramod can create campaigns'); return; }\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('nc-year').value    = new Date().getFullYear();\u001b[39m\n\u001b[31m  document.getElementById('nc-phase').value   = '1';\u001b[39m\n\u001b[31m  document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  document.getElementById('nc-err').textContent     = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-new-campaign').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateCampaignPreview() {\u001b[39m\n\u001b[31m  const year  = document.getElementById('nc-year').value;\u001b[39m\n\u001b[31m  const brand = document.getElementById('nc-brand').value;\u001b[39m\n\u001b[31m  const clt   = document.getElementById('nc-client').value;\u001b[39m\n\u001b[31m  const camp  = document.getElementById('nc-campaign').value;\u001b[39m\n\u001b[31m  const phase = document.getElementById('nc-phase').value;\u001b[39m\n\u001b[31m  if (brand && clt && camp) {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = _buildCampaignId(year, brand, clt, camp, phase);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewCampaign() {\u001b[39m\n\u001b[31m  const year   = Number(document.getElementById('nc-year').value);\u001b[39m\n\u001b[31m  const brand  = document.getElementById('nc-brand').value.trim();\u001b[39m\n\u001b[31m  const client = document.getElementById('nc-client').value.trim();\u001b[39m\n\u001b[31m  const cname  = document.getElementById('nc-campaign').value.trim();\u001b[39m\n\u001b[31m  const phase  = Number(document.getElementById('nc-phase').value) || 1;\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('nc-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const brandEl = document.getElementById('nc-brand');\u001b[39m\n\u001b[31m  const clientEl = document.getElementById('nc-client');\u001b[39m\n\u001b[31m  const cnameEl = document.getElementById('nc-campaign');\u001b[39m\n\u001b[31m  [brandEl, clientEl, cnameEl].forEach(el => el.style.borderColor = '');\u001b[39m\n\u001b[31m  let _missingField = false;\u001b[39m\n\u001b[31m  if (!brand)  { brandEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!client) { clientEl.style.borderColor = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!cname)  { cnameEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (_missingField) { errEl.textContent = 'Brand, Client and Campaign Name are required'; return; }\u001b[39m\n\u001b[31m  const campaignId = _buildCampaignId(year, brand, client, cname, phase);·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nc-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/campaigns`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ campaign_id:campaignId, year, brand, client, campaign_name:cname, phase, created_by:currentUser.id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || 'Failed to create';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show');\u001b[39m\n\u001b[31m    toast(`✅ Campaign created`);\u001b[39m\n\u001b[31m    loadJobs();\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Create'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Tour modal ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _buildEmpCheckboxes(assigned = []) {\u001b[39m\n\u001b[31m  return _allEmployees.map(e =>\u001b[39m\n\u001b[31m    `<label style=\\\"display:flex;align-items:center;gap:10px;padding:8px 10px;background:var(--card);\u001b[39m\n\u001b[31m      border:1px solid var(--border);border-radius:8px;cursor:pointer\\\">\u001b[39m\n\u001b[31m      <input type=\\\"checkbox\\\" value=\\\"${e.id}\\\" ${assigned.includes(e.id)?'checked':''} style=\\\"width:16px;height:16px;accent-color:var(--accent)\\\">\u001b[39m\n\u001b[31m      <span style=\\\"font-size:14px;font-weight:600\\\">${e.name}</span>\u001b[39m\n\u001b[31m      <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${e.role}</span>\u001b[39m\n\u001b[31m    </label>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openNewTour() {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = null;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '🗺️ New Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Create Tour';\u001b[39m\n\u001b[31m  document.getElementById('nt-title').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-start').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-end').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-notes').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(_currentCampaignId)}&select=tour_id`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const existing = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    const nn = String(existing.length + 1).padStart(2, '0');\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T${nn}`;\u001b[39m\n\u001b[31m  } catch(_) {\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T01`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (_allEmployees.length)\u001b[39m\n\u001b[31m    document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes();·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEditTour(tourId) {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = tourId;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '✏️ Edit Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Save Changes';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const tours = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    if (!tours.length) { toast('Tour not found'); return; }\u001b[39m\n\u001b[31m    const t = tours[0];\u001b[39m\n\u001b[31m    document.getElementById('nt-title').value = t.title || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-start').value = t.start_date || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-end').value   = t.end_date   || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-notes').value = t.notes      || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = tourId;\u001b[39m\n\u001b[31m    if (_allEmployees.length)\u001b[39m\n\u001b[31m      document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes(t.assigned_employees || []);\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to load tour'); return; }·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewTour() {\u001b[39m\n\u001b[31m  const title = document.getElementById('nt-title').value.trim();\u001b[39m\n\u001b[31m  const start = document.getElementById('nt-start').value || null;\u001b[39m\n\u001b[31m  const end   = document.getElementById('nt-end').value   || null;\u001b[39m\n\u001b[31m  const notes = document.getElementById('nt-notes').value.trim() || null;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('nt-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const checked = [...document.querySelectorAll('#nt-employees input:checked')].map(cb => cb.value);\u001b[39m\n\u001b[31m  if (!checked.length) { errEl.textContent = 'Assign at least one employee'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nt-save');\u001b[39m\n\u001b[31m  const wasEdit = !!_editTourId;\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = wasEdit ? 'Saving…' : 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editTourId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(_editTourId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      const tourId = document.getElementById('nt-id-preview').textContent;\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          tour_id: tourId, campaign_id: _currentCampaignId,\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes, status: 'planned',\u001b[39m\n\u001b[31m          created_by: currentUser.id,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || (_editTourId ? 'Failed to save' : 'Failed to create');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show');\u001b[39m\n\u001b[31m    toast(_editTourId ? '✅ Tour updated' : '✅ Tour created');\u001b[39m\n\u001b[31m    _editTourId = null;\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = wasEdit ? 'Save Changes' : 'Create Tour';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── EVENT WIRING ── */\u001b[39m\n\u001b[31mfunction wireEvents() {\u001b[39m\n\u001b[31m  // Jobs screens\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-back').addEventListener('click',     () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-refresh').addEventListener('click',  loadJobs);\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').addEventListener('click',  openNewCampaign);\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-back').addEventListener('click', () => showScreen('jobs'));\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-refresh').addEventListener('click', () => loadCampaign(_currentCampaignId));\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').addEventListener('click',      openNewTour);·\u001b[39m\n\u001b[31m  // New Campaign modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-save').addEventListener('click', saveNewCampaign);\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign','nc-year','nc-phase'].forEach(id =>\u001b[39m\n\u001b[31m    document.getElementById(id).addEventListener('input', updateCampaignPreview));·\u001b[39m\n\u001b[31m  // New Tour modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').addEventListener('click', saveNewTour);·\u001b[39m\n\u001b[31m  document.getElementById('btn-team-back').addEventListener('click', () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-team-refresh').addEventListener('click', loadTeamPins);\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-cancel').addEventListener('click', () => {\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-save').addEventListener('click', doSetPin);·\u001b[39m\n\u001b[31m  // Set PIN — progressive field reveal\u001b[39m\n\u001b[31m  document.getElementById('setpin-new').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const val = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const showConf = val.length === 4;\u001b[39m\n\u001b[31m    document.getElementById('setpin-confirm-group').style.display = showConf ? '' : 'none';\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m    if (!showConf) { document.getElementById('setpin-confirm').value = ''; document.getElementById('setpin-auth').value = ''; }\u001b[39m\n\u001b[31m    if (showConf) setTimeout(() => document.getElementById('setpin-confirm').focus(), 20);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const newVal = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const conf   = document.getElementById('setpin-confirm').value;\u001b[39m\n\u001b[31m    const showAuth = conf.length === 4 && conf === newVal;\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = showAuth ? '' : 'none';\u001b[39m\n\u001b[31m    if (!showAuth) document.getElementById('setpin-auth').value = '';\u001b[39m\n\u001b[31m    if (showAuth) setTimeout(() => document.getElementById('setpin-auth').focus(), 20);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // Hub AI config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-save').addEventListener('click',   saveHubAiConfig);\u001b[39m\n\u001b[31m  document.getElementById('btn-check-keys').addEventListener('click',    checkApiKeyStatus);·\u001b[39m\n\u001b[31m  // Hub QA config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-qa').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-save').addEventListener('click', saveHubQaConfig);·\u001b[39m\n\u001b[31m  // Share picker\u001b[39m\n\u001b[31m  document.getElementById('share-to-expense').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/expense/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-recce').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/recce/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-installation').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/installation/current/?share=1';\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-04: employee dropdown change resets PIN buffer and lockout\u001b[39m\n\u001b[31m  document.getElementById('login-name').addEventListener('change', () => {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-07: ESC key closes any open modal\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (e.key === 'Escape') {\u001b[39m\n\u001b[31m      document.querySelectorAll('.modal-overlay.show').forEach(m => m.classList.remove('show'));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-11: keyboard digit/backspace input on login screen\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (!document.getElementById('screen-login').classList.contains('active')) return;\u001b[39m\n\u001b[31m    if (e.key >= '0' && e.key <= '9') { handlePinKey(e.key); }\u001b[39m\n\u001b[31m    else if (e.key === 'Backspace') { e.preventDefault(); handlePinKey('B'); }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-02/B-08: re-check session TTL when user returns to the tab\u001b[39m\n\u001b[31m  document.addEventListener('visibilitychange', () => {\u001b[39m\n\u001b[31m    if (document.visibilityState === 'visible' && currentUser) {\u001b[39m\n\u001b[31m      if (!loadSession()) doLogout();\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── BOOT ── */\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  wireEvents();·\u001b[39m\n\u001b[31m  if (location.search.includes('share=1')) {\u001b[39m\n\u001b[31m    pendingShare = true;\u001b[39m\n\u001b[31m    history.replaceState({}, '', '/hub/');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Always populate the login dropdown (needed if user logs out later)\u001b[39m\n\u001b[31m  initLogin(); // fire-and-forget — does not block home screen path·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess) {\u001b[39m\n\u001b[31m    currentUser = { id: sess.empId, name: sess.name, role: sess.role };\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  setTimeout(() => showScreen('login'), 1500);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SERVICE WORKER: install + update ── */\u001b[39m\n\u001b[31mif ('serviceWorker' in navigator) {\u001b[39m\n\u001b[31m  navigator.serviceWorker.register('/hub/sw.js').then(reg => {·\u001b[39m\n\u001b[31m    // Install banner: show when browser signals app is installable\u001b[39m\n\u001b[31m    let deferredInstall = null;\u001b[39m\n\u001b[31m    window.addEventListener('beforeinstallprompt', e => {\u001b[39m\n\u001b[31m      e.preventDefault();\u001b[39m\n\u001b[31m      deferredInstall = e;\u001b[39m\n\u001b[31m      if (!window.matchMedia('(display-mode: standalone)').matches) {\u001b[39m\n\u001b[31m        const delay = document.getElementById('update-banner').classList.contains('show') ? 3500 : 0;\u001b[39m\n\u001b[31m        setTimeout(() => document.getElementById('install-banner').classList.add('show'), delay);\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    window.addEventListener('appinstalled', () => {\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install').addEventListener('click', async () => {\u001b[39m\n\u001b[31m      if (!deferredInstall) return;\u001b[39m\n\u001b[31m      deferredInstall.prompt();\u001b[39m\n\u001b[31m      await deferredInstall.userChoice;\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m    // Update banner: show when a new SW version is waiting to activate\u001b[39m\n\u001b[31m    function showUpdateBanner() {\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.add('show');\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (reg.waiting) showUpdateBanner();·\u001b[39m\n\u001b[31m    reg.addEventListener('updatefound', () => {\u001b[39m\n\u001b[31m      const newSW = reg.installing;\u001b[39m\n\u001b[31m      newSW.addEventListener('statechange', () => {\u001b[39m\n\u001b[31m        if (newSW.state === 'installed' && navigator.serviceWorker.controller) {\u001b[39m\n\u001b[31m          showUpdateBanner();\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    document.getElementById('btn-update').addEventListener('click', () => {\u001b[39m\n\u001b[31m      if (reg.waiting) {\u001b[39m\n\u001b[31m        reg.waiting.postMessage({ type: 'SKIP_WAITING' });\u001b[39m\n\u001b[31m        navigator.serviceWorker.addEventListener('controllerchange', () => {\u001b[39m\n\u001b[31m          window.location.reload();\u001b[39m\n\u001b[31m        }, { once: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-update-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m  }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mboot();\u001b[39m\n\u001b[31m</script>··\u001b[39m\n\u001b[31m</body></html>\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"recce-v10\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html><html lang=\\\"en\\\"><head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#FF6B35\\\">\u001b[39m\n\u001b[31m<meta name=\\\"mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-status-bar-style\\\" content=\\\"black-translucent\\\">\u001b[39m\n\u001b[31m<title>360LM</title>\u001b[39m\n\u001b[31m<meta name=\\\"description\\\" content=\\\"360LM Operations Hub — employee tools\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/hub/manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"icon\\\" href=\\\"/hub/icons/icon-192.png\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--surface:#161616;--card:#1e1e1e;--border:#2a2a2a;\u001b[39m\n\u001b[31m  --accent:#FF6B35;--accent2:#FFB800;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --danger:#e74c3c;--success:#2ecc71;--info:#3498db;\u001b[39m\n\u001b[31m  --radius:12px;--font:'Segoe UI',system-ui,sans-serif;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:var(--font);min-height:100vh;overflow-x:hidden}\u001b[39m\n\u001b[31m.screen{display:none;min-height:100vh;flex-direction:column}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── HEADER ── */\u001b[39m\n\u001b[31m.header{background:var(--surface);border-bottom:1px solid var(--border);padding:14px 16px;\u001b[39m\n\u001b[31m  display:flex;align-items:center;gap:12px;position:sticky;top:0;z-index:10;flex-shrink:0}\u001b[39m\n\u001b[31m.header h1{font-size:18px;font-weight:700;color:var(--accent);flex:1}\u001b[39m\n\u001b[31m.header .sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.btn-icon{background:none;border:none;color:var(--muted);font-size:22px;cursor:pointer;\u001b[39m\n\u001b[31m  padding:4px;border-radius:8px;line-height:1;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m.btn-icon:active{background:var(--border)}·\u001b[39m\n\u001b[31m/* ── BODY ── */\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:16px}·\u001b[39m\n\u001b[31m/* ── SPLASH ── */\u001b[39m\n\u001b[31m#screen-splash{align-items:center;justify-content:center;gap:8px}\u001b[39m\n\u001b[31m.splash-logo{font-size:72px;font-weight:900;color:var(--accent);letter-spacing:-2px;line-height:1}\u001b[39m\n\u001b[31m.splash-sub{font-size:15px;color:var(--muted);letter-spacing:2px;text-transform:uppercase}\u001b[39m\n\u001b[31m.splash-dot{width:8px;height:8px;border-radius:50%;background:var(--accent);\u001b[39m\n\u001b[31m  animation:pulse 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes pulse{0%,100%{opacity:.3;transform:scale(.8)}50%{opacity:1;transform:scale(1)}}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  min-height:100dvh;padding:24px;gap:20px;overflow-y:auto}\u001b[39m\n\u001b[31m.login-logo{font-size:44px;font-weight:900;color:var(--accent);letter-spacing:-1px}\u001b[39m\n\u001b[31m.login-sub{font-size:13px;color:var(--muted);text-transform:uppercase;letter-spacing:2px}\u001b[39m\n\u001b[31m.form-group{width:100%;max-width:360px;display:flex;flex-direction:column;gap:8px}\u001b[39m\n\u001b[31m.form-group label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px}\u001b[39m\n\u001b[31mselect{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31mselect:focus{border-color:var(--accent)}·\u001b[39m\n\u001b[31m/* ── PIN PAD ── */\u001b[39m\n\u001b[31m.pin-display{display:flex;gap:14px;justify-content:center;margin:4px 0}\u001b[39m\n\u001b[31m.pin-dot{width:16px;height:16px;border-radius:50%;border:2px solid var(--muted);background:transparent;transition:.15s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.pin-pad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;max-width:260px;margin:0 auto;width:100%}\u001b[39m\n\u001b[31m.pin-key{background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  font-size:22px;font-weight:600;padding:16px;border-radius:var(--radius);cursor:pointer;\u001b[39m\n\u001b[31m  text-align:center;line-height:1;user-select:none}\u001b[39m\n\u001b[31m.pin-key:active{background:var(--border)}\u001b[39m\n\u001b[31m.pin-key.clr{color:var(--danger);font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.pin-key.back{color:var(--accent2)}\u001b[39m\n\u001b[31m.login-err{font-size:13px;color:var(--danger);text-align:center;min-height:18px}\u001b[39m\n\u001b[31m@keyframes shake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-6px)}40%,80%{transform:translateX(6px)}}\u001b[39m\n\u001b[31m.pin-display.shake{animation:shake .4s ease}·\u001b[39m\n\u001b[31m/* ── KPI CARDS ── */\u001b[39m\n\u001b[31m.kpi-row{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\u001b[39m\n\u001b[31m.kpi-4col{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m#home-body{gap:10px}\u001b[39m\n\u001b[31m.kpi-card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:10px 8px;text-align:center;border-left:3px solid var(--accent);cursor:default}\u001b[39m\n\u001b[31m.kpi-card.alert{border-left-color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-card.warn{border-left-color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-card.ok{border-left-color:var(--success)}\u001b[39m\n\u001b[31m.kpi-card.info{border-left-color:var(--info)}\u001b[39m\n\u001b[31m.kpi-val{font-size:18px;font-weight:900;color:var(--accent);line-height:1.2}\u001b[39m\n\u001b[31m.kpi-val.red{color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-val.amber{color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-val.green{color:var(--success)}\u001b[39m\n\u001b[31m.kpi-val.blue{color:var(--info)}\u001b[39m\n\u001b[31m.kpi-lbl{font-size:10px;color:var(--muted);margin-top:4px;text-transform:uppercase;letter-spacing:.4px;line-height:1.3}\u001b[39m\n\u001b[31m.kpi-skel{background:var(--border);border-radius:4px;height:24px;margin-bottom:4px;\u001b[39m\n\u001b[31m  animation:shimmer 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes shimmer{0%,100%{opacity:.5}50%{opacity:1}}·\u001b[39m\n\u001b[31m/* ── SECTION TITLE ── */\u001b[39m\n\u001b[31m.section-title{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.8px}·\u001b[39m\n\u001b[31m/* ── MGMT HOME: MAIN TILES ── */\u001b[39m\n\u001b[31m.hub-main-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px}\u001b[39m\n\u001b[31m.hub-main-tile{background:var(--card);border:2px solid var(--border);border-radius:18px;\u001b[39m\n\u001b[31m  padding:36px 12px 28px;text-align:center;cursor:pointer;transition:background .15s,border-color .15s}\u001b[39m\n\u001b[31m.hub-main-tile:active{background:#1a2a1a;border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-main-icon{font-size:40px;line-height:1;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-main-label{font-size:15px;font-weight:800;color:var(--text)}\u001b[39m\n\u001b[31m/* ── MGMT HOME: QUICK ACTIONS ── */\u001b[39m\n\u001b[31m.hub-qa-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-qa-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(88px,1fr));gap:10px}\u001b[39m\n\u001b[31m.hub-qa-tile{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px 8px;text-align:center;cursor:pointer;display:flex;flex-direction:column;\u001b[39m\n\u001b[31m  align-items:center;gap:6px;transition:background .15s}\u001b[39m\n\u001b[31m.hub-qa-tile:active{background:var(--border)}\u001b[39m\n\u001b[31m.hub-qa-icon{font-size:26px;line-height:1}\u001b[39m\n\u001b[31m.hub-qa-label{font-size:11px;font-weight:700;color:var(--text);line-height:1.3}\u001b[39m\n\u001b[31m.hub-qa-item{display:flex;align-items:center;gap:12px;padding:10px 0;\u001b[39m\n\u001b[31m  border-bottom:1px solid var(--border);cursor:pointer}\u001b[39m\n\u001b[31m.hub-qa-item:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.hub-qa-item-icon{font-size:22px;width:32px;text-align:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item-label{flex:1;font-size:14px;font-weight:600}\u001b[39m\n\u001b[31m.hub-qa-item-toggle{width:20px;height:20px;border-radius:4px;border:2px solid var(--border);\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle::after{content:'✓';color:#fff;font-weight:700;font-size:12px}·\u001b[39m\n\u001b[31m/* ── TILE GRID ── */\u001b[39m\n\u001b[31m.tile-grid{display:grid;gap:12px}\u001b[39m\n\u001b[31m.tile-grid.mgmt{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m.tile-grid.field{grid-template-columns:repeat(2,1fr)}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:18px 8px;display:flex;flex-direction:column;align-items:center;gap:8px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s;position:relative}\u001b[39m\n\u001b[31m.tile:active{background:var(--border);transform:scale(.97)}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px;line-height:1}\u001b[39m\n\u001b[31m.tile-label{font-size:11px;font-weight:700;color:var(--text);text-align:center;text-transform:uppercase;letter-spacing:.4px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-icon{font-size:24px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-label{font-size:10px}\u001b[39m\n\u001b[31m.tile.coming{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.tile.coming:active{transform:none;background:var(--card)}\u001b[39m\n\u001b[31m.soon-badge{position:absolute;top:6px;right:6px;background:var(--accent2);color:#000;\u001b[39m\n\u001b[31m  font-size:8px;font-weight:800;padding:2px 5px;border-radius:20px;text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── USER BADGE ── */\u001b[39m\n\u001b[31m.user-badge{display:flex;align-items:center;gap:12px}\u001b[39m\n\u001b[31m.avatar{width:44px;height:44px;background:var(--accent);border-radius:50%;\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:800;\u001b[39m\n\u001b[31m  color:#fff;flex-shrink:0;text-transform:uppercase}\u001b[39m\n\u001b[31m.user-name{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.user-role{font-size:12px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── TEAM PINS ── */\u001b[39m\n\u001b[31m.emp-row{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px}\u001b[39m\n\u001b[31m.btn{display:flex;align-items:center;justify-content:center;gap:6px;border:none;\u001b[39m\n\u001b[31m  border-radius:var(--radius);font-size:15px;font-weight:700;padding:12px 18px;\u001b[39m\n\u001b[31m  cursor:pointer;width:100%;transition:.15s;font-family:var(--font)}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--card);border:1px solid var(--border);color:var(--text)}\u001b[39m\n\u001b[31m.btn-sm{padding:7px 12px;font-size:12px;font-weight:700;width:auto;flex-shrink:0}\u001b[39m\n\u001b[31m.info-note{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;font-size:13px;color:var(--muted);line-height:1.5}·\u001b[39m\n\u001b[31m/* ── MODAL ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:200;\u001b[39m\n\u001b[31m  align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;\u001b[39m\n\u001b[31m  padding:24px;width:100%;max-width:480px;display:flex;flex-direction:column;gap:14px;\u001b[39m\n\u001b[31m  max-height:90dvh;overflow-y:auto;-webkit-overflow-scrolling:touch}\u001b[39m\n\u001b[31m.modal-title{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.modal-box input[type=password]{\u001b[39m\n\u001b[31m  width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31m.modal-box input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.action-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px}·\u001b[39m\n\u001b[31m/* ── SHARE PICKER ── */\u001b[39m\n\u001b[31m#screen-share .body{align-items:center;justify-content:center;gap:24px}\u001b[39m\n\u001b[31m.share-prompt{font-size:15px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.share-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;width:100%;max-width:360px}\u001b[39m\n\u001b[31m.share-tile{background:var(--card);border:2px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:24px 8px;display:flex;flex-direction:column;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s}\u001b[39m\n\u001b[31m.share-tile:active{background:var(--border);transform:scale(.97);border-color:var(--accent)}\u001b[39m\n\u001b[31m.share-tile-icon{font-size:36px;line-height:1}\u001b[39m\n\u001b[31m.share-tile-label{font-size:12px;font-weight:700;color:var(--text);text-align:center;\u001b[39m\n\u001b[31m  text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── INSTALL BANNER ── */\u001b[39m\n\u001b[31m#install-banner{display:none;position:fixed;bottom:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:linear-gradient(135deg,#1a1a1a,#0d2010);\u001b[39m\n\u001b[31m  border-top:1px solid var(--accent);padding:14px 16px;\u001b[39m\n\u001b[31m  align-items:center;gap:12px;box-shadow:0 -4px 24px rgba(0,0,0,.7)}\u001b[39m\n\u001b[31m#install-banner.show{display:flex}\u001b[39m\n\u001b[31m#install-banner .ib-icon{font-size:30px;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-text{flex:1}\u001b[39m\n\u001b[31m#install-banner .ib-title{font-size:14px;font-weight:700;color:var(--text)}\u001b[39m\n\u001b[31m#install-banner .ib-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#install-banner .ib-btn{background:var(--accent);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-close{background:none;border:none;color:var(--muted);font-size:20px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── UPDATE BANNER ── */\u001b[39m\n\u001b[31m#update-banner{display:none;position:fixed;top:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:#1a2a1a;border-bottom:2px solid var(--success);\u001b[39m\n\u001b[31m  padding:12px 16px;align-items:center;gap:12px}\u001b[39m\n\u001b[31m#update-banner.show{display:flex}\u001b[39m\n\u001b[31m#update-banner .ub-text{flex:1;font-size:14px;font-weight:600;color:var(--text)}\u001b[39m\n\u001b[31m#update-banner .ub-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#update-banner .ub-btn{background:var(--success);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:8px 14px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#update-banner .ub-close{background:none;border:none;color:var(--muted);font-size:18px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#222;color:#fff;padding:10px 20px;border-radius:20px;font-size:14px;font-weight:600;\u001b[39m\n\u001b[31m  opacity:0;transition:.3s;pointer-events:none;white-space:nowrap;z-index:500}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  padding:40px;color:var(--muted);font-size:14px;gap:12px}\u001b[39m\n\u001b[31m.spinner{width:36px;height:36px;border:3px solid var(--border);border-top-color:var(--accent);\u001b[39m\n\u001b[31m  border-radius:50%;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* ── CARD (jobs / campaign screens) ── */\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius)}\u001b[39m\n\u001b[31m.card.tappable:active{border-color:var(--accent);opacity:.85}·\u001b[39m\n\u001b[31m/* ── STATUS CHIPS (tour / campaign) ── */\u001b[39m\n\u001b[31m.status-chip{font-size:11px;padding:4px 11px;border-radius:8px;cursor:pointer;\u001b[39m\n\u001b[31m  background:var(--card);color:var(--text);border:1px solid var(--border);font-weight:600;\u001b[39m\n\u001b[31m  font-family:var(--font);transition:.15s}\u001b[39m\n\u001b[31m.status-chip.sel{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.status-chip:active{opacity:.8}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SPLASH\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-splash\\\" class=\\\"screen active\\\">\u001b[39m\n\u001b[31m  <div class=\\\"splash-logo\\\">360LM</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-sub\\\">Operations Hub</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-dot\\\" style=\\\"margin-top:16px\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     LOGIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-login\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-wrap\\\">\u001b[39m\n\u001b[31m    <div class=\\\"login-logo\\\">360LM</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">Operations Hub</div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Select Your Name</label>\u001b[39m\n\u001b[31m      <select id=\\\"login-name\\\"><option value=\\\"\\\">— Choose employee —</option><option value=\\\"amit\\\">Amit</option><option value=\\\"ashok\\\">Ashok</option><option value=\\\"gurpreet\\\">Gurpreet</option><option value=\\\"harish\\\">Harish Kumar Lal</option><option value=\\\"mukesh\\\">Mukesh</option><option value=\\\"narender\\\">Narender</option><option value=\\\"pramod\\\">Pramod Narang</option><option value=\\\"rakesh\\\">Rakesh</option><option value=\\\"sachin\\\">Sachin</option><option value=\\\"shambhu\\\">Shambhu</option><option value=\\\"umashankar\\\">Umashankar</option></select>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m      <label style=\\\"text-align:center\\\">Enter PIN</label>\u001b[39m\n\u001b[31m      <div class=\\\"pin-display\\\" id=\\\"pin-display\\\">\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd0\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd1\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd2\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd3\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"login-err\\\" id=\\\"login-err\\\"></div>\u001b[39m\n\u001b[31m      <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-1\\\" data-k=\\\"1\\\" aria-label=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-2\\\" data-k=\\\"2\\\" aria-label=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-3\\\" data-k=\\\"3\\\" aria-label=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-4\\\" data-k=\\\"4\\\" aria-label=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-5\\\" data-k=\\\"5\\\" aria-label=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-6\\\" data-k=\\\"6\\\" aria-label=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-7\\\" data-k=\\\"7\\\" aria-label=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-8\\\" data-k=\\\"8\\\" aria-label=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-9\\\" data-k=\\\"9\\\" aria-label=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key clr\\\" id=\\\"pk-C\\\" data-k=\\\"C\\\" aria-label=\\\"Clear\\\">CLR</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-0\\\" data-k=\\\"0\\\" aria-label=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key back\\\" id=\\\"pk-B\\\" data-k=\\\"B\\\" aria-label=\\\"Backspace\\\">⌫</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     HOME  (built dynamically)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\" id=\\\"home-header\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"home-body\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SHARE PICKER\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-share\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>Share Image To</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">Choose where to send this photo</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"share-prompt\\\">Which tool should receive this image?</div>\u001b[39m\n\u001b[31m    <div class=\\\"share-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-expense\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🧾</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Expenses</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-recce\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">📍</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Recce</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-installation\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🔧</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Installation</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     TEAM PINs  (admin only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-team\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-back\\\" style=\\\"font-size:20px\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Team PINs</h1><div class=\\\"sub\\\">Set employee login PINs</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-refresh\\\" title=\\\"Refresh\\\">🔄</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"info-note\\\">PINs are stored encrypted — current PIN cannot be viewed. You can set a new PIN for any employee below.</div>\u001b[39m\n\u001b[31m    <div id=\\\"team-list\\\"></div>·\u001b[39m\n\u001b[31m    <!-- Icon Admin — Harish & Pramod only -->\u001b[39m\n\u001b[31m    <div id=\\\"icon-admin-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:8px\\\">🎨 PWA Icons (Admin)</div>\u001b[39m\n\u001b[31m      <div class=\\\"info-note\\\" style=\\\"margin-bottom:10px\\\">Upload a new icon for any PWA. Upload both 192×192 and 512×512 sizes. PNG/JPG/WEBP accepted — auto-resized on server.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;flex-direction:column;gap:10px\\\">·\u001b[39m\n\u001b[31m        <!-- Secret management -->\u001b[39m\n\u001b[31m        <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:12px;padding:14px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px\\\">🔑 Admin Secret</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:center;gap:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"font-family:monospace;font-size:13px;color:var(--accent);background:var(--bg);\u001b[39m\n\u001b[31m              border-radius:6px;padding:8px 12px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\" id=\\\"icon-secret-display\\\">—</div>\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"toggleSecretVisible()\\\" id=\\\"btn-secret-show\\\">Show</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:10px\\\">Change secret (min 16 chars):</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m            <input id=\\\"icon-new-secret\\\" type=\\\"text\\\" placeholder=\\\"Enter new secret…\\\" style=\\\"flex:1;background:var(--bg);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m              padding:10px 12px;border-radius:8px;font-size:13px;font-family:monospace;outline:none\\\">\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"changeIconSecret()\\\" style=\\\"white-space:nowrap\\\">Update</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div id=\\\"icon-secret-err\\\" style=\\\"font-size:11px;color:var(--danger);min-height:14px;margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>·\u001b[39m\n\u001b[31m        <!-- Upload form -->\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Select PWA</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-pwa-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"hub\\\">Hub</option>\u001b[39m\n\u001b[31m            <option value=\\\"expense\\\">Expenses</option>\u001b[39m\n\u001b[31m            <option value=\\\"finance\\\">Finance</option>\u001b[39m\n\u001b[31m            <option value=\\\"installation\\\">Installation</option>\u001b[39m\n\u001b[31m            <option value=\\\"recce\\\">Recce</option>\u001b[39m\n\u001b[31m            <option value=\\\"dispatch\\\">Dispatch</option>\u001b[39m\n\u001b[31m            <option value=\\\"production\\\">Production</option>\u001b[39m\n\u001b[31m            <option value=\\\"vehicle\\\">Vehicle Log</option>\u001b[39m\n\u001b[31m            <option value=\\\"vrs\\\">Vehicle Request</option>\u001b[39m\n\u001b[31m            <option value=\\\"sales\\\">Sales</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Icon Size</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-size-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"192\\\">192 × 192 (standard)</option>\u001b[39m\n\u001b[31m            <option value=\\\"512\\\">512 × 512 (large)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Choose Image File</label>\u001b[39m\n\u001b[31m          <input type=\\\"file\\\" id=\\\"icon-file-input\\\" accept=\\\"image/png,image/jpeg,image/webp\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:14px;width:100%\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-preview-wrap\\\" style=\\\"display:none;text-align:center\\\">\u001b[39m\n\u001b[31m          <img id=\\\"icon-preview-img\\\" style=\\\"width:96px;height:96px;border-radius:16px;border:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-upload-err\\\" style=\\\"font-size:12px;color:var(--danger);min-height:16px\\\"></div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" onclick=\\\"uploadIcon()\\\">Upload Icon</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     MODAL: Admin set PIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-setpin\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🔑 Set PIN — <span id=\\\"setpin-target-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>New PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Enter new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-confirm-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Confirm New PIN</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-confirm\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Repeat new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-auth-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Your own current PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"setpin-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-setpin-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-setpin-save\\\">Set PIN</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     JOBS — Campaign list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-jobs\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Jobs</h1><div class=\\\"sub\\\">Campaigns &amp; Tours</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh jobs\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-campaign\\\" title=\\\"New Campaign\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"jobs-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     CAMPAIGN DETAIL — Tour list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-campaign\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 id=\\\"campaign-hdr-title\\\">Campaign</h1><div class=\\\"sub\\\" id=\\\"campaign-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh campaign\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-tour\\\" title=\\\"New Tour\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"campaign-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Campaign -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-campaign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">📋 New Campaign</div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Year</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-year\\\" min=\\\"2024\\\" max=\\\"2030\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Phase #</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-phase\\\" value=\\\"1\\\" min=\\\"1\\\" max=\\\"99\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Brand <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-brand\\\" placeholder=\\\"e.g. Lenovo\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Client <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-client\\\" placeholder=\\\"e.g. Lenovo India\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Campaign Name <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-campaign\\\" placeholder=\\\"e.g. Chandigarh BTL\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Generated Campaign ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nc-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nc-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nc-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nc-save\\\">Create</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Tour -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-tour\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px;max-height:88vh;overflow-y:auto\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🗺️ New Tour</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Tour Title</div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nt-title\\\" placeholder=\\\"e.g. Chandigarh Week 1\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Start Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-start\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">End Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-end\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:6px\\\">Assign Employees <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-employees\\\" style=\\\"display:flex;flex-direction:column;gap:6px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"color:var(--muted);font-size:13px\\\">Loading…</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Notes</div>\u001b[39m\n\u001b[31m      <textarea id=\\\"nt-notes\\\" rows=\\\"2\\\" placeholder=\\\"Optional instructions or remarks\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px;font-family:inherit;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Tour ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-id-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nt-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nt-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nt-save\\\">Create Tour</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- UPDATE BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"update-banner\\\">\u001b[39m\n\u001b[31m  <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ub-text\\\">App update available</div>\u001b[39m\n\u001b[31m    <div class=\\\"ub-sub\\\">Tap to refresh and get the latest version</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ub-btn\\\" id=\\\"btn-update\\\">Update now</button>\u001b[39m\n\u001b[31m  <button class=\\\"ub-close\\\" id=\\\"btn-update-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- INSTALL BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"install-banner\\\">\u001b[39m\n\u001b[31m  <div class=\\\"ib-icon\\\">📲</div>\u001b[39m\n\u001b[31m  <div class=\\\"ib-text\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ib-title\\\">Install 360LM Hub</div>\u001b[39m\n\u001b[31m    <div class=\\\"ib-sub\\\">Add to home screen for the best experience</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ib-btn\\\" id=\\\"btn-install\\\">Install</button>\u001b[39m\n\u001b[31m  <button class=\\\"ib-close\\\" id=\\\"btn-install-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: AI Config (mgmt only) -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-ai\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🤖 AI Config — Screenshot Extraction</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Tesseract OCR</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-tesseract\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Extraction Fallback</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-enabled\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Model</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-model\\\">\u001b[39m\n\u001b[31m        <option value=\\\"haiku\\\">Haiku (fast, cost-effective)</option>\u001b[39m\n\u001b[31m        <option value=\\\"sonnet\\\">Sonnet (higher accuracy)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-ai-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-ai-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"border-top:1px solid var(--border);padding-top:12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">API Key Health</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px;margin-bottom:10px\\\">Live check — result is always the same for all users</div>\u001b[39m\n\u001b[31m      <div id=\\\"api-key-status-wrap\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-check-keys\\\" style=\\\"margin-top:10px;width:100%\\\">Check API Keys</button>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:14px;border-top:1px solid var(--border);padding-top:12px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">View Usage &amp; Billing</div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Anthropic</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/usage\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Google Maps</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/google/maps-apis/api-list\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: Hub Quick-Action Config -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-qa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">⚡ Quick Actions</div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-config-list\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-cfg-err\\\" style=\\\"color:var(--danger);font-size:13px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-qa-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-qa-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SCRIPT\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31m/* ── CONFIG ── */\u001b[39m\n\u001b[31mconst CFG = {\u001b[39m\n\u001b[31m  api:        '/db',\u001b[39m\n\u001b[31m  apiHeaders: { 'Content-Type': 'application/json', 'Accept-Profile': 'expense' },\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31m/* ── SERVER ACCESS MAP (fetched from hub-access.json, overrides ACCESS for non-mgmt) ── */\u001b[39m\n\u001b[31mlet _serverAccess = {};\u001b[39m\n\u001b[31masync function loadHubAccess() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/hub/hub-access.json?t=' + Date.now());\u001b[39m\n\u001b[31m    if (r.ok) _serverAccess = await r.json();\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── ROLE ACCESS MAP (hardcoded fallback) ── */\u001b[39m\n\u001b[31mconst ACCESS = {\u001b[39m\n\u001b[31m  harish:     ['expense','installation','finance','recce','dispatch','production','jobs','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  pramod:     ['expense','installation','finance','recce','dispatch','production','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  rakesh:     ['installation','expense','production','jobs','vehicle','vrs','sales','hr'],\u001b[39m\n\u001b[31m  ashok:      ['expense','production','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  shambhu:    ['finance','expense','vehicle','vrs','stores','activity','hr'],\u001b[39m\n\u001b[31m  gurpreet:   ['expense','dispatch','vehicle','vrs','stores','hr'],\u001b[39m\n\u001b[31m  sachin:     ['installation','expense','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  mukesh:     ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  umashankar: ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  narender:   ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  amit:       ['vehicle','vrs','hr'],\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst TILES = [\u001b[39m\n\u001b[31m  { id:'expense',      label:'Expenses',     icon:'🧾', url:'/expense/',               live:true  },\u001b[39m\n\u001b[31m  { id:'installation', label:'Installation', icon:'🔧', url:'/installation/current/',  live:true  },\u001b[39m\n\u001b[31m  { id:'finance',      label:'Finance',      icon:'💰', url:'/finance/',               live:true  },\u001b[39m\n\u001b[31m  { id:'recce',        label:'Recce',        icon:'📍', url:'/recce/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'dispatch',     label:'Dispatch',     icon:'🚚', url:'/dispatch/',              live:true  },\u001b[39m\n\u001b[31m  { id:'production',   label:'Production',   icon:'⚙️', url:'/production/',            live:true  },\u001b[39m\n\u001b[31m  { id:'jobs',         label:'Jobs',         icon:'📋', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'vehicle',      label:'Vehicle Log',  icon:'🚐', url:'/vehicle/',               live:true  },\u001b[39m\n\u001b[31m  { id:'vrs',          label:'Veh. Request', icon:'📋', url:'/vrs/',                   live:true  },\u001b[39m\n\u001b[31m  { id:'sales',        label:'Sales',        icon:'💼', url:'/sales/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'stores',       label:'Stores',       icon:'📦', url:'/stores/',                live:true  },\u001b[39m\n\u001b[31m  { id:'client',       label:'Client',       icon:'👤', url:'/client/',                live:true  },\u001b[39m\n\u001b[31m  { id:'team-pins',    label:'Team PINs',    icon:'🔑', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'admin',        label:'Admin',        icon:'🛠️', url:'/admin/',                  live:true  },\u001b[39m\n\u001b[31m  { id:'activity',     label:'Activity',     icon:'🎯', url:'/activity/',               live:true  },\u001b[39m\n\u001b[31m  { id:'hr',           label:'HR',           icon:'👥', url:'/hr/',                     live:true  },\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31mconst MGMT = ['harish', 'pramod'];·\u001b[39m\n\u001b[31m/* ── STATE ── */\u001b[39m\n\u001b[31mlet currentUser     = null;\u001b[39m\n\u001b[31mlet pinBuffer       = '';\u001b[39m\n\u001b[31mlet setPinTarget    = null;\u001b[39m\n\u001b[31mlet pendingShare    = false;\u001b[39m\n\u001b[31mlet _loginAttempts  = 0;\u001b[39m\n\u001b[31mlet _lockoutUntil   = 0;\u001b[39m\n\u001b[31mlet _loginInFlight  = false;·\u001b[39m\n\u001b[31m/* ── UTILS ── */\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => { s.classList.remove('active'); s.style.display = ''; });\u001b[39m\n\u001b[31m  document.getElementById('screen-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet toastTimer;\u001b[39m\n\u001b[31mfunction toast(msg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.classList.add('show');\u001b[39m\n\u001b[31m  clearTimeout(toastTimer);\u001b[39m\n\u001b[31m  toastTimer = setTimeout(() => el.classList.remove('show'), 2800);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fmtINR(n) {\u001b[39m\n\u001b[31m  if (n >= 100000) return '₹' + (n / 100000).toFixed(1) + 'L';\u001b[39m\n\u001b[31m  if (n >= 1000)   return '₹' + (n / 1000).toFixed(1) + 'K';\u001b[39m\n\u001b[31m  return '₹' + Math.round(n);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SESSION ── */\u001b[39m\n\u001b[31mconst SESSION_KEY  = 'lm360-session';\u001b[39m\n\u001b[31mconst EXP_SES_KEY  = '360exp-session';\u001b[39m\n\u001b[31mconst SESSION_TTL  = 12 * 3600 * 1000;·\u001b[39m\n\u001b[31mfunction saveSession(user) {\u001b[39m\n\u001b[31m  const payload = { empId: user.id, name: user.name, role: user.role, loginAt: new Date().toISOString(), source: 'hub' };\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m  localStorage.setItem(EXP_SES_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearSession() {\u001b[39m\n\u001b[31m  localStorage.removeItem(SESSION_KEY);\u001b[39m\n\u001b[31m  localStorage.removeItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const raw = localStorage.getItem(SESSION_KEY) || localStorage.getItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m    if (!raw) return null;\u001b[39m\n\u001b[31m    const s = JSON.parse(raw);\u001b[39m\n\u001b[31m    if (!s.empId || !s.name || !s.role) return null;\u001b[39m\n\u001b[31m    if (Date.now() - new Date(s.loginAt).getTime() > SESSION_TTL) { clearSession(); return null; }\u001b[39m\n\u001b[31m    return s;\u001b[39m\n\u001b[31m  } catch (_) { return null; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGIN FLOW ── */\u001b[39m\n\u001b[31masync function initLogin() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('login-name');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— Choose employee —</option>'; // clear before repopulating\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    emps.forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    [\u001b[39m\n\u001b[31m      { id:'amit',        name:'Amit'             },\u001b[39m\n\u001b[31m      { id:'ashok',       name:'Ashok'            },\u001b[39m\n\u001b[31m      { id:'gurpreet',    name:'Gurpreet'         },\u001b[39m\n\u001b[31m      { id:'harish',      name:'Harish Kumar Lal' },\u001b[39m\n\u001b[31m      { id:'mukesh',      name:'Mukesh'           },\u001b[39m\n\u001b[31m      { id:'narender',    name:'Narender'         },\u001b[39m\n\u001b[31m      { id:'pramod',      name:'Pramod Narang'    },\u001b[39m\n\u001b[31m      { id:'rakesh',      name:'Rakesh'           },\u001b[39m\n\u001b[31m      { id:'sachin',      name:'Sachin'           },\u001b[39m\n\u001b[31m      { id:'shambhu',     name:'Shambhu'          },\u001b[39m\n\u001b[31m      { id:'umashankar',  name:'Umashankar'       },\u001b[39m\n\u001b[31m    ].forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  document.querySelectorAll('.pin-key').forEach(btn =>\u001b[39m\n\u001b[31m    btn.addEventListener('click', () => handlePinKey(btn.dataset.k))\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handlePinKey(k) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (_loginInFlight) return;\u001b[39m\n\u001b[31m  // Lockout guard\u001b[39m\n\u001b[31m  if (_lockoutUntil && Date.now() < _lockoutUntil) {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    errEl.textContent = `Too many attempts — wait ${secs}s`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (_lockoutUntil) { _lockoutUntil = 0; _loginAttempts = 0; }\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (k === 'C') { pinBuffer = ''; }\u001b[39m\n\u001b[31m  else if (k === 'B') { pinBuffer = pinBuffer.slice(0, -1); }\u001b[39m\n\u001b[31m  else {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (!empId) { toast('Select your name first'); return; }\u001b[39m\n\u001b[31m    if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  if (pinBuffer.length === 4) {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (empId) attemptLogin(empId, pinBuffer);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updatePinDisplay() {\u001b[39m\n\u001b[31m  for (let i = 0; i < 4; i++)\u001b[39m\n\u001b[31m    document.getElementById('pd' + i).classList.toggle('filled', i < pinBuffer.length);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _shakePin() {\u001b[39m\n\u001b[31m  const pd = document.getElementById('pin-display');\u001b[39m\n\u001b[31m  pd.classList.add('shake');\u001b[39m\n\u001b[31m  setTimeout(() => pd.classList.remove('shake'), 400);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _startLockout() {\u001b[39m\n\u001b[31m  _lockoutUntil = Date.now() + 30000;\u001b[39m\n\u001b[31m  _loginAttempts = 0;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  const tick = () => {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    if (secs > 0) { errEl.textContent = `Too many attempts — wait ${secs}s`; setTimeout(tick, 500); }\u001b[39m\n\u001b[31m    else { _lockoutUntil = 0; errEl.textContent = ''; }\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  tick();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function attemptLogin(empId, pin) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  _loginInFlight = true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pin }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      _loginAttempts++;\u001b[39m\n\u001b[31m      _shakePin();\u001b[39m\n\u001b[31m      if (_loginAttempts >= 5) { _startLockout(); return; }\u001b[39m\n\u001b[31m      errEl.textContent = `Wrong PIN — ${5 - _loginAttempts} attempt${5 - _loginAttempts !== 1 ? 's' : ''} left`;\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    currentUser = { id: user.id, name: user.name, role: user.role };\u001b[39m\n\u001b[31m    saveSession(currentUser);\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — check connection';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    _loginInFlight = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── HOME SCREEN ── */\u001b[39m\n\u001b[31masync function buildHome() {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const body   = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  // Management users always use hardcoded ACCESS; others prefer server-side hub-access.json\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const tiles  = accessIds.map(id => TILES.find(t => t.id === id)).filter(Boolean);·\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"avatar\\\">${initials}</div>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1 style=\\\"font-size:16px\\\">${currentUser.name}</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">${currentUser.role}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${isMgmt ? '<button class=\\\"btn-icon\\\" id=\\\"btn-hub-ai\\\" title=\\\"AI Config\\\" style=\\\"font-size:18px\\\">🤖</button>' : ''}\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m  if (isMgmt) document.getElementById('btn-hub-ai').addEventListener('click', openHubAiConfig);·\u001b[39m\n\u001b[31m  if (isMgmt) {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"hub-main-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-dash\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">📊</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">Dashboard</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-tools\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">🧰</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">All Tools</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-header\\\">\u001b[39m\n\u001b[31m        <div class=\\\"section-title\\\" style=\\\"margin:0\\\">Quick Actions</div>\u001b[39m\n\u001b[31m        <button class=\\\"btn-icon\\\" id=\\\"btn-hub-qa-cfg\\\" title=\\\"Configure Quick Actions\\\" style=\\\"font-size:18px;padding:8px;min-width:44px;min-height:44px\\\">⚙️</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-grid\\\" id=\\\"hub-qa-grid\\\"></div>`;\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-dash').addEventListener('click',  () => enterHubDash(tiles));\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-tools').addEventListener('click', () => enterHubTools(tiles));\u001b[39m\n\u001b[31m    document.getElementById('btn-hub-qa-cfg').addEventListener('click', openHubQaConfig);\u001b[39m\n\u001b[31m    renderHubQA();\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"tile-grid field\\\" id=\\\"tile-grid\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"text-align:center;font-size:12px;color:var(--muted);padding-top:8px\\\">Tap a tile to open your tool</div>`;\u001b[39m\n\u001b[31m    renderTiles(tiles, 'field');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderTiles(tiles, mode) {\u001b[39m\n\u001b[31m  const grid = document.getElementById('tile-grid');\u001b[39m\n\u001b[31m  tiles.forEach(tile => {\u001b[39m\n\u001b[31m    const div = document.createElement('div');\u001b[39m\n\u001b[31m    div.className = 'tile ' + mode + (tile.live ? '' : ' coming');\u001b[39m\n\u001b[31m    div.innerHTML = `<div class=\\\"tile-icon\\\">${tile.icon}</div><div class=\\\"tile-label\\\">${tile.label}</div>` +\u001b[39m\n\u001b[31m      (tile.live ? '' : '<div class=\\\"soon-badge\\\">Soon</div>');\u001b[39m\n\u001b[31m    div.addEventListener('click', () => handleTile(tile));\u001b[39m\n\u001b[31m    grid.appendChild(div);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handleTile(tile) {\u001b[39m\n\u001b[31m  if (!tile.live) { toast('Coming soon'); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'team-pins') { enterTeamPins(); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'jobs')      { enterJobs();     return; }\u001b[39m\n\u001b[31m  window.location.href = tile.url;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT SUB-VIEWS ── */\u001b[39m\n\u001b[31mfunction _mgmtHeader(title) {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-subview-back\\\" style=\\\"font-size:20px;font-weight:900\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 style=\\\"font-size:16px\\\">${title}</h1></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-subview-back').addEventListener('click', () => buildHome());\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubDash(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('Dashboard');\u001b[39m\n\u001b[31m  const hdr = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const refreshBtn = document.createElement('button');\u001b[39m\n\u001b[31m  refreshBtn.className = 'btn-icon';\u001b[39m\n\u001b[31m  refreshBtn.title = 'Refresh dashboard';\u001b[39m\n\u001b[31m  refreshBtn.setAttribute('aria-label', 'Refresh dashboard');\u001b[39m\n\u001b[31m  refreshBtn.style.cssText = 'font-size:18px';\u001b[39m\n\u001b[31m  refreshBtn.textContent = '🔄';\u001b[39m\n\u001b[31m  refreshBtn.addEventListener('click', loadKPIs);\u001b[39m\n\u001b[31m  hdr.insertBefore(refreshBtn, hdr.querySelector('#btn-logout'));\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Actions Required</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-actions\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Operations</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row kpi-4col\\\" id=\\\"kpi-ops\\\">${skel}${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Production</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-prod\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Installation</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-inst\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">HR</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-hr\\\">${skel}${skel}${skel}</div>`;\u001b[39m\n\u001b[31m  loadKPIs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubTools(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('All Tools');\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  body.innerHTML = `<div class=\\\"tile-grid mgmt\\\" id=\\\"tile-grid\\\"></div>`;\u001b[39m\n\u001b[31m  renderTiles(tiles, 'mgmt');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT AI CONFIG ── */\u001b[39m\n\u001b[31mconst HUB_FIN_HDR = { 'Content-Type': 'application/json', 'Accept-Profile': 'finance' };·\u001b[39m\n\u001b[31masync function openHubAiConfig() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r    = await fetch(`${CFG.api}/upi_config`, { headers: HUB_FIN_HDR });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const get  = key => rows.find(x => x.key === key)?.value;\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-tesseract').value = get('tesseract_enabled') || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-enabled').value   = get('ai_enabled')        || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-model').value     = get('ai_model')           || 'haiku';\u001b[39m\n\u001b[31m  } catch(_) { toast('Could not load AI config'); return; }\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-ai').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveHubAiConfig() {\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-hub-ai-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  const pairs = [\u001b[39m\n\u001b[31m    { key:'tesseract_enabled', value: document.getElementById('hub-ai-tesseract').value },\u001b[39m\n\u001b[31m    { key:'ai_enabled',        value: document.getElementById('hub-ai-enabled').value },\u001b[39m\n\u001b[31m    { key:'ai_model',          value: document.getElementById('hub-ai-model').value },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const results = await Promise.all(pairs.map(p =>\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/upi_config?key=eq.${p.key}`, {\u001b[39m\n\u001b[31m        method: 'PATCH',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'finance', 'Prefer': 'return=minimal' },\u001b[39m\n\u001b[31m        body: JSON.stringify({ value: p.value }),\u001b[39m\n\u001b[31m      })\u001b[39m\n\u001b[31m    ));\u001b[39m\n\u001b[31m    if (results.some(r => !r.ok)) throw new Error('partial failure');\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show');\u001b[39m\n\u001b[31m    toast('✅ AI config saved');\u001b[39m\n\u001b[31m  } catch(_) { toast('Save failed'); }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Save'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function checkApiKeyStatus() {\u001b[39m\n\u001b[31m  const btn  = document.getElementById('btn-check-keys');\u001b[39m\n\u001b[31m  const wrap = document.getElementById('api-key-status-wrap');\u001b[39m\n\u001b[31m  btn.textContent = 'Checking…';\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  wrap.innerHTML = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/tour-ai/key-status');\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Proxy error ' + r.status);\u001b[39m\n\u001b[31m    const d = await r.json();\u001b[39m\n\u001b[31m    function mkBadge(info, label, link) {\u001b[39m\n\u001b[31m      const col  = info.ok ? 'var(--success)' : 'var(--danger)';\u001b[39m\n\u001b[31m      const icon = info.ok ? '✅' : '❌';\u001b[39m\n\u001b[31m      const note = info.ok ? (info.model || 'Valid') : (info.error || 'Error');\u001b[39m\n\u001b[31m      const hint = info.hint ? ' · key ' + info.hint : '';\u001b[39m\n\u001b[31m      return `<div style=\\\"background:var(--card);border:1px solid var(--border);border-left:3px solid ${col};border-radius:8px;padding:10px 12px;display:flex;align-items:center;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:13px;font-weight:600;color:var(--text)\\\">${icon} ${label}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${note}${hint}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <a href=\\\"${link}\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:11px;color:var(--info);text-decoration:none;white-space:nowrap\\\">Billing ↗</a>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    wrap.innerHTML =\u001b[39m\n\u001b[31m      mkBadge(d.anthropic, 'Anthropic API',    'https://console.anthropic.com/settings/billing') +\u001b[39m\n\u001b[31m      mkBadge(d.maps,      'Google Maps API',  'https://console.cloud.google.com/billing');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    wrap.innerHTML = `<div style=\\\"font-size:12px;color:var(--danger)\\\">Error: ${e.message}</div>`;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.textContent = 'Check Again';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT QUICK ACTIONS ── */\u001b[39m\n\u001b[31mconst HUB_QA_DEFS = [\u001b[39m\n\u001b[31m  { id:'offer',       icon:'📋', label:'New Offer',    accessId:'sales',        fn:() => { window.location.href='/sales/'; } },\u001b[39m\n\u001b[31m  { id:'paytm',       icon:'📱', label:'UPI Pay',       accessId:'finance',      fn:() => { window.location.href='/finance/?qa=upi'; } },\u001b[39m\n\u001b[31m  { id:'expense',     icon:'🧾', label:'Expenses',      accessId:'expense',      fn:() => { window.location.href='/expense/'; } },\u001b[39m\n\u001b[31m  { id:'finance',     icon:'💰', label:'Finance',       accessId:'finance',      fn:() => { window.location.href='/finance/'; } },\u001b[39m\n\u001b[31m  { id:'dispatch',    icon:'🚚', label:'Dispatch',      accessId:'dispatch',     fn:() => { window.location.href='/dispatch/'; } },\u001b[39m\n\u001b[31m  { id:'stores',      icon:'📦', label:'Stores',        accessId:'stores',       fn:() => { window.location.href='/stores/'; } },\u001b[39m\n\u001b[31m  { id:'recce',       icon:'📍', label:'Recce',         accessId:'recce',        fn:() => { window.location.href='/recce/'; } },\u001b[39m\n\u001b[31m  { id:'production',  icon:'⚙️', label:'Production',   accessId:'production',   fn:() => { window.location.href='/production/'; } },\u001b[39m\n\u001b[31m  { id:'tours',       icon:'🗺️', label:'Tour Plan',   accessId:'installation', fn:() => { window.location.href='/tour-planner/'; } },\u001b[39m\n\u001b[31m  { id:'installation',icon:'🔧', label:'Installation',  accessId:'installation', fn:() => { window.location.href='/installation/current/'; } },\u001b[39m\n\u001b[31m  { id:'activity',    icon:'🎯', label:'Activity',      accessId:'activity',     fn:() => { window.location.href='/activity/'; } },\u001b[39m\n\u001b[31m];\u001b[39m\n\u001b[31mconst HUB_QA_DEFAULT = ['offer', 'paytm'];\u001b[39m\n\u001b[31mconst HUB_QA_STORE   = '360lm-hub-qa';\u001b[39m\n\u001b[31mlet   _hubQaTempSel  = [];·\u001b[39m\n\u001b[31mfunction getHubQA() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(HUB_QA_STORE));\u001b[39m\n\u001b[31m    if (Array.isArray(s) && s.length) return s;\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m  return [...HUB_QA_DEFAULT];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHubQA() {\u001b[39m\n\u001b[31m  const ids  = getHubQA();\u001b[39m\n\u001b[31m  const grid = document.getElementById('hub-qa-grid');\u001b[39m\n\u001b[31m  if (!grid) return;\u001b[39m\n\u001b[31m  if (!ids.length) {\u001b[39m\n\u001b[31m    grid.innerHTML = `<div style=\\\"color:var(--muted);font-size:13px;grid-column:1/-1;\u001b[39m\n\u001b[31m      text-align:center;padding:16px\\\">Tap ⚙️ to add quick actions</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  grid.innerHTML = ids.map(id => {\u001b[39m\n\u001b[31m    const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!d) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-tile\\\" onclick=\\\"hubQaRun('${id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction hubQaRun(id) {\u001b[39m\n\u001b[31m  const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m  if (d) d.fn();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openHubQaConfig() {\u001b[39m\n\u001b[31m  _hubQaTempSel = [...getHubQA()];\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const visibleDefs = HUB_QA_DEFS.filter(d => accessIds.includes(d.accessId));\u001b[39m\n\u001b[31m  const list = document.getElementById('hub-qa-config-list');\u001b[39m\n\u001b[31m  list.innerHTML = visibleDefs.map(d => {\u001b[39m\n\u001b[31m    const sel = _hubQaTempSel.includes(d.id);\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-item${sel?' selected':''}\\\" id=\\\"hub-qa-item-${d.id}\\\"\u001b[39m\n\u001b[31m        onclick=\\\"toggleHubQaItem('${d.id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-toggle\\\"></div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleHubQaItem(id) {\u001b[39m\n\u001b[31m  const idx = _hubQaTempSel.indexOf(id);\u001b[39m\n\u001b[31m  if (idx >= 0) {\u001b[39m\n\u001b[31m    _hubQaTempSel.splice(idx, 1);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    if (_hubQaTempSel.length >= 6) {\u001b[39m\n\u001b[31m      document.getElementById('hub-qa-cfg-err').textContent = 'Maximum 6 quick actions';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _hubQaTempSel.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  const el = document.getElementById('hub-qa-item-' + id);\u001b[39m\n\u001b[31m  if (el) el.className = 'hub-qa-item' + (_hubQaTempSel.includes(id) ? ' selected' : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction saveHubQaConfig() {\u001b[39m\n\u001b[31m  localStorage.setItem(HUB_QA_STORE, JSON.stringify(_hubQaTempSel));\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.remove('show');\u001b[39m\n\u001b[31m  renderHubQA();\u001b[39m\n\u001b[31m  toast('✅ Quick actions updated');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── KPI FETCH ── */\u001b[39m\n\u001b[31mfunction _hdr(schema) {\u001b[39m\n\u001b[31m  return { 'Content-Type': 'application/json', 'Accept-Profile': schema };\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _safe(p) { return p.catch(() => null); }·\u001b[39m\n\u001b[31masync function loadKPIs() {\u001b[39m\n\u001b[31m  const mon = firstOfMonth();\u001b[39m\n\u001b[31m  const curMonth = new Date().toISOString().slice(0, 7); // YYYY-MM\u001b[39m\n\u001b[31m  const todayIST = (() => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; })();·\u001b[39m\n\u001b[31m  // Reset sections to loading skeletons before fetching (makes refresh visible)\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  [['kpi-actions',3],['kpi-ops',4],['kpi-prod',3],['kpi-inst',3],['kpi-hr',3]].forEach(([id,n]) => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    if (el) el.innerHTML = skel.repeat(n);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  const [\u001b[39m\n\u001b[31m    sheetsRes, claimsRes, salaryRes,\u001b[39m\n\u001b[31m    cashRes,   dispRes,   recceRes,  prodRes,\u001b[39m\n\u001b[31m    instJobsRes, instCtrsRes, storeItemsRes,\u001b[39m\n\u001b[31m    hrAttRes, hrEmpRes, hrConcernRes, hrSlipsRes\u001b[39m\n\u001b[31m  ] = await Promise.all([\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/sheets?status=in.(submitted,escalated)&select=sheet_id`, { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/approved_pending_reimbursement?select=sheet_id`,         { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_register?status=in.(pending,partial)&month=eq.${curMonth}&select=record_id`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/accounts?account_id=eq.petty-cash&select=current_balance`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/dispatches?status=eq.active&select=dispatch_id`,         { headers: _hdr('dispatch') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/submissions?submitted_at=gte.${mon}&select=sub_id`,      { headers: _hdr('recce') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/orders?status=not.in.(dispatched)&select=order_id,status`, { headers: _hdr('production') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/jobs?select=job_id,status`,                              { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/counters?select=counter_id,status`,                      { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/items?active=eq.true&select=id,current_stock,min_stock`, { headers: _hdr('stores') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance?date=eq.${todayIST}&status=eq.present&select=attendance_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/employee_list?select=id`,                               { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance_concerns?status=eq.open&select=concern_id`,  { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_slips?month=eq.${curMonth}&status=eq.finalized&select=slip_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m  ]);·\u001b[39m\n\u001b[31m  const sheets    = sheetsRes?.ok    ? await sheetsRes.json()    : [];\u001b[39m\n\u001b[31m  const claims    = claimsRes?.ok    ? await claimsRes.json()    : [];\u001b[39m\n\u001b[31m  const salary    = salaryRes?.ok    ? await salaryRes.json()    : [];\u001b[39m\n\u001b[31m  const cashArr   = cashRes?.ok      ? await cashRes.json()      : [];\u001b[39m\n\u001b[31m  const disps     = dispRes?.ok      ? await dispRes.json()      : [];\u001b[39m\n\u001b[31m  const recces    = recceRes?.ok     ? await recceRes.json()     : [];\u001b[39m\n\u001b[31m  const prods     = prodRes?.ok      ? await prodRes.json()      : [];\u001b[39m\n\u001b[31m  const instJobs  = instJobsRes?.ok  ? await instJobsRes.json()  : [];\u001b[39m\n\u001b[31m  const instCtrs  = instCtrsRes?.ok  ? await instCtrsRes.json()  : [];\u001b[39m\n\u001b[31m  const storeItems = storeItemsRes?.ok ? await storeItemsRes.json() : [];\u001b[39m\n\u001b[31m  const hrAtt     = hrAttRes?.ok     ? await hrAttRes.json()     : [];\u001b[39m\n\u001b[31m  const hrEmp     = hrEmpRes?.ok     ? await hrEmpRes.json()     : [];\u001b[39m\n\u001b[31m  const hrConcern = hrConcernRes?.ok ? await hrConcernRes.json() : [];\u001b[39m\n\u001b[31m  const hrSlips   = hrSlipsRes?.ok   ? await hrSlipsRes.json()   : [];·\u001b[39m\n\u001b[31m  const cash = Number(cashArr[0]?.current_balance || 0);·\u001b[39m\n\u001b[31m  const actionsEl = document.getElementById('kpi-actions');\u001b[39m\n\u001b[31m  const opsEl     = document.getElementById('kpi-ops');\u001b[39m\n\u001b[31m  const prodEl    = document.getElementById('kpi-prod');\u001b[39m\n\u001b[31m  const instEl    = document.getElementById('kpi-inst');\u001b[39m\n\u001b[31m  const hrEl      = document.getElementById('kpi-hr');\u001b[39m\n\u001b[31m  if (!actionsEl || !opsEl || !prodEl || !instEl) return;·\u001b[39m\n\u001b[31m  // ── Actions Required ────────────────────────────────────────────\u001b[39m\n\u001b[31m  actionsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${sheets.length ? 'alert' : 'ok'}\\\" data-href=\\\"/expense/?tab=approvals\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${sheets.length ? 'red' : 'green'}\\\">${sheets.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Expense Approvals</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${claims.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=claims\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${claims.length ? 'amber' : 'green'}\\\">${claims.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Claims to Reimburse</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${salary.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=salary\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${salary.length ? 'amber' : 'green'}\\\">${salary.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Salary Due This Month</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Operations ──────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const cashColor = cash < 2000 ? 'red' : cash < 5000 ? 'amber' : 'green';\u001b[39m\n\u001b[31m  const cashCard  = cash < 2000 ? 'alert' : cash < 5000 ? 'warn' : 'ok';\u001b[39m\n\u001b[31m  const lowStock = storeItems.filter(i => i.current_stock < i.min_stock).length;\u001b[39m\n\u001b[31m  opsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${cashCard}\\\" data-href=\\\"/finance/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${cashColor}\\\">${fmtINR(cash)}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">${cash < 0 ? '⚠️ ' : ''}Petty Cash</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/dispatch/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${disps.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Dispatches</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/recce/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${recces.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Recce This Month</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${lowStock > 0 ? 'alert' : 'ok'}\\\" data-href=\\\"/stores/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${lowStock > 0 ? 'red' : 'green'}\\\">${lowStock}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Low Stock Items</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Production ───────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const prodPending = prods.filter(o => o.status === 'pending').length;\u001b[39m\n\u001b[31m  const prodActive  = prods.filter(o => o.status === 'in_production' || o.status === 'quality_check').length;\u001b[39m\n\u001b[31m  const prodReady   = prods.filter(o => o.status === 'ready').length;\u001b[39m\n\u001b[31m  prodEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodPending ? 'warn' : 'ok'}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodPending ? 'amber' : 'green'}\\\">${prodPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Pending Orders</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${prodActive}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">In Production</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodReady ? 'ok' : ''}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodReady ? 'green' : ''}\\\">${prodReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Ready to Dispatch</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Installation ─────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const activeJobs  = instJobs.filter(j => j.status === 'active').length;\u001b[39m\n\u001b[31m  const ctrsPending = instCtrs.filter(c => c.status === 'recce_pending' || c.status === 'mockup_pending' || c.status === 'unclear').length;\u001b[39m\n\u001b[31m  const ctrsReady   = instCtrs.filter(c => c.status === 'ready').length;\u001b[39m\n\u001b[31m  instEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${activeJobs ? 'info' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${activeJobs ? 'blue' : 'green'}\\\">${activeJobs}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Jobs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsPending ? 'warn' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsPending ? 'amber' : 'green'}\\\">${ctrsPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Pending</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsReady ? 'ok' : ''}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsReady ? 'green' : ''}\\\">${ctrsReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Ready</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── HR ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31m  if (hrEl) {\u001b[39m\n\u001b[31m    const totalEmp   = hrEmp.length || 0;\u001b[39m\n\u001b[31m    const presentCnt = hrAtt.length;\u001b[39m\n\u001b[31m    const attPct     = totalEmp > 0 ? Math.round(presentCnt / totalEmp * 100) : 0;\u001b[39m\n\u001b[31m    const attColor   = attPct >= 80 ? 'green' : attPct >= 50 ? 'amber' : 'red';\u001b[39m\n\u001b[31m    const attCard    = attPct >= 80 ? 'ok' : attPct >= 50 ? 'warn' : 'alert';\u001b[39m\n\u001b[31m    const concernCnt = hrConcern.length;\u001b[39m\n\u001b[31m    const slipsDue   = hrSlips.length;\u001b[39m\n\u001b[31m    hrEl.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${attCard}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${attColor}\\\">${attPct}%</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Attendance Today</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${concernCnt ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${concernCnt ? 'amber' : 'green'}\\\">${concernCnt}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Open Concerns</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${slipsDue ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${slipsDue ? 'amber' : 'green'}\\\">${slipsDue}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Slips to Pay</div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Wire tap-to-navigate on all KPI cards\u001b[39m\n\u001b[31m  document.querySelectorAll('.kpi-card[data-href]').forEach(card => {\u001b[39m\n\u001b[31m    card.addEventListener('click', () => window.location.href = card.dataset.href);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction firstOfMonth() {\u001b[39m\n\u001b[31m  const d = new Date();\u001b[39m\n\u001b[31m  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGOUT ── */\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  pinBuffer   = '';\u001b[39m\n\u001b[31m  pendingShare = false;\u001b[39m\n\u001b[31m  _loginAttempts = 0; _lockoutUntil = 0; _loginInFlight = false;\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  document.getElementById('login-name').value = '';\u001b[39m\n\u001b[31m  document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m  showScreen('login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── TEAM PINs ── */\u001b[39m\n\u001b[31masync function enterTeamPins() {\u001b[39m\n\u001b[31m  showScreen('team');\u001b[39m\n\u001b[31m  await loadTeamPins();\u001b[39m\n\u001b[31m  // Show icon admin section for harish/pramod\u001b[39m\n\u001b[31m  const sec = document.getElementById('icon-admin-section');\u001b[39m\n\u001b[31m  if (sec) {\u001b[39m\n\u001b[31m    sec.style.display = MGMT.includes(currentUser.id) ? 'block' : 'none';\u001b[39m\n\u001b[31m    if (MGMT.includes(currentUser.id)) refreshSecretDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── ICON ADMIN ────────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst ICON_SECRET_KEY    = 'lm360_icon_secret';\u001b[39m\n\u001b[31mconst ICON_SECRET_INITIAL = 'nlr8Zsqw6XsD0rEXPQ_RfpYiOeNhH-9VglYKoIsElHE';·\u001b[39m\n\u001b[31mfunction getIconSecret() {\u001b[39m\n\u001b[31m  return localStorage.getItem(ICON_SECRET_KEY) || ICON_SECRET_INITIAL;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction setIconSecret(s) {\u001b[39m\n\u001b[31m  localStorage.setItem(ICON_SECRET_KEY, s);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet secretVisible = false;\u001b[39m\n\u001b[31mfunction refreshSecretDisplay() {\u001b[39m\n\u001b[31m  const el = document.getElementById('icon-secret-display');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  const s = getIconSecret();\u001b[39m\n\u001b[31m  el.textContent = secretVisible ? s : s.slice(0,4) + '••••••••••••••••' + s.slice(-4);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleSecretVisible() {\u001b[39m\n\u001b[31m  secretVisible = !secretVisible;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-secret-show');\u001b[39m\n\u001b[31m  if (btn) btn.textContent = secretVisible ? 'Hide' : 'Show';\u001b[39m\n\u001b[31m  refreshSecretDisplay();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function changeIconSecret() {\u001b[39m\n\u001b[31m  const newSecret = document.getElementById('icon-new-secret').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-secret-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (newSecret.length < 16) { errEl.textContent = 'Minimum 16 characters'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/change-secret', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret(), 'X-New-Secret': newSecret }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Failed');\u001b[39m\n\u001b[31m    setIconSecret(newSecret);\u001b[39m\n\u001b[31m    document.getElementById('icon-new-secret').value = '';\u001b[39m\n\u001b[31m    refreshSecretDisplay();\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = '✓ Secret updated';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mdocument.addEventListener('DOMContentLoaded', () => {\u001b[39m\n\u001b[31m  const fileInput = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  if (fileInput) {\u001b[39m\n\u001b[31m    fileInput.addEventListener('change', function() {\u001b[39m\n\u001b[31m      const wrap = document.getElementById('icon-preview-wrap');\u001b[39m\n\u001b[31m      const img  = document.getElementById('icon-preview-img');\u001b[39m\n\u001b[31m      if (this.files[0]) { img.src = URL.createObjectURL(this.files[0]); wrap.style.display = 'block'; }\u001b[39m\n\u001b[31m      else { wrap.style.display = 'none'; }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function uploadIcon() {\u001b[39m\n\u001b[31m  const pwa   = document.getElementById('icon-pwa-select').value;\u001b[39m\n\u001b[31m  const size  = document.getElementById('icon-size-select').value;\u001b[39m\n\u001b[31m  const input = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-upload-err');\u001b[39m\n\u001b[31m  errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (!input.files[0]) { errEl.textContent = 'Choose an image file first'; return; }\u001b[39m\n\u001b[31m  const formData = new FormData();\u001b[39m\n\u001b[31m  formData.append('pwa', pwa);\u001b[39m\n\u001b[31m  formData.append('size', size);\u001b[39m\n\u001b[31m  formData.append('icon', input.files[0]);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret() },\u001b[39m\n\u001b[31m      body: formData\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Upload failed');\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = `✓ Icon updated for ${pwa} (${size}px). Clear browser cache / reinstall to see new icon.`;\u001b[39m\n\u001b[31m    input.value = '';\u001b[39m\n\u001b[31m    document.getElementById('icon-preview-wrap').style.display = 'none';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadTeamPins() {\u001b[39m\n\u001b[31m  const list = document.getElementById('team-list');\u001b[39m\n\u001b[31m  list.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    list.innerHTML = '';\u001b[39m\n\u001b[31m    emps.forEach(emp => {\u001b[39m\n\u001b[31m      const row = document.createElement('div');\u001b[39m\n\u001b[31m      row.className = 'emp-row';\u001b[39m\n\u001b[31m      row.innerHTML = `\u001b[39m\n\u001b[31m        <div style=\\\"min-width:0;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:15px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.name}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.role}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-secondary btn-sm\\\" data-id=\\\"${emp.id}\\\" data-name=\\\"${emp.name}\\\">Set PIN</button>`;\u001b[39m\n\u001b[31m      row.querySelector('button').addEventListener('click', () => openSetPin(emp.id, emp.name));\u001b[39m\n\u001b[31m      list.appendChild(row);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    list.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openSetPin(targetId, targetName) {\u001b[39m\n\u001b[31m  setPinTarget = targetId;\u001b[39m\n\u001b[31m  document.getElementById('setpin-target-name').textContent = targetName;\u001b[39m\n\u001b[31m  ['setpin-new','setpin-confirm','setpin-auth'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-setpin').classList.add('show');\u001b[39m\n\u001b[31m  setTimeout(() => document.getElementById('setpin-new').focus(), 50);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function doSetPin() {\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('setpin-new').value.trim();\u001b[39m\n\u001b[31m  const conf    = document.getElementById('setpin-confirm').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('setpin-auth').value.trim();\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('setpin-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  if (!newPin || !conf || !authPin) { errEl.textContent = 'All fields are required'; return; }\u001b[39m\n\u001b[31m  if (newPin !== conf)              { errEl.textContent = 'New PINs do not match'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))     { errEl.textContent = 'PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-setpin-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_admin_id:  currentUser.id,\u001b[39m\n\u001b[31m        p_admin_pin: authPin,\u001b[39m\n\u001b[31m        p_target_id: setPinTarget,\u001b[39m\n\u001b[31m        p_new_pin:   newPin,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const err = await resp.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Failed to set PIN';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m    toast('✅ PIN updated for ' + document.getElementById('setpin-target-name').textContent);\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Set PIN';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ═══════════════════════════════════════════\u001b[39m\n\u001b[31m   JOBS — Campaign + Tour management\u001b[39m\n\u001b[31m   Access: harish, pramod (campaigns + tours)\u001b[39m\n\u001b[31m           rakesh (tours only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ */\u001b[39m\n\u001b[31mconst INST_RD = { 'Accept-Profile': 'installation' };\u001b[39m\n\u001b[31mconst INST_WR = { 'Content-Type':'application/json','Accept-Profile':'installation','Content-Profile':'installation' };·\u001b[39m\n\u001b[31mlet _currentCampaignId = null;\u001b[39m\n\u001b[31mlet _allEmployees      = [];\u001b[39m\n\u001b[31mlet _editTourId        = null;  // null = create mode, string = edit mode·\u001b[39m\n\u001b[31m// ── Helpers ──────────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _slugify(s) {\u001b[39m\n\u001b[31m  return s.toUpperCase().trim()\u001b[39m\n\u001b[31m    .replace(/[^A-Z0-9]+/g, '_')\u001b[39m\n\u001b[31m    .replace(/^_|_$/g, '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _buildCampaignId(year, brand, client, campaign, phase) {\u001b[39m\n\u001b[31m  const nn = String(phase || 1).padStart(2, '0');\u001b[39m\n\u001b[31m  return `${year}-${_slugify(brand)}-${_slugify(client)}-${_slugify(campaign)}-${nn}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _campStatusBadge(s) {\u001b[39m\n\u001b[31m  const map = { active:'ok', completed:'', on_hold:'warn' };\u001b[39m\n\u001b[31m  const col = { active:'green', completed:'', on_hold:'amber' };\u001b[39m\n\u001b[31m  return `<span class=\\\"kpi-val ${col[s]||''}\\\" style=\\\"font-size:12px;font-weight:700\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _tourStatusBadge(s) {\u001b[39m\n\u001b[31m  const colors = { active:'var(--success)', planned:'var(--info)', completed:'var(--muted)', cancelled:'var(--danger)' };\u001b[39m\n\u001b[31m  return `<span style=\\\"font-size:11px;font-weight:700;padding:2px 8px;border-radius:10px;\u001b[39m\n\u001b[31m    background:var(--card);color:${colors[s]||'var(--muted)'}\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _canCreateCampaign() { return ['harish','pramod'].includes(currentUser?.id); }\u001b[39m\n\u001b[31mfunction _canCreateTour()     { return ['harish','pramod','rakesh'].includes(currentUser?.id); }·\u001b[39m\n\u001b[31m// ── Jobs screen ───────────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterJobs() {\u001b[39m\n\u001b[31m  showScreen('jobs');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').style.display = _canCreateCampaign() ? '' : 'none';\u001b[39m\n\u001b[31m  await loadJobs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadJobs() {\u001b[39m\n\u001b[31m  const el = document.getElementById('jobs-body');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?order=created_at.desc`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?select=tour_id,campaign_id,status`, { headers: INST_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const campaigns = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours     = tResp.ok ? await tResp.json() : [];·\u001b[39m\n\u001b[31m    if (!campaigns.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty\\\"><div style=\\\"font-size:32px\\\">📋</div><div>No campaigns yet</div></div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    el.innerHTML = campaigns.map(c => {\u001b[39m\n\u001b[31m      const cTours  = tours.filter(t => t.campaign_id === c.campaign_id);\u001b[39m\n\u001b[31m      const active  = cTours.filter(t => t.status === 'active').length;\u001b[39m\n\u001b[31m      const total   = cTours.length;\u001b[39m\n\u001b[31m      const statusColors = { active:'var(--success)', on_hold:'var(--accent2)', completed:'var(--muted)' };\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card tappable\\\" style=\\\"padding:14px\\\" onclick=\\\"enterCampaign('${c.campaign_id}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:flex-start;gap:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:15px;font-weight:700\\\">${c.brand} — ${c.campaign_name}\u001b[39m\n\u001b[31m                ${c.phase > 1 ? `<span style=\\\"font-size:11px;color:var(--muted)\\\"> Phase ${c.phase}</span>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:2px\\\">${c.client} · ${c.year}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:4px\\\">\u001b[39m\n\u001b[31m                ${total} tour${total!==1?'s':''} · ${active} active\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;align-items:flex-end;gap:6px\\\">\u001b[39m\n\u001b[31m              <span style=\\\"font-size:11px;font-weight:700;color:${statusColors[c.status]||'var(--muted)'}\\\">\u001b[39m\n\u001b[31m                ${(c.status||'').replace('_',' ')}\u001b[39m\n\u001b[31m              </span>\u001b[39m\n\u001b[31m              <span style=\\\"font-size:18px;color:var(--muted)\\\">›</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-top:8px;word-break:break-all\\\">${c.campaign_id}</div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign detail ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterCampaign(campaignId) {\u001b[39m\n\u001b[31m  _currentCampaignId = campaignId;\u001b[39m\n\u001b[31m  showScreen('campaign');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').style.display = _canCreateTour() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-sub').textContent   = '';\u001b[39m\n\u001b[31m  await loadCampaign(campaignId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCampaign(campaignId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('campaign-body');\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp, empResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(campaignId)}&order=start_date`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const camps = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours = tResp.ok ? await tResp.json() : [];\u001b[39m\n\u001b[31m    _allEmployees = empResp.ok ? await empResp.json() : [];·\u001b[39m\n\u001b[31m    if (!camps.length) { body.innerHTML = '<div class=\\\"empty\\\">Campaign not found</div>'; return; }\u001b[39m\n\u001b[31m    const c = camps[0];·\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-title').textContent = `${c.brand} — ${c.campaign_name}`;\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-sub').textContent   = `${c.client} · ${c.year}${c.phase>1?' · Phase '+c.phase:''}`;·\u001b[39m\n\u001b[31m    const empMap = {};\u001b[39m\n\u001b[31m    _allEmployees.forEach(e => empMap[e.id] = e.name);·\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"padding:12px 14px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Campaign ID</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:10px;color:var(--muted);word-break:break-all;text-align:right;max-width:65%\\\">${c.campaign_id}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Client</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:13px;font-weight:600\\\">${c.client}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${_canCreateCampaign() ? `\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div class=\\\"section-title\\\" style=\\\"margin-bottom:6px\\\">Campaign Status</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m            ${['active','on_hold','completed'].map(s =>\u001b[39m\n\u001b[31m              `<button class=\\\"status-chip${c.status===s?' sel':''}\\\" onclick=\\\"updateCampaignStatus('${c.campaign_id}','${s}')\\\">\u001b[39m\n\u001b[31m                ${s.replace('_',' ')}\u001b[39m\n\u001b[31m              </button>`).join('')}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>` : ''}\u001b[39m\n\u001b[31m      </div>·\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:4px\\\">Tours (${tours.length})</div>\u001b[39m\n\u001b[31m      ${tours.length === 0 ? `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\">\u001b[39m\n\u001b[31m        <div>No tours yet</div>\u001b[39m\n\u001b[31m        ${_canCreateTour() ? '<div style=\\\"font-size:12px\\\">Tap ＋ to add the first tour</div>' : ''}\u001b[39m\n\u001b[31m      </div>` :\u001b[39m\n\u001b[31m      tours.map(t => {\u001b[39m\n\u001b[31m        const empNames = (t.assigned_employees || []).map(id => empMap[id] || id).join(', ') || 'No one assigned';\u001b[39m\n\u001b[31m        const dates    = t.start_date ? `${t.start_date} → ${t.end_date||'?'}` : 'Dates not set';\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m          <div class=\\\"card\\\" style=\\\"padding:14px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;align-items:flex-start;gap:8px\\\">\u001b[39m\n\u001b[31m              <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m                <div style=\\\"font-size:14px;font-weight:700\\\">${t.title || t.tour_id}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">📅 ${dates}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">👥 ${empNames}</div>\u001b[39m\n\u001b[31m                ${t.notes ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">📝 ${t.notes}</div>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              ${_tourStatusBadge(t.status)}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            ${_canCreateTour() ? `\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;gap:6px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m              ${['planned','active','completed'].map(s =>\u001b[39m\n\u001b[31m                `<button class=\\\"status-chip${t.status===s?' sel':''}\\\"\u001b[39m\n\u001b[31m                  onclick=\\\"updateTourStatus('${t.tour_id}','${s}')\\\">${s}</button>`).join('')}\u001b[39m\n\u001b[31m              <button class=\\\"status-chip\\\" onclick=\\\"openEditTour('${t.tour_id}')\\\"\u001b[39m\n\u001b[31m                style=\\\"margin-left:auto;background:var(--surface);border-color:#3a3a6e;color:#8888ff\\\">\u001b[39m\n\u001b[31m                ✏️ Edit\u001b[39m\n\u001b[31m              </button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m            <div style=\\\"font-size:10px;color:var(--muted);margin-top:6px\\\">${t.tour_id}</div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m      <div style=\\\"height:20px\\\"></div>`;\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateTourStatus(tourId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Tour marked ${status}`);\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateCampaignStatus(campaignId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Campaign marked ${status.replace('_',' ')}`);\u001b[39m\n\u001b[31m    loadCampaign(campaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Campaign modal ────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewCampaign() {\u001b[39m\n\u001b[31m  if (!_canCreateCampaign()) { toast('Only Harish or Pramod can create campaigns'); return; }\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('nc-year').value    = new Date().getFullYear();\u001b[39m\n\u001b[31m  document.getElementById('nc-phase').value   = '1';\u001b[39m\n\u001b[31m  document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  document.getElementById('nc-err').textContent     = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-new-campaign').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateCampaignPreview() {\u001b[39m\n\u001b[31m  const year  = document.getElementById('nc-year').value;\u001b[39m\n\u001b[31m  const brand = document.getElementById('nc-brand').value;\u001b[39m\n\u001b[31m  const clt   = document.getElementById('nc-client').value;\u001b[39m\n\u001b[31m  const camp  = document.getElementById('nc-campaign').value;\u001b[39m\n\u001b[31m  const phase = document.getElementById('nc-phase').value;\u001b[39m\n\u001b[31m  if (brand && clt && camp) {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = _buildCampaignId(year, brand, clt, camp, phase);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewCampaign() {\u001b[39m\n\u001b[31m  const year   = Number(document.getElementById('nc-year').value);\u001b[39m\n\u001b[31m  const brand  = document.getElementById('nc-brand').value.trim();\u001b[39m\n\u001b[31m  const client = document.getElementById('nc-client').value.trim();\u001b[39m\n\u001b[31m  const cname  = document.getElementById('nc-campaign').value.trim();\u001b[39m\n\u001b[31m  const phase  = Number(document.getElementById('nc-phase').value) || 1;\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('nc-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const brandEl = document.getElementById('nc-brand');\u001b[39m\n\u001b[31m  const clientEl = document.getElementById('nc-client');\u001b[39m\n\u001b[31m  const cnameEl = document.getElementById('nc-campaign');\u001b[39m\n\u001b[31m  [brandEl, clientEl, cnameEl].forEach(el => el.style.borderColor = '');\u001b[39m\n\u001b[31m  let _missingField = false;\u001b[39m\n\u001b[31m  if (!brand)  { brandEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!client) { clientEl.style.borderColor = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!cname)  { cnameEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (_missingField) { errEl.textContent = 'Brand, Client and Campaign Name are required'; return; }\u001b[39m\n\u001b[31m  const campaignId = _buildCampaignId(year, brand, client, cname, phase);·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nc-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/campaigns`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ campaign_id:campaignId, year, brand, client, campaign_name:cname, phase, created_by:currentUser.id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || 'Failed to create';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show');\u001b[39m\n\u001b[31m    toast(`✅ Campaign created`);\u001b[39m\n\u001b[31m    loadJobs();\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Create'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Tour modal ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _buildEmpCheckboxes(assigned = []) {\u001b[39m\n\u001b[31m  return _allEmployees.map(e =>\u001b[39m\n\u001b[31m    `<label style=\\\"display:flex;align-items:center;gap:10px;padding:8px 10px;background:var(--card);\u001b[39m\n\u001b[31m      border:1px solid var(--border);border-radius:8px;cursor:pointer\\\">\u001b[39m\n\u001b[31m      <input type=\\\"checkbox\\\" value=\\\"${e.id}\\\" ${assigned.includes(e.id)?'checked':''} style=\\\"width:16px;height:16px;accent-color:var(--accent)\\\">\u001b[39m\n\u001b[31m      <span style=\\\"font-size:14px;font-weight:600\\\">${e.name}</span>\u001b[39m\n\u001b[31m      <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${e.role}</span>\u001b[39m\n\u001b[31m    </label>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openNewTour() {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = null;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '🗺️ New Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Create Tour';\u001b[39m\n\u001b[31m  document.getElementById('nt-title').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-start').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-end').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-notes').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(_currentCampaignId)}&select=tour_id`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const existing = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    const nn = String(existing.length + 1).padStart(2, '0');\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T${nn}`;\u001b[39m\n\u001b[31m  } catch(_) {\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T01`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (_allEmployees.length)\u001b[39m\n\u001b[31m    document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes();·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEditTour(tourId) {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = tourId;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '✏️ Edit Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Save Changes';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const tours = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    if (!tours.length) { toast('Tour not found'); return; }\u001b[39m\n\u001b[31m    const t = tours[0];\u001b[39m\n\u001b[31m    document.getElementById('nt-title').value = t.title || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-start').value = t.start_date || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-end').value   = t.end_date   || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-notes').value = t.notes      || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = tourId;\u001b[39m\n\u001b[31m    if (_allEmployees.length)\u001b[39m\n\u001b[31m      document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes(t.assigned_employees || []);\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to load tour'); return; }·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewTour() {\u001b[39m\n\u001b[31m  const title = document.getElementById('nt-title').value.trim();\u001b[39m\n\u001b[31m  const start = document.getElementById('nt-start').value || null;\u001b[39m\n\u001b[31m  const end   = document.getElementById('nt-end').value   || null;\u001b[39m\n\u001b[31m  const notes = document.getElementById('nt-notes').value.trim() || null;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('nt-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const checked = [...document.querySelectorAll('#nt-employees input:checked')].map(cb => cb.value);\u001b[39m\n\u001b[31m  if (!checked.length) { errEl.textContent = 'Assign at least one employee'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nt-save');\u001b[39m\n\u001b[31m  const wasEdit = !!_editTourId;\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = wasEdit ? 'Saving…' : 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editTourId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(_editTourId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      const tourId = document.getElementById('nt-id-preview').textContent;\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          tour_id: tourId, campaign_id: _currentCampaignId,\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes, status: 'planned',\u001b[39m\n\u001b[31m          created_by: currentUser.id,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || (_editTourId ? 'Failed to save' : 'Failed to create');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show');\u001b[39m\n\u001b[31m    toast(_editTourId ? '✅ Tour updated' : '✅ Tour created');\u001b[39m\n\u001b[31m    _editTourId = null;\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = wasEdit ? 'Save Changes' : 'Create Tour';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── EVENT WIRING ── */\u001b[39m\n\u001b[31mfunction wireEvents() {\u001b[39m\n\u001b[31m  // Jobs screens\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-back').addEventListener('click',     () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-refresh').addEventListener('click',  loadJobs);\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').addEventListener('click',  openNewCampaign);\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-back').addEventListener('click', () => showScreen('jobs'));\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-refresh').addEventListener('click', () => loadCampaign(_currentCampaignId));\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').addEventListener('click',      openNewTour);·\u001b[39m\n\u001b[31m  // New Campaign modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-save').addEventListener('click', saveNewCampaign);\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign','nc-year','nc-phase'].forEach(id =>\u001b[39m\n\u001b[31m    document.getElementById(id).addEventListener('input', updateCampaignPreview));·\u001b[39m\n\u001b[31m  // New Tour modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').addEventListener('click', saveNewTour);·\u001b[39m\n\u001b[31m  document.getElementById('btn-team-back').addEventListener('click', () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-team-refresh').addEventListener('click', loadTeamPins);\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-cancel').addEventListener('click', () => {\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-save').addEventListener('click', doSetPin);·\u001b[39m\n\u001b[31m  // Set PIN — progressive field reveal\u001b[39m\n\u001b[31m  document.getElementById('setpin-new').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const val = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const showConf = val.length === 4;\u001b[39m\n\u001b[31m    document.getElementById('setpin-confirm-group').style.display = showConf ? '' : 'none';\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m    if (!showConf) { document.getElementById('setpin-confirm').value = ''; document.getElementById('setpin-auth').value = ''; }\u001b[39m\n\u001b[31m    if (showConf) setTimeout(() => document.getElementById('setpin-confirm').focus(), 20);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const newVal = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const conf   = document.getElementById('setpin-confirm').value;\u001b[39m\n\u001b[31m    const showAuth = conf.length === 4 && conf === newVal;\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = showAuth ? '' : 'none';\u001b[39m\n\u001b[31m    if (!showAuth) document.getElementById('setpin-auth').value = '';\u001b[39m\n\u001b[31m    if (showAuth) setTimeout(() => document.getElementById('setpin-auth').focus(), 20);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // Hub AI config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-save').addEventListener('click',   saveHubAiConfig);\u001b[39m\n\u001b[31m  document.getElementById('btn-check-keys').addEventListener('click',    checkApiKeyStatus);·\u001b[39m\n\u001b[31m  // Hub QA config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-qa').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-save').addEventListener('click', saveHubQaConfig);·\u001b[39m\n\u001b[31m  // Share picker\u001b[39m\n\u001b[31m  document.getElementById('share-to-expense').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/expense/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-recce').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/recce/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-installation').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/installation/current/?share=1';\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-04: employee dropdown change resets PIN buffer and lockout\u001b[39m\n\u001b[31m  document.getElementById('login-name').addEventListener('change', () => {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-07: ESC key closes any open modal\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (e.key === 'Escape') {\u001b[39m\n\u001b[31m      document.querySelectorAll('.modal-overlay.show').forEach(m => m.classList.remove('show'));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-11: keyboard digit/backspace input on login screen\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (!document.getElementById('screen-login').classList.contains('active')) return;\u001b[39m\n\u001b[31m    if (e.key >= '0' && e.key <= '9') { handlePinKey(e.key); }\u001b[39m\n\u001b[31m    else if (e.key === 'Backspace') { e.preventDefault(); handlePinKey('B'); }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-02/B-08: re-check session TTL when user returns to the tab\u001b[39m\n\u001b[31m  document.addEventListener('visibilitychange', () => {\u001b[39m\n\u001b[31m    if (document.visibilityState === 'visible' && currentUser) {\u001b[39m\n\u001b[31m      if (!loadSession()) doLogout();\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── BOOT ── */\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  wireEvents();·\u001b[39m\n\u001b[31m  if (location.search.includes('share=1')) {\u001b[39m\n\u001b[31m    pendingShare = true;\u001b[39m\n\u001b[31m    history.replaceState({}, '', '/hub/');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Always populate the login dropdown (needed if user logs out later)\u001b[39m\n\u001b[31m  initLogin(); // fire-and-forget — does not block home screen path·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess) {\u001b[39m\n\u001b[31m    currentUser = { id: sess.empId, name: sess.name, role: sess.role };\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  setTimeout(() => showScreen('login'), 1500);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SERVICE WORKER: install + update ── */\u001b[39m\n\u001b[31mif ('serviceWorker' in navigator) {\u001b[39m\n\u001b[31m  navigator.serviceWorker.register('/hub/sw.js').then(reg => {·\u001b[39m\n\u001b[31m    // Install banner: show when browser signals app is installable\u001b[39m\n\u001b[31m    let deferredInstall = null;\u001b[39m\n\u001b[31m    window.addEventListener('beforeinstallprompt', e => {\u001b[39m\n\u001b[31m      e.preventDefault();\u001b[39m\n\u001b[31m      deferredInstall = e;\u001b[39m\n\u001b[31m      if (!window.matchMedia('(display-mode: standalone)').matches) {\u001b[39m\n\u001b[31m        const delay = document.getElementById('update-banner').classList.contains('show') ? 3500 : 0;\u001b[39m\n\u001b[31m        setTimeout(() => document.getElementById('install-banner').classList.add('show'), delay);\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    window.addEventListener('appinstalled', () => {\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install').addEventListener('click', async () => {\u001b[39m\n\u001b[31m      if (!deferredInstall) return;\u001b[39m\n\u001b[31m      deferredInstall.prompt();\u001b[39m\n\u001b[31m      await deferredInstall.userChoice;\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m    // Update banner: show when a new SW version is waiting to activate\u001b[39m\n\u001b[31m    function showUpdateBanner() {\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.add('show');\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (reg.waiting) showUpdateBanner();·\u001b[39m\n\u001b[31m    reg.addEventListener('updatefound', () => {\u001b[39m\n\u001b[31m      const newSW = reg.installing;\u001b[39m\n\u001b[31m      newSW.addEventListener('statechange', () => {\u001b[39m\n\u001b[31m        if (newSW.state === 'installed' && navigator.serviceWorker.controller) {\u001b[39m\n\u001b[31m          showUpdateBanner();\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    document.getElementById('btn-update').addEventListener('click', () => {\u001b[39m\n\u001b[31m      if (reg.waiting) {\u001b[39m\n\u001b[31m        reg.waiting.postMessage({ type: 'SKIP_WAITING' });\u001b[39m\n\u001b[31m        navigator.serviceWorker.addEventListener('controllerchange', () => {\u001b[39m\n\u001b[31m          window.location.reload();\u001b[39m\n\u001b[31m        }, { once: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-update-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m  }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mboot();\u001b[39m\n\u001b[31m</script>··\u001b[39m\n\u001b[31m</body></html>\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:331:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 331
                        },
                        "snippet": "  329 |     await page.goto(BASE_URL);\n  330 |     const src = await page.content();\n> 331 |     expect(src).toContain('recce-v10');\n      |                 ^\n  332 |     expect(src).not.toContain('recce-v9');\n  333 |   });\n  334 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 331
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"recce-v10\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html><html lang=\\\"en\\\"><head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#FF6B35\\\">\u001b[39m\n\u001b[31m<meta name=\\\"mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-status-bar-style\\\" content=\\\"black-translucent\\\">\u001b[39m\n\u001b[31m<title>360LM</title>\u001b[39m\n\u001b[31m<meta name=\\\"description\\\" content=\\\"360LM Operations Hub — employee tools\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/hub/manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"icon\\\" href=\\\"/hub/icons/icon-192.png\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--surface:#161616;--card:#1e1e1e;--border:#2a2a2a;\u001b[39m\n\u001b[31m  --accent:#FF6B35;--accent2:#FFB800;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --danger:#e74c3c;--success:#2ecc71;--info:#3498db;\u001b[39m\n\u001b[31m  --radius:12px;--font:'Segoe UI',system-ui,sans-serif;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:var(--font);min-height:100vh;overflow-x:hidden}\u001b[39m\n\u001b[31m.screen{display:none;min-height:100vh;flex-direction:column}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── HEADER ── */\u001b[39m\n\u001b[31m.header{background:var(--surface);border-bottom:1px solid var(--border);padding:14px 16px;\u001b[39m\n\u001b[31m  display:flex;align-items:center;gap:12px;position:sticky;top:0;z-index:10;flex-shrink:0}\u001b[39m\n\u001b[31m.header h1{font-size:18px;font-weight:700;color:var(--accent);flex:1}\u001b[39m\n\u001b[31m.header .sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.btn-icon{background:none;border:none;color:var(--muted);font-size:22px;cursor:pointer;\u001b[39m\n\u001b[31m  padding:4px;border-radius:8px;line-height:1;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m.btn-icon:active{background:var(--border)}·\u001b[39m\n\u001b[31m/* ── BODY ── */\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:16px}·\u001b[39m\n\u001b[31m/* ── SPLASH ── */\u001b[39m\n\u001b[31m#screen-splash{align-items:center;justify-content:center;gap:8px}\u001b[39m\n\u001b[31m.splash-logo{font-size:72px;font-weight:900;color:var(--accent);letter-spacing:-2px;line-height:1}\u001b[39m\n\u001b[31m.splash-sub{font-size:15px;color:var(--muted);letter-spacing:2px;text-transform:uppercase}\u001b[39m\n\u001b[31m.splash-dot{width:8px;height:8px;border-radius:50%;background:var(--accent);\u001b[39m\n\u001b[31m  animation:pulse 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes pulse{0%,100%{opacity:.3;transform:scale(.8)}50%{opacity:1;transform:scale(1)}}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  min-height:100dvh;padding:24px;gap:20px;overflow-y:auto}\u001b[39m\n\u001b[31m.login-logo{font-size:44px;font-weight:900;color:var(--accent);letter-spacing:-1px}\u001b[39m\n\u001b[31m.login-sub{font-size:13px;color:var(--muted);text-transform:uppercase;letter-spacing:2px}\u001b[39m\n\u001b[31m.form-group{width:100%;max-width:360px;display:flex;flex-direction:column;gap:8px}\u001b[39m\n\u001b[31m.form-group label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px}\u001b[39m\n\u001b[31mselect{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31mselect:focus{border-color:var(--accent)}·\u001b[39m\n\u001b[31m/* ── PIN PAD ── */\u001b[39m\n\u001b[31m.pin-display{display:flex;gap:14px;justify-content:center;margin:4px 0}\u001b[39m\n\u001b[31m.pin-dot{width:16px;height:16px;border-radius:50%;border:2px solid var(--muted);background:transparent;transition:.15s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.pin-pad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;max-width:260px;margin:0 auto;width:100%}\u001b[39m\n\u001b[31m.pin-key{background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  font-size:22px;font-weight:600;padding:16px;border-radius:var(--radius);cursor:pointer;\u001b[39m\n\u001b[31m  text-align:center;line-height:1;user-select:none}\u001b[39m\n\u001b[31m.pin-key:active{background:var(--border)}\u001b[39m\n\u001b[31m.pin-key.clr{color:var(--danger);font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.pin-key.back{color:var(--accent2)}\u001b[39m\n\u001b[31m.login-err{font-size:13px;color:var(--danger);text-align:center;min-height:18px}\u001b[39m\n\u001b[31m@keyframes shake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-6px)}40%,80%{transform:translateX(6px)}}\u001b[39m\n\u001b[31m.pin-display.shake{animation:shake .4s ease}·\u001b[39m\n\u001b[31m/* ── KPI CARDS ── */\u001b[39m\n\u001b[31m.kpi-row{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\u001b[39m\n\u001b[31m.kpi-4col{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m#home-body{gap:10px}\u001b[39m\n\u001b[31m.kpi-card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:10px 8px;text-align:center;border-left:3px solid var(--accent);cursor:default}\u001b[39m\n\u001b[31m.kpi-card.alert{border-left-color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-card.warn{border-left-color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-card.ok{border-left-color:var(--success)}\u001b[39m\n\u001b[31m.kpi-card.info{border-left-color:var(--info)}\u001b[39m\n\u001b[31m.kpi-val{font-size:18px;font-weight:900;color:var(--accent);line-height:1.2}\u001b[39m\n\u001b[31m.kpi-val.red{color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-val.amber{color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-val.green{color:var(--success)}\u001b[39m\n\u001b[31m.kpi-val.blue{color:var(--info)}\u001b[39m\n\u001b[31m.kpi-lbl{font-size:10px;color:var(--muted);margin-top:4px;text-transform:uppercase;letter-spacing:.4px;line-height:1.3}\u001b[39m\n\u001b[31m.kpi-skel{background:var(--border);border-radius:4px;height:24px;margin-bottom:4px;\u001b[39m\n\u001b[31m  animation:shimmer 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes shimmer{0%,100%{opacity:.5}50%{opacity:1}}·\u001b[39m\n\u001b[31m/* ── SECTION TITLE ── */\u001b[39m\n\u001b[31m.section-title{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.8px}·\u001b[39m\n\u001b[31m/* ── MGMT HOME: MAIN TILES ── */\u001b[39m\n\u001b[31m.hub-main-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px}\u001b[39m\n\u001b[31m.hub-main-tile{background:var(--card);border:2px solid var(--border);border-radius:18px;\u001b[39m\n\u001b[31m  padding:36px 12px 28px;text-align:center;cursor:pointer;transition:background .15s,border-color .15s}\u001b[39m\n\u001b[31m.hub-main-tile:active{background:#1a2a1a;border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-main-icon{font-size:40px;line-height:1;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-main-label{font-size:15px;font-weight:800;color:var(--text)}\u001b[39m\n\u001b[31m/* ── MGMT HOME: QUICK ACTIONS ── */\u001b[39m\n\u001b[31m.hub-qa-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-qa-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(88px,1fr));gap:10px}\u001b[39m\n\u001b[31m.hub-qa-tile{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px 8px;text-align:center;cursor:pointer;display:flex;flex-direction:column;\u001b[39m\n\u001b[31m  align-items:center;gap:6px;transition:background .15s}\u001b[39m\n\u001b[31m.hub-qa-tile:active{background:var(--border)}\u001b[39m\n\u001b[31m.hub-qa-icon{font-size:26px;line-height:1}\u001b[39m\n\u001b[31m.hub-qa-label{font-size:11px;font-weight:700;color:var(--text);line-height:1.3}\u001b[39m\n\u001b[31m.hub-qa-item{display:flex;align-items:center;gap:12px;padding:10px 0;\u001b[39m\n\u001b[31m  border-bottom:1px solid var(--border);cursor:pointer}\u001b[39m\n\u001b[31m.hub-qa-item:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.hub-qa-item-icon{font-size:22px;width:32px;text-align:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item-label{flex:1;font-size:14px;font-weight:600}\u001b[39m\n\u001b[31m.hub-qa-item-toggle{width:20px;height:20px;border-radius:4px;border:2px solid var(--border);\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle::after{content:'✓';color:#fff;font-weight:700;font-size:12px}·\u001b[39m\n\u001b[31m/* ── TILE GRID ── */\u001b[39m\n\u001b[31m.tile-grid{display:grid;gap:12px}\u001b[39m\n\u001b[31m.tile-grid.mgmt{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m.tile-grid.field{grid-template-columns:repeat(2,1fr)}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:18px 8px;display:flex;flex-direction:column;align-items:center;gap:8px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s;position:relative}\u001b[39m\n\u001b[31m.tile:active{background:var(--border);transform:scale(.97)}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px;line-height:1}\u001b[39m\n\u001b[31m.tile-label{font-size:11px;font-weight:700;color:var(--text);text-align:center;text-transform:uppercase;letter-spacing:.4px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-icon{font-size:24px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-label{font-size:10px}\u001b[39m\n\u001b[31m.tile.coming{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.tile.coming:active{transform:none;background:var(--card)}\u001b[39m\n\u001b[31m.soon-badge{position:absolute;top:6px;right:6px;background:var(--accent2);color:#000;\u001b[39m\n\u001b[31m  font-size:8px;font-weight:800;padding:2px 5px;border-radius:20px;text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── USER BADGE ── */\u001b[39m\n\u001b[31m.user-badge{display:flex;align-items:center;gap:12px}\u001b[39m\n\u001b[31m.avatar{width:44px;height:44px;background:var(--accent);border-radius:50%;\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:800;\u001b[39m\n\u001b[31m  color:#fff;flex-shrink:0;text-transform:uppercase}\u001b[39m\n\u001b[31m.user-name{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.user-role{font-size:12px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── TEAM PINS ── */\u001b[39m\n\u001b[31m.emp-row{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px}\u001b[39m\n\u001b[31m.btn{display:flex;align-items:center;justify-content:center;gap:6px;border:none;\u001b[39m\n\u001b[31m  border-radius:var(--radius);font-size:15px;font-weight:700;padding:12px 18px;\u001b[39m\n\u001b[31m  cursor:pointer;width:100%;transition:.15s;font-family:var(--font)}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--card);border:1px solid var(--border);color:var(--text)}\u001b[39m\n\u001b[31m.btn-sm{padding:7px 12px;font-size:12px;font-weight:700;width:auto;flex-shrink:0}\u001b[39m\n\u001b[31m.info-note{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;font-size:13px;color:var(--muted);line-height:1.5}·\u001b[39m\n\u001b[31m/* ── MODAL ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:200;\u001b[39m\n\u001b[31m  align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;\u001b[39m\n\u001b[31m  padding:24px;width:100%;max-width:480px;display:flex;flex-direction:column;gap:14px;\u001b[39m\n\u001b[31m  max-height:90dvh;overflow-y:auto;-webkit-overflow-scrolling:touch}\u001b[39m\n\u001b[31m.modal-title{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.modal-box input[type=password]{\u001b[39m\n\u001b[31m  width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31m.modal-box input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.action-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px}·\u001b[39m\n\u001b[31m/* ── SHARE PICKER ── */\u001b[39m\n\u001b[31m#screen-share .body{align-items:center;justify-content:center;gap:24px}\u001b[39m\n\u001b[31m.share-prompt{font-size:15px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.share-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;width:100%;max-width:360px}\u001b[39m\n\u001b[31m.share-tile{background:var(--card);border:2px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:24px 8px;display:flex;flex-direction:column;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s}\u001b[39m\n\u001b[31m.share-tile:active{background:var(--border);transform:scale(.97);border-color:var(--accent)}\u001b[39m\n\u001b[31m.share-tile-icon{font-size:36px;line-height:1}\u001b[39m\n\u001b[31m.share-tile-label{font-size:12px;font-weight:700;color:var(--text);text-align:center;\u001b[39m\n\u001b[31m  text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── INSTALL BANNER ── */\u001b[39m\n\u001b[31m#install-banner{display:none;position:fixed;bottom:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:linear-gradient(135deg,#1a1a1a,#0d2010);\u001b[39m\n\u001b[31m  border-top:1px solid var(--accent);padding:14px 16px;\u001b[39m\n\u001b[31m  align-items:center;gap:12px;box-shadow:0 -4px 24px rgba(0,0,0,.7)}\u001b[39m\n\u001b[31m#install-banner.show{display:flex}\u001b[39m\n\u001b[31m#install-banner .ib-icon{font-size:30px;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-text{flex:1}\u001b[39m\n\u001b[31m#install-banner .ib-title{font-size:14px;font-weight:700;color:var(--text)}\u001b[39m\n\u001b[31m#install-banner .ib-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#install-banner .ib-btn{background:var(--accent);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-close{background:none;border:none;color:var(--muted);font-size:20px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── UPDATE BANNER ── */\u001b[39m\n\u001b[31m#update-banner{display:none;position:fixed;top:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:#1a2a1a;border-bottom:2px solid var(--success);\u001b[39m\n\u001b[31m  padding:12px 16px;align-items:center;gap:12px}\u001b[39m\n\u001b[31m#update-banner.show{display:flex}\u001b[39m\n\u001b[31m#update-banner .ub-text{flex:1;font-size:14px;font-weight:600;color:var(--text)}\u001b[39m\n\u001b[31m#update-banner .ub-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#update-banner .ub-btn{background:var(--success);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:8px 14px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#update-banner .ub-close{background:none;border:none;color:var(--muted);font-size:18px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#222;color:#fff;padding:10px 20px;border-radius:20px;font-size:14px;font-weight:600;\u001b[39m\n\u001b[31m  opacity:0;transition:.3s;pointer-events:none;white-space:nowrap;z-index:500}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  padding:40px;color:var(--muted);font-size:14px;gap:12px}\u001b[39m\n\u001b[31m.spinner{width:36px;height:36px;border:3px solid var(--border);border-top-color:var(--accent);\u001b[39m\n\u001b[31m  border-radius:50%;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* ── CARD (jobs / campaign screens) ── */\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius)}\u001b[39m\n\u001b[31m.card.tappable:active{border-color:var(--accent);opacity:.85}·\u001b[39m\n\u001b[31m/* ── STATUS CHIPS (tour / campaign) ── */\u001b[39m\n\u001b[31m.status-chip{font-size:11px;padding:4px 11px;border-radius:8px;cursor:pointer;\u001b[39m\n\u001b[31m  background:var(--card);color:var(--text);border:1px solid var(--border);font-weight:600;\u001b[39m\n\u001b[31m  font-family:var(--font);transition:.15s}\u001b[39m\n\u001b[31m.status-chip.sel{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.status-chip:active{opacity:.8}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SPLASH\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-splash\\\" class=\\\"screen active\\\">\u001b[39m\n\u001b[31m  <div class=\\\"splash-logo\\\">360LM</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-sub\\\">Operations Hub</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-dot\\\" style=\\\"margin-top:16px\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     LOGIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-login\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-wrap\\\">\u001b[39m\n\u001b[31m    <div class=\\\"login-logo\\\">360LM</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">Operations Hub</div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Select Your Name</label>\u001b[39m\n\u001b[31m      <select id=\\\"login-name\\\"><option value=\\\"\\\">— Choose employee —</option><option value=\\\"amit\\\">Amit</option><option value=\\\"ashok\\\">Ashok</option><option value=\\\"gurpreet\\\">Gurpreet</option><option value=\\\"harish\\\">Harish Kumar Lal</option><option value=\\\"mukesh\\\">Mukesh</option><option value=\\\"narender\\\">Narender</option><option value=\\\"pramod\\\">Pramod Narang</option><option value=\\\"rakesh\\\">Rakesh</option><option value=\\\"sachin\\\">Sachin</option><option value=\\\"shambhu\\\">Shambhu</option><option value=\\\"umashankar\\\">Umashankar</option></select>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m      <label style=\\\"text-align:center\\\">Enter PIN</label>\u001b[39m\n\u001b[31m      <div class=\\\"pin-display\\\" id=\\\"pin-display\\\">\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd0\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd1\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd2\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd3\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"login-err\\\" id=\\\"login-err\\\"></div>\u001b[39m\n\u001b[31m      <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-1\\\" data-k=\\\"1\\\" aria-label=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-2\\\" data-k=\\\"2\\\" aria-label=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-3\\\" data-k=\\\"3\\\" aria-label=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-4\\\" data-k=\\\"4\\\" aria-label=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-5\\\" data-k=\\\"5\\\" aria-label=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-6\\\" data-k=\\\"6\\\" aria-label=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-7\\\" data-k=\\\"7\\\" aria-label=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-8\\\" data-k=\\\"8\\\" aria-label=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-9\\\" data-k=\\\"9\\\" aria-label=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key clr\\\" id=\\\"pk-C\\\" data-k=\\\"C\\\" aria-label=\\\"Clear\\\">CLR</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-0\\\" data-k=\\\"0\\\" aria-label=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key back\\\" id=\\\"pk-B\\\" data-k=\\\"B\\\" aria-label=\\\"Backspace\\\">⌫</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     HOME  (built dynamically)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\" id=\\\"home-header\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"home-body\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SHARE PICKER\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-share\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>Share Image To</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">Choose where to send this photo</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"share-prompt\\\">Which tool should receive this image?</div>\u001b[39m\n\u001b[31m    <div class=\\\"share-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-expense\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🧾</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Expenses</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-recce\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">📍</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Recce</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-installation\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🔧</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Installation</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     TEAM PINs  (admin only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-team\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-back\\\" style=\\\"font-size:20px\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Team PINs</h1><div class=\\\"sub\\\">Set employee login PINs</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-refresh\\\" title=\\\"Refresh\\\">🔄</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"info-note\\\">PINs are stored encrypted — current PIN cannot be viewed. You can set a new PIN for any employee below.</div>\u001b[39m\n\u001b[31m    <div id=\\\"team-list\\\"></div>·\u001b[39m\n\u001b[31m    <!-- Icon Admin — Harish & Pramod only -->\u001b[39m\n\u001b[31m    <div id=\\\"icon-admin-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:8px\\\">🎨 PWA Icons (Admin)</div>\u001b[39m\n\u001b[31m      <div class=\\\"info-note\\\" style=\\\"margin-bottom:10px\\\">Upload a new icon for any PWA. Upload both 192×192 and 512×512 sizes. PNG/JPG/WEBP accepted — auto-resized on server.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;flex-direction:column;gap:10px\\\">·\u001b[39m\n\u001b[31m        <!-- Secret management -->\u001b[39m\n\u001b[31m        <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:12px;padding:14px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px\\\">🔑 Admin Secret</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:center;gap:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"font-family:monospace;font-size:13px;color:var(--accent);background:var(--bg);\u001b[39m\n\u001b[31m              border-radius:6px;padding:8px 12px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\" id=\\\"icon-secret-display\\\">—</div>\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"toggleSecretVisible()\\\" id=\\\"btn-secret-show\\\">Show</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:10px\\\">Change secret (min 16 chars):</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m            <input id=\\\"icon-new-secret\\\" type=\\\"text\\\" placeholder=\\\"Enter new secret…\\\" style=\\\"flex:1;background:var(--bg);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m              padding:10px 12px;border-radius:8px;font-size:13px;font-family:monospace;outline:none\\\">\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"changeIconSecret()\\\" style=\\\"white-space:nowrap\\\">Update</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div id=\\\"icon-secret-err\\\" style=\\\"font-size:11px;color:var(--danger);min-height:14px;margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>·\u001b[39m\n\u001b[31m        <!-- Upload form -->\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Select PWA</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-pwa-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"hub\\\">Hub</option>\u001b[39m\n\u001b[31m            <option value=\\\"expense\\\">Expenses</option>\u001b[39m\n\u001b[31m            <option value=\\\"finance\\\">Finance</option>\u001b[39m\n\u001b[31m            <option value=\\\"installation\\\">Installation</option>\u001b[39m\n\u001b[31m            <option value=\\\"recce\\\">Recce</option>\u001b[39m\n\u001b[31m            <option value=\\\"dispatch\\\">Dispatch</option>\u001b[39m\n\u001b[31m            <option value=\\\"production\\\">Production</option>\u001b[39m\n\u001b[31m            <option value=\\\"vehicle\\\">Vehicle Log</option>\u001b[39m\n\u001b[31m            <option value=\\\"vrs\\\">Vehicle Request</option>\u001b[39m\n\u001b[31m            <option value=\\\"sales\\\">Sales</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Icon Size</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-size-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"192\\\">192 × 192 (standard)</option>\u001b[39m\n\u001b[31m            <option value=\\\"512\\\">512 × 512 (large)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Choose Image File</label>\u001b[39m\n\u001b[31m          <input type=\\\"file\\\" id=\\\"icon-file-input\\\" accept=\\\"image/png,image/jpeg,image/webp\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:14px;width:100%\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-preview-wrap\\\" style=\\\"display:none;text-align:center\\\">\u001b[39m\n\u001b[31m          <img id=\\\"icon-preview-img\\\" style=\\\"width:96px;height:96px;border-radius:16px;border:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-upload-err\\\" style=\\\"font-size:12px;color:var(--danger);min-height:16px\\\"></div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" onclick=\\\"uploadIcon()\\\">Upload Icon</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     MODAL: Admin set PIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-setpin\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🔑 Set PIN — <span id=\\\"setpin-target-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>New PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Enter new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-confirm-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Confirm New PIN</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-confirm\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Repeat new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-auth-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Your own current PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"setpin-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-setpin-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-setpin-save\\\">Set PIN</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     JOBS — Campaign list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-jobs\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Jobs</h1><div class=\\\"sub\\\">Campaigns &amp; Tours</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh jobs\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-campaign\\\" title=\\\"New Campaign\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"jobs-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     CAMPAIGN DETAIL — Tour list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-campaign\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 id=\\\"campaign-hdr-title\\\">Campaign</h1><div class=\\\"sub\\\" id=\\\"campaign-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh campaign\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-tour\\\" title=\\\"New Tour\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"campaign-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Campaign -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-campaign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">📋 New Campaign</div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Year</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-year\\\" min=\\\"2024\\\" max=\\\"2030\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Phase #</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-phase\\\" value=\\\"1\\\" min=\\\"1\\\" max=\\\"99\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Brand <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-brand\\\" placeholder=\\\"e.g. Lenovo\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Client <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-client\\\" placeholder=\\\"e.g. Lenovo India\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Campaign Name <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-campaign\\\" placeholder=\\\"e.g. Chandigarh BTL\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Generated Campaign ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nc-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nc-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nc-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nc-save\\\">Create</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Tour -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-tour\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px;max-height:88vh;overflow-y:auto\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🗺️ New Tour</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Tour Title</div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nt-title\\\" placeholder=\\\"e.g. Chandigarh Week 1\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Start Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-start\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">End Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-end\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:6px\\\">Assign Employees <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-employees\\\" style=\\\"display:flex;flex-direction:column;gap:6px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"color:var(--muted);font-size:13px\\\">Loading…</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Notes</div>\u001b[39m\n\u001b[31m      <textarea id=\\\"nt-notes\\\" rows=\\\"2\\\" placeholder=\\\"Optional instructions or remarks\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px;font-family:inherit;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Tour ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-id-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nt-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nt-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nt-save\\\">Create Tour</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- UPDATE BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"update-banner\\\">\u001b[39m\n\u001b[31m  <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ub-text\\\">App update available</div>\u001b[39m\n\u001b[31m    <div class=\\\"ub-sub\\\">Tap to refresh and get the latest version</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ub-btn\\\" id=\\\"btn-update\\\">Update now</button>\u001b[39m\n\u001b[31m  <button class=\\\"ub-close\\\" id=\\\"btn-update-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- INSTALL BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"install-banner\\\">\u001b[39m\n\u001b[31m  <div class=\\\"ib-icon\\\">📲</div>\u001b[39m\n\u001b[31m  <div class=\\\"ib-text\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ib-title\\\">Install 360LM Hub</div>\u001b[39m\n\u001b[31m    <div class=\\\"ib-sub\\\">Add to home screen for the best experience</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ib-btn\\\" id=\\\"btn-install\\\">Install</button>\u001b[39m\n\u001b[31m  <button class=\\\"ib-close\\\" id=\\\"btn-install-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: AI Config (mgmt only) -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-ai\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🤖 AI Config — Screenshot Extraction</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Tesseract OCR</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-tesseract\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Extraction Fallback</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-enabled\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Model</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-model\\\">\u001b[39m\n\u001b[31m        <option value=\\\"haiku\\\">Haiku (fast, cost-effective)</option>\u001b[39m\n\u001b[31m        <option value=\\\"sonnet\\\">Sonnet (higher accuracy)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-ai-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-ai-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"border-top:1px solid var(--border);padding-top:12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">API Key Health</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px;margin-bottom:10px\\\">Live check — result is always the same for all users</div>\u001b[39m\n\u001b[31m      <div id=\\\"api-key-status-wrap\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-check-keys\\\" style=\\\"margin-top:10px;width:100%\\\">Check API Keys</button>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:14px;border-top:1px solid var(--border);padding-top:12px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">View Usage &amp; Billing</div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Anthropic</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/usage\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Google Maps</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/google/maps-apis/api-list\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: Hub Quick-Action Config -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-qa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">⚡ Quick Actions</div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-config-list\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-cfg-err\\\" style=\\\"color:var(--danger);font-size:13px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-qa-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-qa-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SCRIPT\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31m/* ── CONFIG ── */\u001b[39m\n\u001b[31mconst CFG = {\u001b[39m\n\u001b[31m  api:        '/db',\u001b[39m\n\u001b[31m  apiHeaders: { 'Content-Type': 'application/json', 'Accept-Profile': 'expense' },\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31m/* ── SERVER ACCESS MAP (fetched from hub-access.json, overrides ACCESS for non-mgmt) ── */\u001b[39m\n\u001b[31mlet _serverAccess = {};\u001b[39m\n\u001b[31masync function loadHubAccess() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/hub/hub-access.json?t=' + Date.now());\u001b[39m\n\u001b[31m    if (r.ok) _serverAccess = await r.json();\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── ROLE ACCESS MAP (hardcoded fallback) ── */\u001b[39m\n\u001b[31mconst ACCESS = {\u001b[39m\n\u001b[31m  harish:     ['expense','installation','finance','recce','dispatch','production','jobs','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  pramod:     ['expense','installation','finance','recce','dispatch','production','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  rakesh:     ['installation','expense','production','jobs','vehicle','vrs','sales','hr'],\u001b[39m\n\u001b[31m  ashok:      ['expense','production','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  shambhu:    ['finance','expense','vehicle','vrs','stores','activity','hr'],\u001b[39m\n\u001b[31m  gurpreet:   ['expense','dispatch','vehicle','vrs','stores','hr'],\u001b[39m\n\u001b[31m  sachin:     ['installation','expense','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  mukesh:     ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  umashankar: ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  narender:   ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  amit:       ['vehicle','vrs','hr'],\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst TILES = [\u001b[39m\n\u001b[31m  { id:'expense',      label:'Expenses',     icon:'🧾', url:'/expense/',               live:true  },\u001b[39m\n\u001b[31m  { id:'installation', label:'Installation', icon:'🔧', url:'/installation/current/',  live:true  },\u001b[39m\n\u001b[31m  { id:'finance',      label:'Finance',      icon:'💰', url:'/finance/',               live:true  },\u001b[39m\n\u001b[31m  { id:'recce',        label:'Recce',        icon:'📍', url:'/recce/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'dispatch',     label:'Dispatch',     icon:'🚚', url:'/dispatch/',              live:true  },\u001b[39m\n\u001b[31m  { id:'production',   label:'Production',   icon:'⚙️', url:'/production/',            live:true  },\u001b[39m\n\u001b[31m  { id:'jobs',         label:'Jobs',         icon:'📋', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'vehicle',      label:'Vehicle Log',  icon:'🚐', url:'/vehicle/',               live:true  },\u001b[39m\n\u001b[31m  { id:'vrs',          label:'Veh. Request', icon:'📋', url:'/vrs/',                   live:true  },\u001b[39m\n\u001b[31m  { id:'sales',        label:'Sales',        icon:'💼', url:'/sales/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'stores',       label:'Stores',       icon:'📦', url:'/stores/',                live:true  },\u001b[39m\n\u001b[31m  { id:'client',       label:'Client',       icon:'👤', url:'/client/',                live:true  },\u001b[39m\n\u001b[31m  { id:'team-pins',    label:'Team PINs',    icon:'🔑', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'admin',        label:'Admin',        icon:'🛠️', url:'/admin/',                  live:true  },\u001b[39m\n\u001b[31m  { id:'activity',     label:'Activity',     icon:'🎯', url:'/activity/',               live:true  },\u001b[39m\n\u001b[31m  { id:'hr',           label:'HR',           icon:'👥', url:'/hr/',                     live:true  },\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31mconst MGMT = ['harish', 'pramod'];·\u001b[39m\n\u001b[31m/* ── STATE ── */\u001b[39m\n\u001b[31mlet currentUser     = null;\u001b[39m\n\u001b[31mlet pinBuffer       = '';\u001b[39m\n\u001b[31mlet setPinTarget    = null;\u001b[39m\n\u001b[31mlet pendingShare    = false;\u001b[39m\n\u001b[31mlet _loginAttempts  = 0;\u001b[39m\n\u001b[31mlet _lockoutUntil   = 0;\u001b[39m\n\u001b[31mlet _loginInFlight  = false;·\u001b[39m\n\u001b[31m/* ── UTILS ── */\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => { s.classList.remove('active'); s.style.display = ''; });\u001b[39m\n\u001b[31m  document.getElementById('screen-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet toastTimer;\u001b[39m\n\u001b[31mfunction toast(msg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.classList.add('show');\u001b[39m\n\u001b[31m  clearTimeout(toastTimer);\u001b[39m\n\u001b[31m  toastTimer = setTimeout(() => el.classList.remove('show'), 2800);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fmtINR(n) {\u001b[39m\n\u001b[31m  if (n >= 100000) return '₹' + (n / 100000).toFixed(1) + 'L';\u001b[39m\n\u001b[31m  if (n >= 1000)   return '₹' + (n / 1000).toFixed(1) + 'K';\u001b[39m\n\u001b[31m  return '₹' + Math.round(n);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SESSION ── */\u001b[39m\n\u001b[31mconst SESSION_KEY  = 'lm360-session';\u001b[39m\n\u001b[31mconst EXP_SES_KEY  = '360exp-session';\u001b[39m\n\u001b[31mconst SESSION_TTL  = 12 * 3600 * 1000;·\u001b[39m\n\u001b[31mfunction saveSession(user) {\u001b[39m\n\u001b[31m  const payload = { empId: user.id, name: user.name, role: user.role, loginAt: new Date().toISOString(), source: 'hub' };\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m  localStorage.setItem(EXP_SES_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearSession() {\u001b[39m\n\u001b[31m  localStorage.removeItem(SESSION_KEY);\u001b[39m\n\u001b[31m  localStorage.removeItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const raw = localStorage.getItem(SESSION_KEY) || localStorage.getItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m    if (!raw) return null;\u001b[39m\n\u001b[31m    const s = JSON.parse(raw);\u001b[39m\n\u001b[31m    if (!s.empId || !s.name || !s.role) return null;\u001b[39m\n\u001b[31m    if (Date.now() - new Date(s.loginAt).getTime() > SESSION_TTL) { clearSession(); return null; }\u001b[39m\n\u001b[31m    return s;\u001b[39m\n\u001b[31m  } catch (_) { return null; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGIN FLOW ── */\u001b[39m\n\u001b[31masync function initLogin() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('login-name');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— Choose employee —</option>'; // clear before repopulating\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    emps.forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    [\u001b[39m\n\u001b[31m      { id:'amit',        name:'Amit'             },\u001b[39m\n\u001b[31m      { id:'ashok',       name:'Ashok'            },\u001b[39m\n\u001b[31m      { id:'gurpreet',    name:'Gurpreet'         },\u001b[39m\n\u001b[31m      { id:'harish',      name:'Harish Kumar Lal' },\u001b[39m\n\u001b[31m      { id:'mukesh',      name:'Mukesh'           },\u001b[39m\n\u001b[31m      { id:'narender',    name:'Narender'         },\u001b[39m\n\u001b[31m      { id:'pramod',      name:'Pramod Narang'    },\u001b[39m\n\u001b[31m      { id:'rakesh',      name:'Rakesh'           },\u001b[39m\n\u001b[31m      { id:'sachin',      name:'Sachin'           },\u001b[39m\n\u001b[31m      { id:'shambhu',     name:'Shambhu'          },\u001b[39m\n\u001b[31m      { id:'umashankar',  name:'Umashankar'       },\u001b[39m\n\u001b[31m    ].forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  document.querySelectorAll('.pin-key').forEach(btn =>\u001b[39m\n\u001b[31m    btn.addEventListener('click', () => handlePinKey(btn.dataset.k))\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handlePinKey(k) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (_loginInFlight) return;\u001b[39m\n\u001b[31m  // Lockout guard\u001b[39m\n\u001b[31m  if (_lockoutUntil && Date.now() < _lockoutUntil) {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    errEl.textContent = `Too many attempts — wait ${secs}s`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (_lockoutUntil) { _lockoutUntil = 0; _loginAttempts = 0; }\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (k === 'C') { pinBuffer = ''; }\u001b[39m\n\u001b[31m  else if (k === 'B') { pinBuffer = pinBuffer.slice(0, -1); }\u001b[39m\n\u001b[31m  else {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (!empId) { toast('Select your name first'); return; }\u001b[39m\n\u001b[31m    if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  if (pinBuffer.length === 4) {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (empId) attemptLogin(empId, pinBuffer);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updatePinDisplay() {\u001b[39m\n\u001b[31m  for (let i = 0; i < 4; i++)\u001b[39m\n\u001b[31m    document.getElementById('pd' + i).classList.toggle('filled', i < pinBuffer.length);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _shakePin() {\u001b[39m\n\u001b[31m  const pd = document.getElementById('pin-display');\u001b[39m\n\u001b[31m  pd.classList.add('shake');\u001b[39m\n\u001b[31m  setTimeout(() => pd.classList.remove('shake'), 400);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _startLockout() {\u001b[39m\n\u001b[31m  _lockoutUntil = Date.now() + 30000;\u001b[39m\n\u001b[31m  _loginAttempts = 0;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  const tick = () => {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    if (secs > 0) { errEl.textContent = `Too many attempts — wait ${secs}s`; setTimeout(tick, 500); }\u001b[39m\n\u001b[31m    else { _lockoutUntil = 0; errEl.textContent = ''; }\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  tick();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function attemptLogin(empId, pin) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  _loginInFlight = true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pin }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      _loginAttempts++;\u001b[39m\n\u001b[31m      _shakePin();\u001b[39m\n\u001b[31m      if (_loginAttempts >= 5) { _startLockout(); return; }\u001b[39m\n\u001b[31m      errEl.textContent = `Wrong PIN — ${5 - _loginAttempts} attempt${5 - _loginAttempts !== 1 ? 's' : ''} left`;\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    currentUser = { id: user.id, name: user.name, role: user.role };\u001b[39m\n\u001b[31m    saveSession(currentUser);\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — check connection';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    _loginInFlight = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── HOME SCREEN ── */\u001b[39m\n\u001b[31masync function buildHome() {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const body   = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  // Management users always use hardcoded ACCESS; others prefer server-side hub-access.json\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const tiles  = accessIds.map(id => TILES.find(t => t.id === id)).filter(Boolean);·\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"avatar\\\">${initials}</div>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1 style=\\\"font-size:16px\\\">${currentUser.name}</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">${currentUser.role}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${isMgmt ? '<button class=\\\"btn-icon\\\" id=\\\"btn-hub-ai\\\" title=\\\"AI Config\\\" style=\\\"font-size:18px\\\">🤖</button>' : ''}\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m  if (isMgmt) document.getElementById('btn-hub-ai').addEventListener('click', openHubAiConfig);·\u001b[39m\n\u001b[31m  if (isMgmt) {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"hub-main-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-dash\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">📊</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">Dashboard</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-tools\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">🧰</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">All Tools</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-header\\\">\u001b[39m\n\u001b[31m        <div class=\\\"section-title\\\" style=\\\"margin:0\\\">Quick Actions</div>\u001b[39m\n\u001b[31m        <button class=\\\"btn-icon\\\" id=\\\"btn-hub-qa-cfg\\\" title=\\\"Configure Quick Actions\\\" style=\\\"font-size:18px;padding:8px;min-width:44px;min-height:44px\\\">⚙️</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-grid\\\" id=\\\"hub-qa-grid\\\"></div>`;\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-dash').addEventListener('click',  () => enterHubDash(tiles));\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-tools').addEventListener('click', () => enterHubTools(tiles));\u001b[39m\n\u001b[31m    document.getElementById('btn-hub-qa-cfg').addEventListener('click', openHubQaConfig);\u001b[39m\n\u001b[31m    renderHubQA();\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"tile-grid field\\\" id=\\\"tile-grid\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"text-align:center;font-size:12px;color:var(--muted);padding-top:8px\\\">Tap a tile to open your tool</div>`;\u001b[39m\n\u001b[31m    renderTiles(tiles, 'field');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderTiles(tiles, mode) {\u001b[39m\n\u001b[31m  const grid = document.getElementById('tile-grid');\u001b[39m\n\u001b[31m  tiles.forEach(tile => {\u001b[39m\n\u001b[31m    const div = document.createElement('div');\u001b[39m\n\u001b[31m    div.className = 'tile ' + mode + (tile.live ? '' : ' coming');\u001b[39m\n\u001b[31m    div.innerHTML = `<div class=\\\"tile-icon\\\">${tile.icon}</div><div class=\\\"tile-label\\\">${tile.label}</div>` +\u001b[39m\n\u001b[31m      (tile.live ? '' : '<div class=\\\"soon-badge\\\">Soon</div>');\u001b[39m\n\u001b[31m    div.addEventListener('click', () => handleTile(tile));\u001b[39m\n\u001b[31m    grid.appendChild(div);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handleTile(tile) {\u001b[39m\n\u001b[31m  if (!tile.live) { toast('Coming soon'); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'team-pins') { enterTeamPins(); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'jobs')      { enterJobs();     return; }\u001b[39m\n\u001b[31m  window.location.href = tile.url;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT SUB-VIEWS ── */\u001b[39m\n\u001b[31mfunction _mgmtHeader(title) {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-subview-back\\\" style=\\\"font-size:20px;font-weight:900\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 style=\\\"font-size:16px\\\">${title}</h1></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-subview-back').addEventListener('click', () => buildHome());\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubDash(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('Dashboard');\u001b[39m\n\u001b[31m  const hdr = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const refreshBtn = document.createElement('button');\u001b[39m\n\u001b[31m  refreshBtn.className = 'btn-icon';\u001b[39m\n\u001b[31m  refreshBtn.title = 'Refresh dashboard';\u001b[39m\n\u001b[31m  refreshBtn.setAttribute('aria-label', 'Refresh dashboard');\u001b[39m\n\u001b[31m  refreshBtn.style.cssText = 'font-size:18px';\u001b[39m\n\u001b[31m  refreshBtn.textContent = '🔄';\u001b[39m\n\u001b[31m  refreshBtn.addEventListener('click', loadKPIs);\u001b[39m\n\u001b[31m  hdr.insertBefore(refreshBtn, hdr.querySelector('#btn-logout'));\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Actions Required</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-actions\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Operations</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row kpi-4col\\\" id=\\\"kpi-ops\\\">${skel}${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Production</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-prod\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Installation</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-inst\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">HR</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-hr\\\">${skel}${skel}${skel}</div>`;\u001b[39m\n\u001b[31m  loadKPIs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubTools(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('All Tools');\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  body.innerHTML = `<div class=\\\"tile-grid mgmt\\\" id=\\\"tile-grid\\\"></div>`;\u001b[39m\n\u001b[31m  renderTiles(tiles, 'mgmt');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT AI CONFIG ── */\u001b[39m\n\u001b[31mconst HUB_FIN_HDR = { 'Content-Type': 'application/json', 'Accept-Profile': 'finance' };·\u001b[39m\n\u001b[31masync function openHubAiConfig() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r    = await fetch(`${CFG.api}/upi_config`, { headers: HUB_FIN_HDR });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const get  = key => rows.find(x => x.key === key)?.value;\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-tesseract').value = get('tesseract_enabled') || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-enabled').value   = get('ai_enabled')        || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-model').value     = get('ai_model')           || 'haiku';\u001b[39m\n\u001b[31m  } catch(_) { toast('Could not load AI config'); return; }\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-ai').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveHubAiConfig() {\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-hub-ai-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  const pairs = [\u001b[39m\n\u001b[31m    { key:'tesseract_enabled', value: document.getElementById('hub-ai-tesseract').value },\u001b[39m\n\u001b[31m    { key:'ai_enabled',        value: document.getElementById('hub-ai-enabled').value },\u001b[39m\n\u001b[31m    { key:'ai_model',          value: document.getElementById('hub-ai-model').value },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const results = await Promise.all(pairs.map(p =>\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/upi_config?key=eq.${p.key}`, {\u001b[39m\n\u001b[31m        method: 'PATCH',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'finance', 'Prefer': 'return=minimal' },\u001b[39m\n\u001b[31m        body: JSON.stringify({ value: p.value }),\u001b[39m\n\u001b[31m      })\u001b[39m\n\u001b[31m    ));\u001b[39m\n\u001b[31m    if (results.some(r => !r.ok)) throw new Error('partial failure');\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show');\u001b[39m\n\u001b[31m    toast('✅ AI config saved');\u001b[39m\n\u001b[31m  } catch(_) { toast('Save failed'); }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Save'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function checkApiKeyStatus() {\u001b[39m\n\u001b[31m  const btn  = document.getElementById('btn-check-keys');\u001b[39m\n\u001b[31m  const wrap = document.getElementById('api-key-status-wrap');\u001b[39m\n\u001b[31m  btn.textContent = 'Checking…';\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  wrap.innerHTML = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/tour-ai/key-status');\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Proxy error ' + r.status);\u001b[39m\n\u001b[31m    const d = await r.json();\u001b[39m\n\u001b[31m    function mkBadge(info, label, link) {\u001b[39m\n\u001b[31m      const col  = info.ok ? 'var(--success)' : 'var(--danger)';\u001b[39m\n\u001b[31m      const icon = info.ok ? '✅' : '❌';\u001b[39m\n\u001b[31m      const note = info.ok ? (info.model || 'Valid') : (info.error || 'Error');\u001b[39m\n\u001b[31m      const hint = info.hint ? ' · key ' + info.hint : '';\u001b[39m\n\u001b[31m      return `<div style=\\\"background:var(--card);border:1px solid var(--border);border-left:3px solid ${col};border-radius:8px;padding:10px 12px;display:flex;align-items:center;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:13px;font-weight:600;color:var(--text)\\\">${icon} ${label}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${note}${hint}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <a href=\\\"${link}\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:11px;color:var(--info);text-decoration:none;white-space:nowrap\\\">Billing ↗</a>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    wrap.innerHTML =\u001b[39m\n\u001b[31m      mkBadge(d.anthropic, 'Anthropic API',    'https://console.anthropic.com/settings/billing') +\u001b[39m\n\u001b[31m      mkBadge(d.maps,      'Google Maps API',  'https://console.cloud.google.com/billing');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    wrap.innerHTML = `<div style=\\\"font-size:12px;color:var(--danger)\\\">Error: ${e.message}</div>`;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.textContent = 'Check Again';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT QUICK ACTIONS ── */\u001b[39m\n\u001b[31mconst HUB_QA_DEFS = [\u001b[39m\n\u001b[31m  { id:'offer',       icon:'📋', label:'New Offer',    accessId:'sales',        fn:() => { window.location.href='/sales/'; } },\u001b[39m\n\u001b[31m  { id:'paytm',       icon:'📱', label:'UPI Pay',       accessId:'finance',      fn:() => { window.location.href='/finance/?qa=upi'; } },\u001b[39m\n\u001b[31m  { id:'expense',     icon:'🧾', label:'Expenses',      accessId:'expense',      fn:() => { window.location.href='/expense/'; } },\u001b[39m\n\u001b[31m  { id:'finance',     icon:'💰', label:'Finance',       accessId:'finance',      fn:() => { window.location.href='/finance/'; } },\u001b[39m\n\u001b[31m  { id:'dispatch',    icon:'🚚', label:'Dispatch',      accessId:'dispatch',     fn:() => { window.location.href='/dispatch/'; } },\u001b[39m\n\u001b[31m  { id:'stores',      icon:'📦', label:'Stores',        accessId:'stores',       fn:() => { window.location.href='/stores/'; } },\u001b[39m\n\u001b[31m  { id:'recce',       icon:'📍', label:'Recce',         accessId:'recce',        fn:() => { window.location.href='/recce/'; } },\u001b[39m\n\u001b[31m  { id:'production',  icon:'⚙️', label:'Production',   accessId:'production',   fn:() => { window.location.href='/production/'; } },\u001b[39m\n\u001b[31m  { id:'tours',       icon:'🗺️', label:'Tour Plan',   accessId:'installation', fn:() => { window.location.href='/tour-planner/'; } },\u001b[39m\n\u001b[31m  { id:'installation',icon:'🔧', label:'Installation',  accessId:'installation', fn:() => { window.location.href='/installation/current/'; } },\u001b[39m\n\u001b[31m  { id:'activity',    icon:'🎯', label:'Activity',      accessId:'activity',     fn:() => { window.location.href='/activity/'; } },\u001b[39m\n\u001b[31m];\u001b[39m\n\u001b[31mconst HUB_QA_DEFAULT = ['offer', 'paytm'];\u001b[39m\n\u001b[31mconst HUB_QA_STORE   = '360lm-hub-qa';\u001b[39m\n\u001b[31mlet   _hubQaTempSel  = [];·\u001b[39m\n\u001b[31mfunction getHubQA() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(HUB_QA_STORE));\u001b[39m\n\u001b[31m    if (Array.isArray(s) && s.length) return s;\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m  return [...HUB_QA_DEFAULT];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHubQA() {\u001b[39m\n\u001b[31m  const ids  = getHubQA();\u001b[39m\n\u001b[31m  const grid = document.getElementById('hub-qa-grid');\u001b[39m\n\u001b[31m  if (!grid) return;\u001b[39m\n\u001b[31m  if (!ids.length) {\u001b[39m\n\u001b[31m    grid.innerHTML = `<div style=\\\"color:var(--muted);font-size:13px;grid-column:1/-1;\u001b[39m\n\u001b[31m      text-align:center;padding:16px\\\">Tap ⚙️ to add quick actions</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  grid.innerHTML = ids.map(id => {\u001b[39m\n\u001b[31m    const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!d) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-tile\\\" onclick=\\\"hubQaRun('${id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction hubQaRun(id) {\u001b[39m\n\u001b[31m  const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m  if (d) d.fn();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openHubQaConfig() {\u001b[39m\n\u001b[31m  _hubQaTempSel = [...getHubQA()];\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const visibleDefs = HUB_QA_DEFS.filter(d => accessIds.includes(d.accessId));\u001b[39m\n\u001b[31m  const list = document.getElementById('hub-qa-config-list');\u001b[39m\n\u001b[31m  list.innerHTML = visibleDefs.map(d => {\u001b[39m\n\u001b[31m    const sel = _hubQaTempSel.includes(d.id);\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-item${sel?' selected':''}\\\" id=\\\"hub-qa-item-${d.id}\\\"\u001b[39m\n\u001b[31m        onclick=\\\"toggleHubQaItem('${d.id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-toggle\\\"></div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleHubQaItem(id) {\u001b[39m\n\u001b[31m  const idx = _hubQaTempSel.indexOf(id);\u001b[39m\n\u001b[31m  if (idx >= 0) {\u001b[39m\n\u001b[31m    _hubQaTempSel.splice(idx, 1);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    if (_hubQaTempSel.length >= 6) {\u001b[39m\n\u001b[31m      document.getElementById('hub-qa-cfg-err').textContent = 'Maximum 6 quick actions';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _hubQaTempSel.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  const el = document.getElementById('hub-qa-item-' + id);\u001b[39m\n\u001b[31m  if (el) el.className = 'hub-qa-item' + (_hubQaTempSel.includes(id) ? ' selected' : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction saveHubQaConfig() {\u001b[39m\n\u001b[31m  localStorage.setItem(HUB_QA_STORE, JSON.stringify(_hubQaTempSel));\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.remove('show');\u001b[39m\n\u001b[31m  renderHubQA();\u001b[39m\n\u001b[31m  toast('✅ Quick actions updated');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── KPI FETCH ── */\u001b[39m\n\u001b[31mfunction _hdr(schema) {\u001b[39m\n\u001b[31m  return { 'Content-Type': 'application/json', 'Accept-Profile': schema };\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _safe(p) { return p.catch(() => null); }·\u001b[39m\n\u001b[31masync function loadKPIs() {\u001b[39m\n\u001b[31m  const mon = firstOfMonth();\u001b[39m\n\u001b[31m  const curMonth = new Date().toISOString().slice(0, 7); // YYYY-MM\u001b[39m\n\u001b[31m  const todayIST = (() => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; })();·\u001b[39m\n\u001b[31m  // Reset sections to loading skeletons before fetching (makes refresh visible)\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  [['kpi-actions',3],['kpi-ops',4],['kpi-prod',3],['kpi-inst',3],['kpi-hr',3]].forEach(([id,n]) => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    if (el) el.innerHTML = skel.repeat(n);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  const [\u001b[39m\n\u001b[31m    sheetsRes, claimsRes, salaryRes,\u001b[39m\n\u001b[31m    cashRes,   dispRes,   recceRes,  prodRes,\u001b[39m\n\u001b[31m    instJobsRes, instCtrsRes, storeItemsRes,\u001b[39m\n\u001b[31m    hrAttRes, hrEmpRes, hrConcernRes, hrSlipsRes\u001b[39m\n\u001b[31m  ] = await Promise.all([\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/sheets?status=in.(submitted,escalated)&select=sheet_id`, { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/approved_pending_reimbursement?select=sheet_id`,         { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_register?status=in.(pending,partial)&month=eq.${curMonth}&select=record_id`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/accounts?account_id=eq.petty-cash&select=current_balance`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/dispatches?status=eq.active&select=dispatch_id`,         { headers: _hdr('dispatch') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/submissions?submitted_at=gte.${mon}&select=sub_id`,      { headers: _hdr('recce') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/orders?status=not.in.(dispatched)&select=order_id,status`, { headers: _hdr('production') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/jobs?select=job_id,status`,                              { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/counters?select=counter_id,status`,                      { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/items?active=eq.true&select=id,current_stock,min_stock`, { headers: _hdr('stores') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance?date=eq.${todayIST}&status=eq.present&select=attendance_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/employee_list?select=id`,                               { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance_concerns?status=eq.open&select=concern_id`,  { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_slips?month=eq.${curMonth}&status=eq.finalized&select=slip_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m  ]);·\u001b[39m\n\u001b[31m  const sheets    = sheetsRes?.ok    ? await sheetsRes.json()    : [];\u001b[39m\n\u001b[31m  const claims    = claimsRes?.ok    ? await claimsRes.json()    : [];\u001b[39m\n\u001b[31m  const salary    = salaryRes?.ok    ? await salaryRes.json()    : [];\u001b[39m\n\u001b[31m  const cashArr   = cashRes?.ok      ? await cashRes.json()      : [];\u001b[39m\n\u001b[31m  const disps     = dispRes?.ok      ? await dispRes.json()      : [];\u001b[39m\n\u001b[31m  const recces    = recceRes?.ok     ? await recceRes.json()     : [];\u001b[39m\n\u001b[31m  const prods     = prodRes?.ok      ? await prodRes.json()      : [];\u001b[39m\n\u001b[31m  const instJobs  = instJobsRes?.ok  ? await instJobsRes.json()  : [];\u001b[39m\n\u001b[31m  const instCtrs  = instCtrsRes?.ok  ? await instCtrsRes.json()  : [];\u001b[39m\n\u001b[31m  const storeItems = storeItemsRes?.ok ? await storeItemsRes.json() : [];\u001b[39m\n\u001b[31m  const hrAtt     = hrAttRes?.ok     ? await hrAttRes.json()     : [];\u001b[39m\n\u001b[31m  const hrEmp     = hrEmpRes?.ok     ? await hrEmpRes.json()     : [];\u001b[39m\n\u001b[31m  const hrConcern = hrConcernRes?.ok ? await hrConcernRes.json() : [];\u001b[39m\n\u001b[31m  const hrSlips   = hrSlipsRes?.ok   ? await hrSlipsRes.json()   : [];·\u001b[39m\n\u001b[31m  const cash = Number(cashArr[0]?.current_balance || 0);·\u001b[39m\n\u001b[31m  const actionsEl = document.getElementById('kpi-actions');\u001b[39m\n\u001b[31m  const opsEl     = document.getElementById('kpi-ops');\u001b[39m\n\u001b[31m  const prodEl    = document.getElementById('kpi-prod');\u001b[39m\n\u001b[31m  const instEl    = document.getElementById('kpi-inst');\u001b[39m\n\u001b[31m  const hrEl      = document.getElementById('kpi-hr');\u001b[39m\n\u001b[31m  if (!actionsEl || !opsEl || !prodEl || !instEl) return;·\u001b[39m\n\u001b[31m  // ── Actions Required ────────────────────────────────────────────\u001b[39m\n\u001b[31m  actionsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${sheets.length ? 'alert' : 'ok'}\\\" data-href=\\\"/expense/?tab=approvals\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${sheets.length ? 'red' : 'green'}\\\">${sheets.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Expense Approvals</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${claims.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=claims\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${claims.length ? 'amber' : 'green'}\\\">${claims.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Claims to Reimburse</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${salary.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=salary\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${salary.length ? 'amber' : 'green'}\\\">${salary.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Salary Due This Month</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Operations ──────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const cashColor = cash < 2000 ? 'red' : cash < 5000 ? 'amber' : 'green';\u001b[39m\n\u001b[31m  const cashCard  = cash < 2000 ? 'alert' : cash < 5000 ? 'warn' : 'ok';\u001b[39m\n\u001b[31m  const lowStock = storeItems.filter(i => i.current_stock < i.min_stock).length;\u001b[39m\n\u001b[31m  opsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${cashCard}\\\" data-href=\\\"/finance/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${cashColor}\\\">${fmtINR(cash)}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">${cash < 0 ? '⚠️ ' : ''}Petty Cash</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/dispatch/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${disps.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Dispatches</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/recce/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${recces.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Recce This Month</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${lowStock > 0 ? 'alert' : 'ok'}\\\" data-href=\\\"/stores/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${lowStock > 0 ? 'red' : 'green'}\\\">${lowStock}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Low Stock Items</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Production ───────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const prodPending = prods.filter(o => o.status === 'pending').length;\u001b[39m\n\u001b[31m  const prodActive  = prods.filter(o => o.status === 'in_production' || o.status === 'quality_check').length;\u001b[39m\n\u001b[31m  const prodReady   = prods.filter(o => o.status === 'ready').length;\u001b[39m\n\u001b[31m  prodEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodPending ? 'warn' : 'ok'}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodPending ? 'amber' : 'green'}\\\">${prodPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Pending Orders</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${prodActive}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">In Production</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodReady ? 'ok' : ''}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodReady ? 'green' : ''}\\\">${prodReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Ready to Dispatch</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Installation ─────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const activeJobs  = instJobs.filter(j => j.status === 'active').length;\u001b[39m\n\u001b[31m  const ctrsPending = instCtrs.filter(c => c.status === 'recce_pending' || c.status === 'mockup_pending' || c.status === 'unclear').length;\u001b[39m\n\u001b[31m  const ctrsReady   = instCtrs.filter(c => c.status === 'ready').length;\u001b[39m\n\u001b[31m  instEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${activeJobs ? 'info' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${activeJobs ? 'blue' : 'green'}\\\">${activeJobs}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Jobs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsPending ? 'warn' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsPending ? 'amber' : 'green'}\\\">${ctrsPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Pending</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsReady ? 'ok' : ''}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsReady ? 'green' : ''}\\\">${ctrsReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Ready</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── HR ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31m  if (hrEl) {\u001b[39m\n\u001b[31m    const totalEmp   = hrEmp.length || 0;\u001b[39m\n\u001b[31m    const presentCnt = hrAtt.length;\u001b[39m\n\u001b[31m    const attPct     = totalEmp > 0 ? Math.round(presentCnt / totalEmp * 100) : 0;\u001b[39m\n\u001b[31m    const attColor   = attPct >= 80 ? 'green' : attPct >= 50 ? 'amber' : 'red';\u001b[39m\n\u001b[31m    const attCard    = attPct >= 80 ? 'ok' : attPct >= 50 ? 'warn' : 'alert';\u001b[39m\n\u001b[31m    const concernCnt = hrConcern.length;\u001b[39m\n\u001b[31m    const slipsDue   = hrSlips.length;\u001b[39m\n\u001b[31m    hrEl.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${attCard}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${attColor}\\\">${attPct}%</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Attendance Today</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${concernCnt ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${concernCnt ? 'amber' : 'green'}\\\">${concernCnt}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Open Concerns</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${slipsDue ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${slipsDue ? 'amber' : 'green'}\\\">${slipsDue}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Slips to Pay</div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Wire tap-to-navigate on all KPI cards\u001b[39m\n\u001b[31m  document.querySelectorAll('.kpi-card[data-href]').forEach(card => {\u001b[39m\n\u001b[31m    card.addEventListener('click', () => window.location.href = card.dataset.href);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction firstOfMonth() {\u001b[39m\n\u001b[31m  const d = new Date();\u001b[39m\n\u001b[31m  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGOUT ── */\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  pinBuffer   = '';\u001b[39m\n\u001b[31m  pendingShare = false;\u001b[39m\n\u001b[31m  _loginAttempts = 0; _lockoutUntil = 0; _loginInFlight = false;\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  document.getElementById('login-name').value = '';\u001b[39m\n\u001b[31m  document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m  showScreen('login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── TEAM PINs ── */\u001b[39m\n\u001b[31masync function enterTeamPins() {\u001b[39m\n\u001b[31m  showScreen('team');\u001b[39m\n\u001b[31m  await loadTeamPins();\u001b[39m\n\u001b[31m  // Show icon admin section for harish/pramod\u001b[39m\n\u001b[31m  const sec = document.getElementById('icon-admin-section');\u001b[39m\n\u001b[31m  if (sec) {\u001b[39m\n\u001b[31m    sec.style.display = MGMT.includes(currentUser.id) ? 'block' : 'none';\u001b[39m\n\u001b[31m    if (MGMT.includes(currentUser.id)) refreshSecretDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── ICON ADMIN ────────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst ICON_SECRET_KEY    = 'lm360_icon_secret';\u001b[39m\n\u001b[31mconst ICON_SECRET_INITIAL = 'nlr8Zsqw6XsD0rEXPQ_RfpYiOeNhH-9VglYKoIsElHE';·\u001b[39m\n\u001b[31mfunction getIconSecret() {\u001b[39m\n\u001b[31m  return localStorage.getItem(ICON_SECRET_KEY) || ICON_SECRET_INITIAL;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction setIconSecret(s) {\u001b[39m\n\u001b[31m  localStorage.setItem(ICON_SECRET_KEY, s);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet secretVisible = false;\u001b[39m\n\u001b[31mfunction refreshSecretDisplay() {\u001b[39m\n\u001b[31m  const el = document.getElementById('icon-secret-display');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  const s = getIconSecret();\u001b[39m\n\u001b[31m  el.textContent = secretVisible ? s : s.slice(0,4) + '••••••••••••••••' + s.slice(-4);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleSecretVisible() {\u001b[39m\n\u001b[31m  secretVisible = !secretVisible;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-secret-show');\u001b[39m\n\u001b[31m  if (btn) btn.textContent = secretVisible ? 'Hide' : 'Show';\u001b[39m\n\u001b[31m  refreshSecretDisplay();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function changeIconSecret() {\u001b[39m\n\u001b[31m  const newSecret = document.getElementById('icon-new-secret').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-secret-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (newSecret.length < 16) { errEl.textContent = 'Minimum 16 characters'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/change-secret', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret(), 'X-New-Secret': newSecret }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Failed');\u001b[39m\n\u001b[31m    setIconSecret(newSecret);\u001b[39m\n\u001b[31m    document.getElementById('icon-new-secret').value = '';\u001b[39m\n\u001b[31m    refreshSecretDisplay();\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = '✓ Secret updated';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mdocument.addEventListener('DOMContentLoaded', () => {\u001b[39m\n\u001b[31m  const fileInput = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  if (fileInput) {\u001b[39m\n\u001b[31m    fileInput.addEventListener('change', function() {\u001b[39m\n\u001b[31m      const wrap = document.getElementById('icon-preview-wrap');\u001b[39m\n\u001b[31m      const img  = document.getElementById('icon-preview-img');\u001b[39m\n\u001b[31m      if (this.files[0]) { img.src = URL.createObjectURL(this.files[0]); wrap.style.display = 'block'; }\u001b[39m\n\u001b[31m      else { wrap.style.display = 'none'; }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function uploadIcon() {\u001b[39m\n\u001b[31m  const pwa   = document.getElementById('icon-pwa-select').value;\u001b[39m\n\u001b[31m  const size  = document.getElementById('icon-size-select').value;\u001b[39m\n\u001b[31m  const input = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-upload-err');\u001b[39m\n\u001b[31m  errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (!input.files[0]) { errEl.textContent = 'Choose an image file first'; return; }\u001b[39m\n\u001b[31m  const formData = new FormData();\u001b[39m\n\u001b[31m  formData.append('pwa', pwa);\u001b[39m\n\u001b[31m  formData.append('size', size);\u001b[39m\n\u001b[31m  formData.append('icon', input.files[0]);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret() },\u001b[39m\n\u001b[31m      body: formData\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Upload failed');\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = `✓ Icon updated for ${pwa} (${size}px). Clear browser cache / reinstall to see new icon.`;\u001b[39m\n\u001b[31m    input.value = '';\u001b[39m\n\u001b[31m    document.getElementById('icon-preview-wrap').style.display = 'none';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadTeamPins() {\u001b[39m\n\u001b[31m  const list = document.getElementById('team-list');\u001b[39m\n\u001b[31m  list.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    list.innerHTML = '';\u001b[39m\n\u001b[31m    emps.forEach(emp => {\u001b[39m\n\u001b[31m      const row = document.createElement('div');\u001b[39m\n\u001b[31m      row.className = 'emp-row';\u001b[39m\n\u001b[31m      row.innerHTML = `\u001b[39m\n\u001b[31m        <div style=\\\"min-width:0;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:15px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.name}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.role}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-secondary btn-sm\\\" data-id=\\\"${emp.id}\\\" data-name=\\\"${emp.name}\\\">Set PIN</button>`;\u001b[39m\n\u001b[31m      row.querySelector('button').addEventListener('click', () => openSetPin(emp.id, emp.name));\u001b[39m\n\u001b[31m      list.appendChild(row);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    list.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openSetPin(targetId, targetName) {\u001b[39m\n\u001b[31m  setPinTarget = targetId;\u001b[39m\n\u001b[31m  document.getElementById('setpin-target-name').textContent = targetName;\u001b[39m\n\u001b[31m  ['setpin-new','setpin-confirm','setpin-auth'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-setpin').classList.add('show');\u001b[39m\n\u001b[31m  setTimeout(() => document.getElementById('setpin-new').focus(), 50);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function doSetPin() {\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('setpin-new').value.trim();\u001b[39m\n\u001b[31m  const conf    = document.getElementById('setpin-confirm').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('setpin-auth').value.trim();\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('setpin-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  if (!newPin || !conf || !authPin) { errEl.textContent = 'All fields are required'; return; }\u001b[39m\n\u001b[31m  if (newPin !== conf)              { errEl.textContent = 'New PINs do not match'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))     { errEl.textContent = 'PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-setpin-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_admin_id:  currentUser.id,\u001b[39m\n\u001b[31m        p_admin_pin: authPin,\u001b[39m\n\u001b[31m        p_target_id: setPinTarget,\u001b[39m\n\u001b[31m        p_new_pin:   newPin,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const err = await resp.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Failed to set PIN';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m    toast('✅ PIN updated for ' + document.getElementById('setpin-target-name').textContent);\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Set PIN';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ═══════════════════════════════════════════\u001b[39m\n\u001b[31m   JOBS — Campaign + Tour management\u001b[39m\n\u001b[31m   Access: harish, pramod (campaigns + tours)\u001b[39m\n\u001b[31m           rakesh (tours only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ */\u001b[39m\n\u001b[31mconst INST_RD = { 'Accept-Profile': 'installation' };\u001b[39m\n\u001b[31mconst INST_WR = { 'Content-Type':'application/json','Accept-Profile':'installation','Content-Profile':'installation' };·\u001b[39m\n\u001b[31mlet _currentCampaignId = null;\u001b[39m\n\u001b[31mlet _allEmployees      = [];\u001b[39m\n\u001b[31mlet _editTourId        = null;  // null = create mode, string = edit mode·\u001b[39m\n\u001b[31m// ── Helpers ──────────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _slugify(s) {\u001b[39m\n\u001b[31m  return s.toUpperCase().trim()\u001b[39m\n\u001b[31m    .replace(/[^A-Z0-9]+/g, '_')\u001b[39m\n\u001b[31m    .replace(/^_|_$/g, '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _buildCampaignId(year, brand, client, campaign, phase) {\u001b[39m\n\u001b[31m  const nn = String(phase || 1).padStart(2, '0');\u001b[39m\n\u001b[31m  return `${year}-${_slugify(brand)}-${_slugify(client)}-${_slugify(campaign)}-${nn}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _campStatusBadge(s) {\u001b[39m\n\u001b[31m  const map = { active:'ok', completed:'', on_hold:'warn' };\u001b[39m\n\u001b[31m  const col = { active:'green', completed:'', on_hold:'amber' };\u001b[39m\n\u001b[31m  return `<span class=\\\"kpi-val ${col[s]||''}\\\" style=\\\"font-size:12px;font-weight:700\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _tourStatusBadge(s) {\u001b[39m\n\u001b[31m  const colors = { active:'var(--success)', planned:'var(--info)', completed:'var(--muted)', cancelled:'var(--danger)' };\u001b[39m\n\u001b[31m  return `<span style=\\\"font-size:11px;font-weight:700;padding:2px 8px;border-radius:10px;\u001b[39m\n\u001b[31m    background:var(--card);color:${colors[s]||'var(--muted)'}\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _canCreateCampaign() { return ['harish','pramod'].includes(currentUser?.id); }\u001b[39m\n\u001b[31mfunction _canCreateTour()     { return ['harish','pramod','rakesh'].includes(currentUser?.id); }·\u001b[39m\n\u001b[31m// ── Jobs screen ───────────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterJobs() {\u001b[39m\n\u001b[31m  showScreen('jobs');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').style.display = _canCreateCampaign() ? '' : 'none';\u001b[39m\n\u001b[31m  await loadJobs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadJobs() {\u001b[39m\n\u001b[31m  const el = document.getElementById('jobs-body');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?order=created_at.desc`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?select=tour_id,campaign_id,status`, { headers: INST_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const campaigns = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours     = tResp.ok ? await tResp.json() : [];·\u001b[39m\n\u001b[31m    if (!campaigns.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty\\\"><div style=\\\"font-size:32px\\\">📋</div><div>No campaigns yet</div></div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    el.innerHTML = campaigns.map(c => {\u001b[39m\n\u001b[31m      const cTours  = tours.filter(t => t.campaign_id === c.campaign_id);\u001b[39m\n\u001b[31m      const active  = cTours.filter(t => t.status === 'active').length;\u001b[39m\n\u001b[31m      const total   = cTours.length;\u001b[39m\n\u001b[31m      const statusColors = { active:'var(--success)', on_hold:'var(--accent2)', completed:'var(--muted)' };\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card tappable\\\" style=\\\"padding:14px\\\" onclick=\\\"enterCampaign('${c.campaign_id}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:flex-start;gap:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:15px;font-weight:700\\\">${c.brand} — ${c.campaign_name}\u001b[39m\n\u001b[31m                ${c.phase > 1 ? `<span style=\\\"font-size:11px;color:var(--muted)\\\"> Phase ${c.phase}</span>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:2px\\\">${c.client} · ${c.year}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:4px\\\">\u001b[39m\n\u001b[31m                ${total} tour${total!==1?'s':''} · ${active} active\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;align-items:flex-end;gap:6px\\\">\u001b[39m\n\u001b[31m              <span style=\\\"font-size:11px;font-weight:700;color:${statusColors[c.status]||'var(--muted)'}\\\">\u001b[39m\n\u001b[31m                ${(c.status||'').replace('_',' ')}\u001b[39m\n\u001b[31m              </span>\u001b[39m\n\u001b[31m              <span style=\\\"font-size:18px;color:var(--muted)\\\">›</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-top:8px;word-break:break-all\\\">${c.campaign_id}</div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign detail ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterCampaign(campaignId) {\u001b[39m\n\u001b[31m  _currentCampaignId = campaignId;\u001b[39m\n\u001b[31m  showScreen('campaign');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').style.display = _canCreateTour() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-sub').textContent   = '';\u001b[39m\n\u001b[31m  await loadCampaign(campaignId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCampaign(campaignId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('campaign-body');\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp, empResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(campaignId)}&order=start_date`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const camps = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours = tResp.ok ? await tResp.json() : [];\u001b[39m\n\u001b[31m    _allEmployees = empResp.ok ? await empResp.json() : [];·\u001b[39m\n\u001b[31m    if (!camps.length) { body.innerHTML = '<div class=\\\"empty\\\">Campaign not found</div>'; return; }\u001b[39m\n\u001b[31m    const c = camps[0];·\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-title').textContent = `${c.brand} — ${c.campaign_name}`;\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-sub').textContent   = `${c.client} · ${c.year}${c.phase>1?' · Phase '+c.phase:''}`;·\u001b[39m\n\u001b[31m    const empMap = {};\u001b[39m\n\u001b[31m    _allEmployees.forEach(e => empMap[e.id] = e.name);·\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"padding:12px 14px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Campaign ID</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:10px;color:var(--muted);word-break:break-all;text-align:right;max-width:65%\\\">${c.campaign_id}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Client</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:13px;font-weight:600\\\">${c.client}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${_canCreateCampaign() ? `\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div class=\\\"section-title\\\" style=\\\"margin-bottom:6px\\\">Campaign Status</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m            ${['active','on_hold','completed'].map(s =>\u001b[39m\n\u001b[31m              `<button class=\\\"status-chip${c.status===s?' sel':''}\\\" onclick=\\\"updateCampaignStatus('${c.campaign_id}','${s}')\\\">\u001b[39m\n\u001b[31m                ${s.replace('_',' ')}\u001b[39m\n\u001b[31m              </button>`).join('')}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>` : ''}\u001b[39m\n\u001b[31m      </div>·\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:4px\\\">Tours (${tours.length})</div>\u001b[39m\n\u001b[31m      ${tours.length === 0 ? `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\">\u001b[39m\n\u001b[31m        <div>No tours yet</div>\u001b[39m\n\u001b[31m        ${_canCreateTour() ? '<div style=\\\"font-size:12px\\\">Tap ＋ to add the first tour</div>' : ''}\u001b[39m\n\u001b[31m      </div>` :\u001b[39m\n\u001b[31m      tours.map(t => {\u001b[39m\n\u001b[31m        const empNames = (t.assigned_employees || []).map(id => empMap[id] || id).join(', ') || 'No one assigned';\u001b[39m\n\u001b[31m        const dates    = t.start_date ? `${t.start_date} → ${t.end_date||'?'}` : 'Dates not set';\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m          <div class=\\\"card\\\" style=\\\"padding:14px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;align-items:flex-start;gap:8px\\\">\u001b[39m\n\u001b[31m              <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m                <div style=\\\"font-size:14px;font-weight:700\\\">${t.title || t.tour_id}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">📅 ${dates}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">👥 ${empNames}</div>\u001b[39m\n\u001b[31m                ${t.notes ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">📝 ${t.notes}</div>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              ${_tourStatusBadge(t.status)}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            ${_canCreateTour() ? `\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;gap:6px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m              ${['planned','active','completed'].map(s =>\u001b[39m\n\u001b[31m                `<button class=\\\"status-chip${t.status===s?' sel':''}\\\"\u001b[39m\n\u001b[31m                  onclick=\\\"updateTourStatus('${t.tour_id}','${s}')\\\">${s}</button>`).join('')}\u001b[39m\n\u001b[31m              <button class=\\\"status-chip\\\" onclick=\\\"openEditTour('${t.tour_id}')\\\"\u001b[39m\n\u001b[31m                style=\\\"margin-left:auto;background:var(--surface);border-color:#3a3a6e;color:#8888ff\\\">\u001b[39m\n\u001b[31m                ✏️ Edit\u001b[39m\n\u001b[31m              </button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m            <div style=\\\"font-size:10px;color:var(--muted);margin-top:6px\\\">${t.tour_id}</div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m      <div style=\\\"height:20px\\\"></div>`;\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateTourStatus(tourId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Tour marked ${status}`);\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateCampaignStatus(campaignId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Campaign marked ${status.replace('_',' ')}`);\u001b[39m\n\u001b[31m    loadCampaign(campaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Campaign modal ────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewCampaign() {\u001b[39m\n\u001b[31m  if (!_canCreateCampaign()) { toast('Only Harish or Pramod can create campaigns'); return; }\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('nc-year').value    = new Date().getFullYear();\u001b[39m\n\u001b[31m  document.getElementById('nc-phase').value   = '1';\u001b[39m\n\u001b[31m  document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  document.getElementById('nc-err').textContent     = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-new-campaign').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateCampaignPreview() {\u001b[39m\n\u001b[31m  const year  = document.getElementById('nc-year').value;\u001b[39m\n\u001b[31m  const brand = document.getElementById('nc-brand').value;\u001b[39m\n\u001b[31m  const clt   = document.getElementById('nc-client').value;\u001b[39m\n\u001b[31m  const camp  = document.getElementById('nc-campaign').value;\u001b[39m\n\u001b[31m  const phase = document.getElementById('nc-phase').value;\u001b[39m\n\u001b[31m  if (brand && clt && camp) {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = _buildCampaignId(year, brand, clt, camp, phase);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewCampaign() {\u001b[39m\n\u001b[31m  const year   = Number(document.getElementById('nc-year').value);\u001b[39m\n\u001b[31m  const brand  = document.getElementById('nc-brand').value.trim();\u001b[39m\n\u001b[31m  const client = document.getElementById('nc-client').value.trim();\u001b[39m\n\u001b[31m  const cname  = document.getElementById('nc-campaign').value.trim();\u001b[39m\n\u001b[31m  const phase  = Number(document.getElementById('nc-phase').value) || 1;\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('nc-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const brandEl = document.getElementById('nc-brand');\u001b[39m\n\u001b[31m  const clientEl = document.getElementById('nc-client');\u001b[39m\n\u001b[31m  const cnameEl = document.getElementById('nc-campaign');\u001b[39m\n\u001b[31m  [brandEl, clientEl, cnameEl].forEach(el => el.style.borderColor = '');\u001b[39m\n\u001b[31m  let _missingField = false;\u001b[39m\n\u001b[31m  if (!brand)  { brandEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!client) { clientEl.style.borderColor = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!cname)  { cnameEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (_missingField) { errEl.textContent = 'Brand, Client and Campaign Name are required'; return; }\u001b[39m\n\u001b[31m  const campaignId = _buildCampaignId(year, brand, client, cname, phase);·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nc-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/campaigns`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ campaign_id:campaignId, year, brand, client, campaign_name:cname, phase, created_by:currentUser.id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || 'Failed to create';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show');\u001b[39m\n\u001b[31m    toast(`✅ Campaign created`);\u001b[39m\n\u001b[31m    loadJobs();\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Create'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Tour modal ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _buildEmpCheckboxes(assigned = []) {\u001b[39m\n\u001b[31m  return _allEmployees.map(e =>\u001b[39m\n\u001b[31m    `<label style=\\\"display:flex;align-items:center;gap:10px;padding:8px 10px;background:var(--card);\u001b[39m\n\u001b[31m      border:1px solid var(--border);border-radius:8px;cursor:pointer\\\">\u001b[39m\n\u001b[31m      <input type=\\\"checkbox\\\" value=\\\"${e.id}\\\" ${assigned.includes(e.id)?'checked':''} style=\\\"width:16px;height:16px;accent-color:var(--accent)\\\">\u001b[39m\n\u001b[31m      <span style=\\\"font-size:14px;font-weight:600\\\">${e.name}</span>\u001b[39m\n\u001b[31m      <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${e.role}</span>\u001b[39m\n\u001b[31m    </label>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openNewTour() {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = null;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '🗺️ New Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Create Tour';\u001b[39m\n\u001b[31m  document.getElementById('nt-title').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-start').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-end').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-notes').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(_currentCampaignId)}&select=tour_id`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const existing = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    const nn = String(existing.length + 1).padStart(2, '0');\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T${nn}`;\u001b[39m\n\u001b[31m  } catch(_) {\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T01`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (_allEmployees.length)\u001b[39m\n\u001b[31m    document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes();·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEditTour(tourId) {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = tourId;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '✏️ Edit Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Save Changes';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const tours = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    if (!tours.length) { toast('Tour not found'); return; }\u001b[39m\n\u001b[31m    const t = tours[0];\u001b[39m\n\u001b[31m    document.getElementById('nt-title').value = t.title || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-start').value = t.start_date || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-end').value   = t.end_date   || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-notes').value = t.notes      || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = tourId;\u001b[39m\n\u001b[31m    if (_allEmployees.length)\u001b[39m\n\u001b[31m      document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes(t.assigned_employees || []);\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to load tour'); return; }·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewTour() {\u001b[39m\n\u001b[31m  const title = document.getElementById('nt-title').value.trim();\u001b[39m\n\u001b[31m  const start = document.getElementById('nt-start').value || null;\u001b[39m\n\u001b[31m  const end   = document.getElementById('nt-end').value   || null;\u001b[39m\n\u001b[31m  const notes = document.getElementById('nt-notes').value.trim() || null;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('nt-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const checked = [...document.querySelectorAll('#nt-employees input:checked')].map(cb => cb.value);\u001b[39m\n\u001b[31m  if (!checked.length) { errEl.textContent = 'Assign at least one employee'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nt-save');\u001b[39m\n\u001b[31m  const wasEdit = !!_editTourId;\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = wasEdit ? 'Saving…' : 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editTourId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(_editTourId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      const tourId = document.getElementById('nt-id-preview').textContent;\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          tour_id: tourId, campaign_id: _currentCampaignId,\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes, status: 'planned',\u001b[39m\n\u001b[31m          created_by: currentUser.id,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || (_editTourId ? 'Failed to save' : 'Failed to create');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show');\u001b[39m\n\u001b[31m    toast(_editTourId ? '✅ Tour updated' : '✅ Tour created');\u001b[39m\n\u001b[31m    _editTourId = null;\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = wasEdit ? 'Save Changes' : 'Create Tour';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── EVENT WIRING ── */\u001b[39m\n\u001b[31mfunction wireEvents() {\u001b[39m\n\u001b[31m  // Jobs screens\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-back').addEventListener('click',     () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-refresh').addEventListener('click',  loadJobs);\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').addEventListener('click',  openNewCampaign);\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-back').addEventListener('click', () => showScreen('jobs'));\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-refresh').addEventListener('click', () => loadCampaign(_currentCampaignId));\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').addEventListener('click',      openNewTour);·\u001b[39m\n\u001b[31m  // New Campaign modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-save').addEventListener('click', saveNewCampaign);\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign','nc-year','nc-phase'].forEach(id =>\u001b[39m\n\u001b[31m    document.getElementById(id).addEventListener('input', updateCampaignPreview));·\u001b[39m\n\u001b[31m  // New Tour modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').addEventListener('click', saveNewTour);·\u001b[39m\n\u001b[31m  document.getElementById('btn-team-back').addEventListener('click', () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-team-refresh').addEventListener('click', loadTeamPins);\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-cancel').addEventListener('click', () => {\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-save').addEventListener('click', doSetPin);·\u001b[39m\n\u001b[31m  // Set PIN — progressive field reveal\u001b[39m\n\u001b[31m  document.getElementById('setpin-new').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const val = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const showConf = val.length === 4;\u001b[39m\n\u001b[31m    document.getElementById('setpin-confirm-group').style.display = showConf ? '' : 'none';\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m    if (!showConf) { document.getElementById('setpin-confirm').value = ''; document.getElementById('setpin-auth').value = ''; }\u001b[39m\n\u001b[31m    if (showConf) setTimeout(() => document.getElementById('setpin-confirm').focus(), 20);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const newVal = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const conf   = document.getElementById('setpin-confirm').value;\u001b[39m\n\u001b[31m    const showAuth = conf.length === 4 && conf === newVal;\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = showAuth ? '' : 'none';\u001b[39m\n\u001b[31m    if (!showAuth) document.getElementById('setpin-auth').value = '';\u001b[39m\n\u001b[31m    if (showAuth) setTimeout(() => document.getElementById('setpin-auth').focus(), 20);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // Hub AI config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-save').addEventListener('click',   saveHubAiConfig);\u001b[39m\n\u001b[31m  document.getElementById('btn-check-keys').addEventListener('click',    checkApiKeyStatus);·\u001b[39m\n\u001b[31m  // Hub QA config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-qa').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-save').addEventListener('click', saveHubQaConfig);·\u001b[39m\n\u001b[31m  // Share picker\u001b[39m\n\u001b[31m  document.getElementById('share-to-expense').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/expense/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-recce').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/recce/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-installation').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/installation/current/?share=1';\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-04: employee dropdown change resets PIN buffer and lockout\u001b[39m\n\u001b[31m  document.getElementById('login-name').addEventListener('change', () => {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-07: ESC key closes any open modal\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (e.key === 'Escape') {\u001b[39m\n\u001b[31m      document.querySelectorAll('.modal-overlay.show').forEach(m => m.classList.remove('show'));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-11: keyboard digit/backspace input on login screen\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (!document.getElementById('screen-login').classList.contains('active')) return;\u001b[39m\n\u001b[31m    if (e.key >= '0' && e.key <= '9') { handlePinKey(e.key); }\u001b[39m\n\u001b[31m    else if (e.key === 'Backspace') { e.preventDefault(); handlePinKey('B'); }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-02/B-08: re-check session TTL when user returns to the tab\u001b[39m\n\u001b[31m  document.addEventListener('visibilitychange', () => {\u001b[39m\n\u001b[31m    if (document.visibilityState === 'visible' && currentUser) {\u001b[39m\n\u001b[31m      if (!loadSession()) doLogout();\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── BOOT ── */\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  wireEvents();·\u001b[39m\n\u001b[31m  if (location.search.includes('share=1')) {\u001b[39m\n\u001b[31m    pendingShare = true;\u001b[39m\n\u001b[31m    history.replaceState({}, '', '/hub/');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Always populate the login dropdown (needed if user logs out later)\u001b[39m\n\u001b[31m  initLogin(); // fire-and-forget — does not block home screen path·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess) {\u001b[39m\n\u001b[31m    currentUser = { id: sess.empId, name: sess.name, role: sess.role };\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  setTimeout(() => showScreen('login'), 1500);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SERVICE WORKER: install + update ── */\u001b[39m\n\u001b[31mif ('serviceWorker' in navigator) {\u001b[39m\n\u001b[31m  navigator.serviceWorker.register('/hub/sw.js').then(reg => {·\u001b[39m\n\u001b[31m    // Install banner: show when browser signals app is installable\u001b[39m\n\u001b[31m    let deferredInstall = null;\u001b[39m\n\u001b[31m    window.addEventListener('beforeinstallprompt', e => {\u001b[39m\n\u001b[31m      e.preventDefault();\u001b[39m\n\u001b[31m      deferredInstall = e;\u001b[39m\n\u001b[31m      if (!window.matchMedia('(display-mode: standalone)').matches) {\u001b[39m\n\u001b[31m        const delay = document.getElementById('update-banner').classList.contains('show') ? 3500 : 0;\u001b[39m\n\u001b[31m        setTimeout(() => document.getElementById('install-banner').classList.add('show'), delay);\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    window.addEventListener('appinstalled', () => {\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install').addEventListener('click', async () => {\u001b[39m\n\u001b[31m      if (!deferredInstall) return;\u001b[39m\n\u001b[31m      deferredInstall.prompt();\u001b[39m\n\u001b[31m      await deferredInstall.userChoice;\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m    // Update banner: show when a new SW version is waiting to activate\u001b[39m\n\u001b[31m    function showUpdateBanner() {\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.add('show');\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (reg.waiting) showUpdateBanner();·\u001b[39m\n\u001b[31m    reg.addEventListener('updatefound', () => {\u001b[39m\n\u001b[31m      const newSW = reg.installing;\u001b[39m\n\u001b[31m      newSW.addEventListener('statechange', () => {\u001b[39m\n\u001b[31m        if (newSW.state === 'installed' && navigator.serviceWorker.controller) {\u001b[39m\n\u001b[31m          showUpdateBanner();\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    document.getElementById('btn-update').addEventListener('click', () => {\u001b[39m\n\u001b[31m      if (reg.waiting) {\u001b[39m\n\u001b[31m        reg.waiting.postMessage({ type: 'SKIP_WAITING' });\u001b[39m\n\u001b[31m        navigator.serviceWorker.addEventListener('controllerchange', () => {\u001b[39m\n\u001b[31m          window.location.reload();\u001b[39m\n\u001b[31m        }, { once: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-update-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m  }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mboot();\u001b[39m\n\u001b[31m</script>··\u001b[39m\n\u001b[31m</body></html>\"\u001b[39m\n\n  329 |     await page.goto(BASE_URL);\n  330 |     const src = await page.content();\n> 331 |     expect(src).toContain('recce-v10');\n      |                 ^\n  332 |     expect(src).not.toContain('recce-v9');\n  333 |   });\n  334 | });\n    at /var/www/360lm/tests/recce.spec.js:331:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:22:41.194Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-5e896-tos-SW-version-is-recce-v10-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-5e896-tos-SW-version-is-recce-v10-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 331
                      }
                    },
                    {
                      "workerIndex": 1,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 2229,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"recce-v10\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html><html lang=\\\"en\\\"><head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#FF6B35\\\">\u001b[39m\n\u001b[31m<meta name=\\\"mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-status-bar-style\\\" content=\\\"black-translucent\\\">\u001b[39m\n\u001b[31m<title>360LM</title>\u001b[39m\n\u001b[31m<meta name=\\\"description\\\" content=\\\"360LM Operations Hub — employee tools\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/hub/manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"icon\\\" href=\\\"/hub/icons/icon-192.png\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--surface:#161616;--card:#1e1e1e;--border:#2a2a2a;\u001b[39m\n\u001b[31m  --accent:#FF6B35;--accent2:#FFB800;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --danger:#e74c3c;--success:#2ecc71;--info:#3498db;\u001b[39m\n\u001b[31m  --radius:12px;--font:'Segoe UI',system-ui,sans-serif;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:var(--font);min-height:100vh;overflow-x:hidden}\u001b[39m\n\u001b[31m.screen{display:none;min-height:100vh;flex-direction:column}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── HEADER ── */\u001b[39m\n\u001b[31m.header{background:var(--surface);border-bottom:1px solid var(--border);padding:14px 16px;\u001b[39m\n\u001b[31m  display:flex;align-items:center;gap:12px;position:sticky;top:0;z-index:10;flex-shrink:0}\u001b[39m\n\u001b[31m.header h1{font-size:18px;font-weight:700;color:var(--accent);flex:1}\u001b[39m\n\u001b[31m.header .sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.btn-icon{background:none;border:none;color:var(--muted);font-size:22px;cursor:pointer;\u001b[39m\n\u001b[31m  padding:4px;border-radius:8px;line-height:1;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m.btn-icon:active{background:var(--border)}·\u001b[39m\n\u001b[31m/* ── BODY ── */\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:16px}·\u001b[39m\n\u001b[31m/* ── SPLASH ── */\u001b[39m\n\u001b[31m#screen-splash{align-items:center;justify-content:center;gap:8px}\u001b[39m\n\u001b[31m.splash-logo{font-size:72px;font-weight:900;color:var(--accent);letter-spacing:-2px;line-height:1}\u001b[39m\n\u001b[31m.splash-sub{font-size:15px;color:var(--muted);letter-spacing:2px;text-transform:uppercase}\u001b[39m\n\u001b[31m.splash-dot{width:8px;height:8px;border-radius:50%;background:var(--accent);\u001b[39m\n\u001b[31m  animation:pulse 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes pulse{0%,100%{opacity:.3;transform:scale(.8)}50%{opacity:1;transform:scale(1)}}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  min-height:100dvh;padding:24px;gap:20px;overflow-y:auto}\u001b[39m\n\u001b[31m.login-logo{font-size:44px;font-weight:900;color:var(--accent);letter-spacing:-1px}\u001b[39m\n\u001b[31m.login-sub{font-size:13px;color:var(--muted);text-transform:uppercase;letter-spacing:2px}\u001b[39m\n\u001b[31m.form-group{width:100%;max-width:360px;display:flex;flex-direction:column;gap:8px}\u001b[39m\n\u001b[31m.form-group label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px}\u001b[39m\n\u001b[31mselect{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31mselect:focus{border-color:var(--accent)}·\u001b[39m\n\u001b[31m/* ── PIN PAD ── */\u001b[39m\n\u001b[31m.pin-display{display:flex;gap:14px;justify-content:center;margin:4px 0}\u001b[39m\n\u001b[31m.pin-dot{width:16px;height:16px;border-radius:50%;border:2px solid var(--muted);background:transparent;transition:.15s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.pin-pad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;max-width:260px;margin:0 auto;width:100%}\u001b[39m\n\u001b[31m.pin-key{background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  font-size:22px;font-weight:600;padding:16px;border-radius:var(--radius);cursor:pointer;\u001b[39m\n\u001b[31m  text-align:center;line-height:1;user-select:none}\u001b[39m\n\u001b[31m.pin-key:active{background:var(--border)}\u001b[39m\n\u001b[31m.pin-key.clr{color:var(--danger);font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.pin-key.back{color:var(--accent2)}\u001b[39m\n\u001b[31m.login-err{font-size:13px;color:var(--danger);text-align:center;min-height:18px}\u001b[39m\n\u001b[31m@keyframes shake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-6px)}40%,80%{transform:translateX(6px)}}\u001b[39m\n\u001b[31m.pin-display.shake{animation:shake .4s ease}·\u001b[39m\n\u001b[31m/* ── KPI CARDS ── */\u001b[39m\n\u001b[31m.kpi-row{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\u001b[39m\n\u001b[31m.kpi-4col{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m#home-body{gap:10px}\u001b[39m\n\u001b[31m.kpi-card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:10px 8px;text-align:center;border-left:3px solid var(--accent);cursor:default}\u001b[39m\n\u001b[31m.kpi-card.alert{border-left-color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-card.warn{border-left-color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-card.ok{border-left-color:var(--success)}\u001b[39m\n\u001b[31m.kpi-card.info{border-left-color:var(--info)}\u001b[39m\n\u001b[31m.kpi-val{font-size:18px;font-weight:900;color:var(--accent);line-height:1.2}\u001b[39m\n\u001b[31m.kpi-val.red{color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-val.amber{color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-val.green{color:var(--success)}\u001b[39m\n\u001b[31m.kpi-val.blue{color:var(--info)}\u001b[39m\n\u001b[31m.kpi-lbl{font-size:10px;color:var(--muted);margin-top:4px;text-transform:uppercase;letter-spacing:.4px;line-height:1.3}\u001b[39m\n\u001b[31m.kpi-skel{background:var(--border);border-radius:4px;height:24px;margin-bottom:4px;\u001b[39m\n\u001b[31m  animation:shimmer 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes shimmer{0%,100%{opacity:.5}50%{opacity:1}}·\u001b[39m\n\u001b[31m/* ── SECTION TITLE ── */\u001b[39m\n\u001b[31m.section-title{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.8px}·\u001b[39m\n\u001b[31m/* ── MGMT HOME: MAIN TILES ── */\u001b[39m\n\u001b[31m.hub-main-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px}\u001b[39m\n\u001b[31m.hub-main-tile{background:var(--card);border:2px solid var(--border);border-radius:18px;\u001b[39m\n\u001b[31m  padding:36px 12px 28px;text-align:center;cursor:pointer;transition:background .15s,border-color .15s}\u001b[39m\n\u001b[31m.hub-main-tile:active{background:#1a2a1a;border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-main-icon{font-size:40px;line-height:1;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-main-label{font-size:15px;font-weight:800;color:var(--text)}\u001b[39m\n\u001b[31m/* ── MGMT HOME: QUICK ACTIONS ── */\u001b[39m\n\u001b[31m.hub-qa-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-qa-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(88px,1fr));gap:10px}\u001b[39m\n\u001b[31m.hub-qa-tile{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px 8px;text-align:center;cursor:pointer;display:flex;flex-direction:column;\u001b[39m\n\u001b[31m  align-items:center;gap:6px;transition:background .15s}\u001b[39m\n\u001b[31m.hub-qa-tile:active{background:var(--border)}\u001b[39m\n\u001b[31m.hub-qa-icon{font-size:26px;line-height:1}\u001b[39m\n\u001b[31m.hub-qa-label{font-size:11px;font-weight:700;color:var(--text);line-height:1.3}\u001b[39m\n\u001b[31m.hub-qa-item{display:flex;align-items:center;gap:12px;padding:10px 0;\u001b[39m\n\u001b[31m  border-bottom:1px solid var(--border);cursor:pointer}\u001b[39m\n\u001b[31m.hub-qa-item:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.hub-qa-item-icon{font-size:22px;width:32px;text-align:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item-label{flex:1;font-size:14px;font-weight:600}\u001b[39m\n\u001b[31m.hub-qa-item-toggle{width:20px;height:20px;border-radius:4px;border:2px solid var(--border);\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle::after{content:'✓';color:#fff;font-weight:700;font-size:12px}·\u001b[39m\n\u001b[31m/* ── TILE GRID ── */\u001b[39m\n\u001b[31m.tile-grid{display:grid;gap:12px}\u001b[39m\n\u001b[31m.tile-grid.mgmt{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m.tile-grid.field{grid-template-columns:repeat(2,1fr)}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:18px 8px;display:flex;flex-direction:column;align-items:center;gap:8px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s;position:relative}\u001b[39m\n\u001b[31m.tile:active{background:var(--border);transform:scale(.97)}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px;line-height:1}\u001b[39m\n\u001b[31m.tile-label{font-size:11px;font-weight:700;color:var(--text);text-align:center;text-transform:uppercase;letter-spacing:.4px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-icon{font-size:24px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-label{font-size:10px}\u001b[39m\n\u001b[31m.tile.coming{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.tile.coming:active{transform:none;background:var(--card)}\u001b[39m\n\u001b[31m.soon-badge{position:absolute;top:6px;right:6px;background:var(--accent2);color:#000;\u001b[39m\n\u001b[31m  font-size:8px;font-weight:800;padding:2px 5px;border-radius:20px;text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── USER BADGE ── */\u001b[39m\n\u001b[31m.user-badge{display:flex;align-items:center;gap:12px}\u001b[39m\n\u001b[31m.avatar{width:44px;height:44px;background:var(--accent);border-radius:50%;\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:800;\u001b[39m\n\u001b[31m  color:#fff;flex-shrink:0;text-transform:uppercase}\u001b[39m\n\u001b[31m.user-name{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.user-role{font-size:12px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── TEAM PINS ── */\u001b[39m\n\u001b[31m.emp-row{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px}\u001b[39m\n\u001b[31m.btn{display:flex;align-items:center;justify-content:center;gap:6px;border:none;\u001b[39m\n\u001b[31m  border-radius:var(--radius);font-size:15px;font-weight:700;padding:12px 18px;\u001b[39m\n\u001b[31m  cursor:pointer;width:100%;transition:.15s;font-family:var(--font)}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--card);border:1px solid var(--border);color:var(--text)}\u001b[39m\n\u001b[31m.btn-sm{padding:7px 12px;font-size:12px;font-weight:700;width:auto;flex-shrink:0}\u001b[39m\n\u001b[31m.info-note{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;font-size:13px;color:var(--muted);line-height:1.5}·\u001b[39m\n\u001b[31m/* ── MODAL ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:200;\u001b[39m\n\u001b[31m  align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;\u001b[39m\n\u001b[31m  padding:24px;width:100%;max-width:480px;display:flex;flex-direction:column;gap:14px;\u001b[39m\n\u001b[31m  max-height:90dvh;overflow-y:auto;-webkit-overflow-scrolling:touch}\u001b[39m\n\u001b[31m.modal-title{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.modal-box input[type=password]{\u001b[39m\n\u001b[31m  width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31m.modal-box input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.action-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px}·\u001b[39m\n\u001b[31m/* ── SHARE PICKER ── */\u001b[39m\n\u001b[31m#screen-share .body{align-items:center;justify-content:center;gap:24px}\u001b[39m\n\u001b[31m.share-prompt{font-size:15px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.share-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;width:100%;max-width:360px}\u001b[39m\n\u001b[31m.share-tile{background:var(--card);border:2px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:24px 8px;display:flex;flex-direction:column;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s}\u001b[39m\n\u001b[31m.share-tile:active{background:var(--border);transform:scale(.97);border-color:var(--accent)}\u001b[39m\n\u001b[31m.share-tile-icon{font-size:36px;line-height:1}\u001b[39m\n\u001b[31m.share-tile-label{font-size:12px;font-weight:700;color:var(--text);text-align:center;\u001b[39m\n\u001b[31m  text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── INSTALL BANNER ── */\u001b[39m\n\u001b[31m#install-banner{display:none;position:fixed;bottom:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:linear-gradient(135deg,#1a1a1a,#0d2010);\u001b[39m\n\u001b[31m  border-top:1px solid var(--accent);padding:14px 16px;\u001b[39m\n\u001b[31m  align-items:center;gap:12px;box-shadow:0 -4px 24px rgba(0,0,0,.7)}\u001b[39m\n\u001b[31m#install-banner.show{display:flex}\u001b[39m\n\u001b[31m#install-banner .ib-icon{font-size:30px;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-text{flex:1}\u001b[39m\n\u001b[31m#install-banner .ib-title{font-size:14px;font-weight:700;color:var(--text)}\u001b[39m\n\u001b[31m#install-banner .ib-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#install-banner .ib-btn{background:var(--accent);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-close{background:none;border:none;color:var(--muted);font-size:20px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── UPDATE BANNER ── */\u001b[39m\n\u001b[31m#update-banner{display:none;position:fixed;top:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:#1a2a1a;border-bottom:2px solid var(--success);\u001b[39m\n\u001b[31m  padding:12px 16px;align-items:center;gap:12px}\u001b[39m\n\u001b[31m#update-banner.show{display:flex}\u001b[39m\n\u001b[31m#update-banner .ub-text{flex:1;font-size:14px;font-weight:600;color:var(--text)}\u001b[39m\n\u001b[31m#update-banner .ub-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#update-banner .ub-btn{background:var(--success);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:8px 14px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#update-banner .ub-close{background:none;border:none;color:var(--muted);font-size:18px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#222;color:#fff;padding:10px 20px;border-radius:20px;font-size:14px;font-weight:600;\u001b[39m\n\u001b[31m  opacity:0;transition:.3s;pointer-events:none;white-space:nowrap;z-index:500}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  padding:40px;color:var(--muted);font-size:14px;gap:12px}\u001b[39m\n\u001b[31m.spinner{width:36px;height:36px;border:3px solid var(--border);border-top-color:var(--accent);\u001b[39m\n\u001b[31m  border-radius:50%;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* ── CARD (jobs / campaign screens) ── */\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius)}\u001b[39m\n\u001b[31m.card.tappable:active{border-color:var(--accent);opacity:.85}·\u001b[39m\n\u001b[31m/* ── STATUS CHIPS (tour / campaign) ── */\u001b[39m\n\u001b[31m.status-chip{font-size:11px;padding:4px 11px;border-radius:8px;cursor:pointer;\u001b[39m\n\u001b[31m  background:var(--card);color:var(--text);border:1px solid var(--border);font-weight:600;\u001b[39m\n\u001b[31m  font-family:var(--font);transition:.15s}\u001b[39m\n\u001b[31m.status-chip.sel{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.status-chip:active{opacity:.8}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SPLASH\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-splash\\\" class=\\\"screen active\\\">\u001b[39m\n\u001b[31m  <div class=\\\"splash-logo\\\">360LM</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-sub\\\">Operations Hub</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-dot\\\" style=\\\"margin-top:16px\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     LOGIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-login\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-wrap\\\">\u001b[39m\n\u001b[31m    <div class=\\\"login-logo\\\">360LM</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">Operations Hub</div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Select Your Name</label>\u001b[39m\n\u001b[31m      <select id=\\\"login-name\\\"><option value=\\\"\\\">— Choose employee —</option><option value=\\\"amit\\\">Amit</option><option value=\\\"ashok\\\">Ashok</option><option value=\\\"gurpreet\\\">Gurpreet</option><option value=\\\"harish\\\">Harish Kumar Lal</option><option value=\\\"mukesh\\\">Mukesh</option><option value=\\\"narender\\\">Narender</option><option value=\\\"pramod\\\">Pramod Narang</option><option value=\\\"rakesh\\\">Rakesh</option><option value=\\\"sachin\\\">Sachin</option><option value=\\\"shambhu\\\">Shambhu</option><option value=\\\"umashankar\\\">Umashankar</option></select>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m      <label style=\\\"text-align:center\\\">Enter PIN</label>\u001b[39m\n\u001b[31m      <div class=\\\"pin-display\\\" id=\\\"pin-display\\\">\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd0\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd1\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd2\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd3\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"login-err\\\" id=\\\"login-err\\\"></div>\u001b[39m\n\u001b[31m      <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-1\\\" data-k=\\\"1\\\" aria-label=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-2\\\" data-k=\\\"2\\\" aria-label=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-3\\\" data-k=\\\"3\\\" aria-label=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-4\\\" data-k=\\\"4\\\" aria-label=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-5\\\" data-k=\\\"5\\\" aria-label=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-6\\\" data-k=\\\"6\\\" aria-label=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-7\\\" data-k=\\\"7\\\" aria-label=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-8\\\" data-k=\\\"8\\\" aria-label=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-9\\\" data-k=\\\"9\\\" aria-label=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key clr\\\" id=\\\"pk-C\\\" data-k=\\\"C\\\" aria-label=\\\"Clear\\\">CLR</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-0\\\" data-k=\\\"0\\\" aria-label=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key back\\\" id=\\\"pk-B\\\" data-k=\\\"B\\\" aria-label=\\\"Backspace\\\">⌫</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     HOME  (built dynamically)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\" id=\\\"home-header\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"home-body\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SHARE PICKER\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-share\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>Share Image To</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">Choose where to send this photo</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"share-prompt\\\">Which tool should receive this image?</div>\u001b[39m\n\u001b[31m    <div class=\\\"share-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-expense\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🧾</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Expenses</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-recce\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">📍</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Recce</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-installation\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🔧</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Installation</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     TEAM PINs  (admin only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-team\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-back\\\" style=\\\"font-size:20px\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Team PINs</h1><div class=\\\"sub\\\">Set employee login PINs</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-refresh\\\" title=\\\"Refresh\\\">🔄</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"info-note\\\">PINs are stored encrypted — current PIN cannot be viewed. You can set a new PIN for any employee below.</div>\u001b[39m\n\u001b[31m    <div id=\\\"team-list\\\"></div>·\u001b[39m\n\u001b[31m    <!-- Icon Admin — Harish & Pramod only -->\u001b[39m\n\u001b[31m    <div id=\\\"icon-admin-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:8px\\\">🎨 PWA Icons (Admin)</div>\u001b[39m\n\u001b[31m      <div class=\\\"info-note\\\" style=\\\"margin-bottom:10px\\\">Upload a new icon for any PWA. Upload both 192×192 and 512×512 sizes. PNG/JPG/WEBP accepted — auto-resized on server.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;flex-direction:column;gap:10px\\\">·\u001b[39m\n\u001b[31m        <!-- Secret management -->\u001b[39m\n\u001b[31m        <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:12px;padding:14px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px\\\">🔑 Admin Secret</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:center;gap:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"font-family:monospace;font-size:13px;color:var(--accent);background:var(--bg);\u001b[39m\n\u001b[31m              border-radius:6px;padding:8px 12px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\" id=\\\"icon-secret-display\\\">—</div>\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"toggleSecretVisible()\\\" id=\\\"btn-secret-show\\\">Show</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:10px\\\">Change secret (min 16 chars):</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m            <input id=\\\"icon-new-secret\\\" type=\\\"text\\\" placeholder=\\\"Enter new secret…\\\" style=\\\"flex:1;background:var(--bg);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m              padding:10px 12px;border-radius:8px;font-size:13px;font-family:monospace;outline:none\\\">\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"changeIconSecret()\\\" style=\\\"white-space:nowrap\\\">Update</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div id=\\\"icon-secret-err\\\" style=\\\"font-size:11px;color:var(--danger);min-height:14px;margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>·\u001b[39m\n\u001b[31m        <!-- Upload form -->\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Select PWA</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-pwa-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"hub\\\">Hub</option>\u001b[39m\n\u001b[31m            <option value=\\\"expense\\\">Expenses</option>\u001b[39m\n\u001b[31m            <option value=\\\"finance\\\">Finance</option>\u001b[39m\n\u001b[31m            <option value=\\\"installation\\\">Installation</option>\u001b[39m\n\u001b[31m            <option value=\\\"recce\\\">Recce</option>\u001b[39m\n\u001b[31m            <option value=\\\"dispatch\\\">Dispatch</option>\u001b[39m\n\u001b[31m            <option value=\\\"production\\\">Production</option>\u001b[39m\n\u001b[31m            <option value=\\\"vehicle\\\">Vehicle Log</option>\u001b[39m\n\u001b[31m            <option value=\\\"vrs\\\">Vehicle Request</option>\u001b[39m\n\u001b[31m            <option value=\\\"sales\\\">Sales</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Icon Size</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-size-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"192\\\">192 × 192 (standard)</option>\u001b[39m\n\u001b[31m            <option value=\\\"512\\\">512 × 512 (large)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Choose Image File</label>\u001b[39m\n\u001b[31m          <input type=\\\"file\\\" id=\\\"icon-file-input\\\" accept=\\\"image/png,image/jpeg,image/webp\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:14px;width:100%\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-preview-wrap\\\" style=\\\"display:none;text-align:center\\\">\u001b[39m\n\u001b[31m          <img id=\\\"icon-preview-img\\\" style=\\\"width:96px;height:96px;border-radius:16px;border:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-upload-err\\\" style=\\\"font-size:12px;color:var(--danger);min-height:16px\\\"></div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" onclick=\\\"uploadIcon()\\\">Upload Icon</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     MODAL: Admin set PIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-setpin\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🔑 Set PIN — <span id=\\\"setpin-target-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>New PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Enter new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-confirm-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Confirm New PIN</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-confirm\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Repeat new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-auth-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Your own current PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"setpin-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-setpin-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-setpin-save\\\">Set PIN</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     JOBS — Campaign list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-jobs\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Jobs</h1><div class=\\\"sub\\\">Campaigns &amp; Tours</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh jobs\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-campaign\\\" title=\\\"New Campaign\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"jobs-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     CAMPAIGN DETAIL — Tour list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-campaign\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 id=\\\"campaign-hdr-title\\\">Campaign</h1><div class=\\\"sub\\\" id=\\\"campaign-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh campaign\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-tour\\\" title=\\\"New Tour\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"campaign-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Campaign -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-campaign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">📋 New Campaign</div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Year</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-year\\\" min=\\\"2024\\\" max=\\\"2030\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Phase #</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-phase\\\" value=\\\"1\\\" min=\\\"1\\\" max=\\\"99\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Brand <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-brand\\\" placeholder=\\\"e.g. Lenovo\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Client <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-client\\\" placeholder=\\\"e.g. Lenovo India\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Campaign Name <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-campaign\\\" placeholder=\\\"e.g. Chandigarh BTL\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Generated Campaign ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nc-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nc-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nc-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nc-save\\\">Create</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Tour -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-tour\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px;max-height:88vh;overflow-y:auto\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🗺️ New Tour</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Tour Title</div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nt-title\\\" placeholder=\\\"e.g. Chandigarh Week 1\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Start Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-start\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">End Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-end\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:6px\\\">Assign Employees <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-employees\\\" style=\\\"display:flex;flex-direction:column;gap:6px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"color:var(--muted);font-size:13px\\\">Loading…</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Notes</div>\u001b[39m\n\u001b[31m      <textarea id=\\\"nt-notes\\\" rows=\\\"2\\\" placeholder=\\\"Optional instructions or remarks\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px;font-family:inherit;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Tour ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-id-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nt-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nt-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nt-save\\\">Create Tour</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- UPDATE BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"update-banner\\\">\u001b[39m\n\u001b[31m  <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ub-text\\\">App update available</div>\u001b[39m\n\u001b[31m    <div class=\\\"ub-sub\\\">Tap to refresh and get the latest version</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ub-btn\\\" id=\\\"btn-update\\\">Update now</button>\u001b[39m\n\u001b[31m  <button class=\\\"ub-close\\\" id=\\\"btn-update-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- INSTALL BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"install-banner\\\">\u001b[39m\n\u001b[31m  <div class=\\\"ib-icon\\\">📲</div>\u001b[39m\n\u001b[31m  <div class=\\\"ib-text\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ib-title\\\">Install 360LM Hub</div>\u001b[39m\n\u001b[31m    <div class=\\\"ib-sub\\\">Add to home screen for the best experience</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ib-btn\\\" id=\\\"btn-install\\\">Install</button>\u001b[39m\n\u001b[31m  <button class=\\\"ib-close\\\" id=\\\"btn-install-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: AI Config (mgmt only) -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-ai\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🤖 AI Config — Screenshot Extraction</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Tesseract OCR</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-tesseract\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Extraction Fallback</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-enabled\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Model</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-model\\\">\u001b[39m\n\u001b[31m        <option value=\\\"haiku\\\">Haiku (fast, cost-effective)</option>\u001b[39m\n\u001b[31m        <option value=\\\"sonnet\\\">Sonnet (higher accuracy)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-ai-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-ai-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"border-top:1px solid var(--border);padding-top:12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">API Key Health</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px;margin-bottom:10px\\\">Live check — result is always the same for all users</div>\u001b[39m\n\u001b[31m      <div id=\\\"api-key-status-wrap\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-check-keys\\\" style=\\\"margin-top:10px;width:100%\\\">Check API Keys</button>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:14px;border-top:1px solid var(--border);padding-top:12px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">View Usage &amp; Billing</div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Anthropic</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/usage\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Google Maps</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/google/maps-apis/api-list\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: Hub Quick-Action Config -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-qa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">⚡ Quick Actions</div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-config-list\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-cfg-err\\\" style=\\\"color:var(--danger);font-size:13px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-qa-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-qa-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SCRIPT\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31m/* ── CONFIG ── */\u001b[39m\n\u001b[31mconst CFG = {\u001b[39m\n\u001b[31m  api:        '/db',\u001b[39m\n\u001b[31m  apiHeaders: { 'Content-Type': 'application/json', 'Accept-Profile': 'expense' },\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31m/* ── SERVER ACCESS MAP (fetched from hub-access.json, overrides ACCESS for non-mgmt) ── */\u001b[39m\n\u001b[31mlet _serverAccess = {};\u001b[39m\n\u001b[31masync function loadHubAccess() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/hub/hub-access.json?t=' + Date.now());\u001b[39m\n\u001b[31m    if (r.ok) _serverAccess = await r.json();\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── ROLE ACCESS MAP (hardcoded fallback) ── */\u001b[39m\n\u001b[31mconst ACCESS = {\u001b[39m\n\u001b[31m  harish:     ['expense','installation','finance','recce','dispatch','production','jobs','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  pramod:     ['expense','installation','finance','recce','dispatch','production','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  rakesh:     ['installation','expense','production','jobs','vehicle','vrs','sales','hr'],\u001b[39m\n\u001b[31m  ashok:      ['expense','production','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  shambhu:    ['finance','expense','vehicle','vrs','stores','activity','hr'],\u001b[39m\n\u001b[31m  gurpreet:   ['expense','dispatch','vehicle','vrs','stores','hr'],\u001b[39m\n\u001b[31m  sachin:     ['installation','expense','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  mukesh:     ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  umashankar: ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  narender:   ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  amit:       ['vehicle','vrs','hr'],\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst TILES = [\u001b[39m\n\u001b[31m  { id:'expense',      label:'Expenses',     icon:'🧾', url:'/expense/',               live:true  },\u001b[39m\n\u001b[31m  { id:'installation', label:'Installation', icon:'🔧', url:'/installation/current/',  live:true  },\u001b[39m\n\u001b[31m  { id:'finance',      label:'Finance',      icon:'💰', url:'/finance/',               live:true  },\u001b[39m\n\u001b[31m  { id:'recce',        label:'Recce',        icon:'📍', url:'/recce/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'dispatch',     label:'Dispatch',     icon:'🚚', url:'/dispatch/',              live:true  },\u001b[39m\n\u001b[31m  { id:'production',   label:'Production',   icon:'⚙️', url:'/production/',            live:true  },\u001b[39m\n\u001b[31m  { id:'jobs',         label:'Jobs',         icon:'📋', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'vehicle',      label:'Vehicle Log',  icon:'🚐', url:'/vehicle/',               live:true  },\u001b[39m\n\u001b[31m  { id:'vrs',          label:'Veh. Request', icon:'📋', url:'/vrs/',                   live:true  },\u001b[39m\n\u001b[31m  { id:'sales',        label:'Sales',        icon:'💼', url:'/sales/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'stores',       label:'Stores',       icon:'📦', url:'/stores/',                live:true  },\u001b[39m\n\u001b[31m  { id:'client',       label:'Client',       icon:'👤', url:'/client/',                live:true  },\u001b[39m\n\u001b[31m  { id:'team-pins',    label:'Team PINs',    icon:'🔑', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'admin',        label:'Admin',        icon:'🛠️', url:'/admin/',                  live:true  },\u001b[39m\n\u001b[31m  { id:'activity',     label:'Activity',     icon:'🎯', url:'/activity/',               live:true  },\u001b[39m\n\u001b[31m  { id:'hr',           label:'HR',           icon:'👥', url:'/hr/',                     live:true  },\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31mconst MGMT = ['harish', 'pramod'];·\u001b[39m\n\u001b[31m/* ── STATE ── */\u001b[39m\n\u001b[31mlet currentUser     = null;\u001b[39m\n\u001b[31mlet pinBuffer       = '';\u001b[39m\n\u001b[31mlet setPinTarget    = null;\u001b[39m\n\u001b[31mlet pendingShare    = false;\u001b[39m\n\u001b[31mlet _loginAttempts  = 0;\u001b[39m\n\u001b[31mlet _lockoutUntil   = 0;\u001b[39m\n\u001b[31mlet _loginInFlight  = false;·\u001b[39m\n\u001b[31m/* ── UTILS ── */\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => { s.classList.remove('active'); s.style.display = ''; });\u001b[39m\n\u001b[31m  document.getElementById('screen-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet toastTimer;\u001b[39m\n\u001b[31mfunction toast(msg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.classList.add('show');\u001b[39m\n\u001b[31m  clearTimeout(toastTimer);\u001b[39m\n\u001b[31m  toastTimer = setTimeout(() => el.classList.remove('show'), 2800);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fmtINR(n) {\u001b[39m\n\u001b[31m  if (n >= 100000) return '₹' + (n / 100000).toFixed(1) + 'L';\u001b[39m\n\u001b[31m  if (n >= 1000)   return '₹' + (n / 1000).toFixed(1) + 'K';\u001b[39m\n\u001b[31m  return '₹' + Math.round(n);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SESSION ── */\u001b[39m\n\u001b[31mconst SESSION_KEY  = 'lm360-session';\u001b[39m\n\u001b[31mconst EXP_SES_KEY  = '360exp-session';\u001b[39m\n\u001b[31mconst SESSION_TTL  = 12 * 3600 * 1000;·\u001b[39m\n\u001b[31mfunction saveSession(user) {\u001b[39m\n\u001b[31m  const payload = { empId: user.id, name: user.name, role: user.role, loginAt: new Date().toISOString(), source: 'hub' };\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m  localStorage.setItem(EXP_SES_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearSession() {\u001b[39m\n\u001b[31m  localStorage.removeItem(SESSION_KEY);\u001b[39m\n\u001b[31m  localStorage.removeItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const raw = localStorage.getItem(SESSION_KEY) || localStorage.getItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m    if (!raw) return null;\u001b[39m\n\u001b[31m    const s = JSON.parse(raw);\u001b[39m\n\u001b[31m    if (!s.empId || !s.name || !s.role) return null;\u001b[39m\n\u001b[31m    if (Date.now() - new Date(s.loginAt).getTime() > SESSION_TTL) { clearSession(); return null; }\u001b[39m\n\u001b[31m    return s;\u001b[39m\n\u001b[31m  } catch (_) { return null; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGIN FLOW ── */\u001b[39m\n\u001b[31masync function initLogin() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('login-name');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— Choose employee —</option>'; // clear before repopulating\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    emps.forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    [\u001b[39m\n\u001b[31m      { id:'amit',        name:'Amit'             },\u001b[39m\n\u001b[31m      { id:'ashok',       name:'Ashok'            },\u001b[39m\n\u001b[31m      { id:'gurpreet',    name:'Gurpreet'         },\u001b[39m\n\u001b[31m      { id:'harish',      name:'Harish Kumar Lal' },\u001b[39m\n\u001b[31m      { id:'mukesh',      name:'Mukesh'           },\u001b[39m\n\u001b[31m      { id:'narender',    name:'Narender'         },\u001b[39m\n\u001b[31m      { id:'pramod',      name:'Pramod Narang'    },\u001b[39m\n\u001b[31m      { id:'rakesh',      name:'Rakesh'           },\u001b[39m\n\u001b[31m      { id:'sachin',      name:'Sachin'           },\u001b[39m\n\u001b[31m      { id:'shambhu',     name:'Shambhu'          },\u001b[39m\n\u001b[31m      { id:'umashankar',  name:'Umashankar'       },\u001b[39m\n\u001b[31m    ].forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  document.querySelectorAll('.pin-key').forEach(btn =>\u001b[39m\n\u001b[31m    btn.addEventListener('click', () => handlePinKey(btn.dataset.k))\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handlePinKey(k) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (_loginInFlight) return;\u001b[39m\n\u001b[31m  // Lockout guard\u001b[39m\n\u001b[31m  if (_lockoutUntil && Date.now() < _lockoutUntil) {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    errEl.textContent = `Too many attempts — wait ${secs}s`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (_lockoutUntil) { _lockoutUntil = 0; _loginAttempts = 0; }\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (k === 'C') { pinBuffer = ''; }\u001b[39m\n\u001b[31m  else if (k === 'B') { pinBuffer = pinBuffer.slice(0, -1); }\u001b[39m\n\u001b[31m  else {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (!empId) { toast('Select your name first'); return; }\u001b[39m\n\u001b[31m    if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  if (pinBuffer.length === 4) {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (empId) attemptLogin(empId, pinBuffer);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updatePinDisplay() {\u001b[39m\n\u001b[31m  for (let i = 0; i < 4; i++)\u001b[39m\n\u001b[31m    document.getElementById('pd' + i).classList.toggle('filled', i < pinBuffer.length);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _shakePin() {\u001b[39m\n\u001b[31m  const pd = document.getElementById('pin-display');\u001b[39m\n\u001b[31m  pd.classList.add('shake');\u001b[39m\n\u001b[31m  setTimeout(() => pd.classList.remove('shake'), 400);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _startLockout() {\u001b[39m\n\u001b[31m  _lockoutUntil = Date.now() + 30000;\u001b[39m\n\u001b[31m  _loginAttempts = 0;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  const tick = () => {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    if (secs > 0) { errEl.textContent = `Too many attempts — wait ${secs}s`; setTimeout(tick, 500); }\u001b[39m\n\u001b[31m    else { _lockoutUntil = 0; errEl.textContent = ''; }\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  tick();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function attemptLogin(empId, pin) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  _loginInFlight = true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pin }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      _loginAttempts++;\u001b[39m\n\u001b[31m      _shakePin();\u001b[39m\n\u001b[31m      if (_loginAttempts >= 5) { _startLockout(); return; }\u001b[39m\n\u001b[31m      errEl.textContent = `Wrong PIN — ${5 - _loginAttempts} attempt${5 - _loginAttempts !== 1 ? 's' : ''} left`;\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    currentUser = { id: user.id, name: user.name, role: user.role };\u001b[39m\n\u001b[31m    saveSession(currentUser);\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — check connection';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    _loginInFlight = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── HOME SCREEN ── */\u001b[39m\n\u001b[31masync function buildHome() {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const body   = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  // Management users always use hardcoded ACCESS; others prefer server-side hub-access.json\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const tiles  = accessIds.map(id => TILES.find(t => t.id === id)).filter(Boolean);·\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"avatar\\\">${initials}</div>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1 style=\\\"font-size:16px\\\">${currentUser.name}</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">${currentUser.role}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${isMgmt ? '<button class=\\\"btn-icon\\\" id=\\\"btn-hub-ai\\\" title=\\\"AI Config\\\" style=\\\"font-size:18px\\\">🤖</button>' : ''}\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m  if (isMgmt) document.getElementById('btn-hub-ai').addEventListener('click', openHubAiConfig);·\u001b[39m\n\u001b[31m  if (isMgmt) {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"hub-main-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-dash\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">📊</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">Dashboard</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-tools\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">🧰</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">All Tools</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-header\\\">\u001b[39m\n\u001b[31m        <div class=\\\"section-title\\\" style=\\\"margin:0\\\">Quick Actions</div>\u001b[39m\n\u001b[31m        <button class=\\\"btn-icon\\\" id=\\\"btn-hub-qa-cfg\\\" title=\\\"Configure Quick Actions\\\" style=\\\"font-size:18px;padding:8px;min-width:44px;min-height:44px\\\">⚙️</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-grid\\\" id=\\\"hub-qa-grid\\\"></div>`;\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-dash').addEventListener('click',  () => enterHubDash(tiles));\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-tools').addEventListener('click', () => enterHubTools(tiles));\u001b[39m\n\u001b[31m    document.getElementById('btn-hub-qa-cfg').addEventListener('click', openHubQaConfig);\u001b[39m\n\u001b[31m    renderHubQA();\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"tile-grid field\\\" id=\\\"tile-grid\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"text-align:center;font-size:12px;color:var(--muted);padding-top:8px\\\">Tap a tile to open your tool</div>`;\u001b[39m\n\u001b[31m    renderTiles(tiles, 'field');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderTiles(tiles, mode) {\u001b[39m\n\u001b[31m  const grid = document.getElementById('tile-grid');\u001b[39m\n\u001b[31m  tiles.forEach(tile => {\u001b[39m\n\u001b[31m    const div = document.createElement('div');\u001b[39m\n\u001b[31m    div.className = 'tile ' + mode + (tile.live ? '' : ' coming');\u001b[39m\n\u001b[31m    div.innerHTML = `<div class=\\\"tile-icon\\\">${tile.icon}</div><div class=\\\"tile-label\\\">${tile.label}</div>` +\u001b[39m\n\u001b[31m      (tile.live ? '' : '<div class=\\\"soon-badge\\\">Soon</div>');\u001b[39m\n\u001b[31m    div.addEventListener('click', () => handleTile(tile));\u001b[39m\n\u001b[31m    grid.appendChild(div);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handleTile(tile) {\u001b[39m\n\u001b[31m  if (!tile.live) { toast('Coming soon'); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'team-pins') { enterTeamPins(); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'jobs')      { enterJobs();     return; }\u001b[39m\n\u001b[31m  window.location.href = tile.url;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT SUB-VIEWS ── */\u001b[39m\n\u001b[31mfunction _mgmtHeader(title) {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-subview-back\\\" style=\\\"font-size:20px;font-weight:900\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 style=\\\"font-size:16px\\\">${title}</h1></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-subview-back').addEventListener('click', () => buildHome());\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubDash(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('Dashboard');\u001b[39m\n\u001b[31m  const hdr = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const refreshBtn = document.createElement('button');\u001b[39m\n\u001b[31m  refreshBtn.className = 'btn-icon';\u001b[39m\n\u001b[31m  refreshBtn.title = 'Refresh dashboard';\u001b[39m\n\u001b[31m  refreshBtn.setAttribute('aria-label', 'Refresh dashboard');\u001b[39m\n\u001b[31m  refreshBtn.style.cssText = 'font-size:18px';\u001b[39m\n\u001b[31m  refreshBtn.textContent = '🔄';\u001b[39m\n\u001b[31m  refreshBtn.addEventListener('click', loadKPIs);\u001b[39m\n\u001b[31m  hdr.insertBefore(refreshBtn, hdr.querySelector('#btn-logout'));\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Actions Required</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-actions\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Operations</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row kpi-4col\\\" id=\\\"kpi-ops\\\">${skel}${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Production</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-prod\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Installation</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-inst\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">HR</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-hr\\\">${skel}${skel}${skel}</div>`;\u001b[39m\n\u001b[31m  loadKPIs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubTools(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('All Tools');\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  body.innerHTML = `<div class=\\\"tile-grid mgmt\\\" id=\\\"tile-grid\\\"></div>`;\u001b[39m\n\u001b[31m  renderTiles(tiles, 'mgmt');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT AI CONFIG ── */\u001b[39m\n\u001b[31mconst HUB_FIN_HDR = { 'Content-Type': 'application/json', 'Accept-Profile': 'finance' };·\u001b[39m\n\u001b[31masync function openHubAiConfig() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r    = await fetch(`${CFG.api}/upi_config`, { headers: HUB_FIN_HDR });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const get  = key => rows.find(x => x.key === key)?.value;\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-tesseract').value = get('tesseract_enabled') || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-enabled').value   = get('ai_enabled')        || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-model').value     = get('ai_model')           || 'haiku';\u001b[39m\n\u001b[31m  } catch(_) { toast('Could not load AI config'); return; }\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-ai').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveHubAiConfig() {\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-hub-ai-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  const pairs = [\u001b[39m\n\u001b[31m    { key:'tesseract_enabled', value: document.getElementById('hub-ai-tesseract').value },\u001b[39m\n\u001b[31m    { key:'ai_enabled',        value: document.getElementById('hub-ai-enabled').value },\u001b[39m\n\u001b[31m    { key:'ai_model',          value: document.getElementById('hub-ai-model').value },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const results = await Promise.all(pairs.map(p =>\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/upi_config?key=eq.${p.key}`, {\u001b[39m\n\u001b[31m        method: 'PATCH',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'finance', 'Prefer': 'return=minimal' },\u001b[39m\n\u001b[31m        body: JSON.stringify({ value: p.value }),\u001b[39m\n\u001b[31m      })\u001b[39m\n\u001b[31m    ));\u001b[39m\n\u001b[31m    if (results.some(r => !r.ok)) throw new Error('partial failure');\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show');\u001b[39m\n\u001b[31m    toast('✅ AI config saved');\u001b[39m\n\u001b[31m  } catch(_) { toast('Save failed'); }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Save'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function checkApiKeyStatus() {\u001b[39m\n\u001b[31m  const btn  = document.getElementById('btn-check-keys');\u001b[39m\n\u001b[31m  const wrap = document.getElementById('api-key-status-wrap');\u001b[39m\n\u001b[31m  btn.textContent = 'Checking…';\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  wrap.innerHTML = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/tour-ai/key-status');\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Proxy error ' + r.status);\u001b[39m\n\u001b[31m    const d = await r.json();\u001b[39m\n\u001b[31m    function mkBadge(info, label, link) {\u001b[39m\n\u001b[31m      const col  = info.ok ? 'var(--success)' : 'var(--danger)';\u001b[39m\n\u001b[31m      const icon = info.ok ? '✅' : '❌';\u001b[39m\n\u001b[31m      const note = info.ok ? (info.model || 'Valid') : (info.error || 'Error');\u001b[39m\n\u001b[31m      const hint = info.hint ? ' · key ' + info.hint : '';\u001b[39m\n\u001b[31m      return `<div style=\\\"background:var(--card);border:1px solid var(--border);border-left:3px solid ${col};border-radius:8px;padding:10px 12px;display:flex;align-items:center;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:13px;font-weight:600;color:var(--text)\\\">${icon} ${label}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${note}${hint}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <a href=\\\"${link}\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:11px;color:var(--info);text-decoration:none;white-space:nowrap\\\">Billing ↗</a>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    wrap.innerHTML =\u001b[39m\n\u001b[31m      mkBadge(d.anthropic, 'Anthropic API',    'https://console.anthropic.com/settings/billing') +\u001b[39m\n\u001b[31m      mkBadge(d.maps,      'Google Maps API',  'https://console.cloud.google.com/billing');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    wrap.innerHTML = `<div style=\\\"font-size:12px;color:var(--danger)\\\">Error: ${e.message}</div>`;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.textContent = 'Check Again';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT QUICK ACTIONS ── */\u001b[39m\n\u001b[31mconst HUB_QA_DEFS = [\u001b[39m\n\u001b[31m  { id:'offer',       icon:'📋', label:'New Offer',    accessId:'sales',        fn:() => { window.location.href='/sales/'; } },\u001b[39m\n\u001b[31m  { id:'paytm',       icon:'📱', label:'UPI Pay',       accessId:'finance',      fn:() => { window.location.href='/finance/?qa=upi'; } },\u001b[39m\n\u001b[31m  { id:'expense',     icon:'🧾', label:'Expenses',      accessId:'expense',      fn:() => { window.location.href='/expense/'; } },\u001b[39m\n\u001b[31m  { id:'finance',     icon:'💰', label:'Finance',       accessId:'finance',      fn:() => { window.location.href='/finance/'; } },\u001b[39m\n\u001b[31m  { id:'dispatch',    icon:'🚚', label:'Dispatch',      accessId:'dispatch',     fn:() => { window.location.href='/dispatch/'; } },\u001b[39m\n\u001b[31m  { id:'stores',      icon:'📦', label:'Stores',        accessId:'stores',       fn:() => { window.location.href='/stores/'; } },\u001b[39m\n\u001b[31m  { id:'recce',       icon:'📍', label:'Recce',         accessId:'recce',        fn:() => { window.location.href='/recce/'; } },\u001b[39m\n\u001b[31m  { id:'production',  icon:'⚙️', label:'Production',   accessId:'production',   fn:() => { window.location.href='/production/'; } },\u001b[39m\n\u001b[31m  { id:'tours',       icon:'🗺️', label:'Tour Plan',   accessId:'installation', fn:() => { window.location.href='/tour-planner/'; } },\u001b[39m\n\u001b[31m  { id:'installation',icon:'🔧', label:'Installation',  accessId:'installation', fn:() => { window.location.href='/installation/current/'; } },\u001b[39m\n\u001b[31m  { id:'activity',    icon:'🎯', label:'Activity',      accessId:'activity',     fn:() => { window.location.href='/activity/'; } },\u001b[39m\n\u001b[31m];\u001b[39m\n\u001b[31mconst HUB_QA_DEFAULT = ['offer', 'paytm'];\u001b[39m\n\u001b[31mconst HUB_QA_STORE   = '360lm-hub-qa';\u001b[39m\n\u001b[31mlet   _hubQaTempSel  = [];·\u001b[39m\n\u001b[31mfunction getHubQA() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(HUB_QA_STORE));\u001b[39m\n\u001b[31m    if (Array.isArray(s) && s.length) return s;\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m  return [...HUB_QA_DEFAULT];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHubQA() {\u001b[39m\n\u001b[31m  const ids  = getHubQA();\u001b[39m\n\u001b[31m  const grid = document.getElementById('hub-qa-grid');\u001b[39m\n\u001b[31m  if (!grid) return;\u001b[39m\n\u001b[31m  if (!ids.length) {\u001b[39m\n\u001b[31m    grid.innerHTML = `<div style=\\\"color:var(--muted);font-size:13px;grid-column:1/-1;\u001b[39m\n\u001b[31m      text-align:center;padding:16px\\\">Tap ⚙️ to add quick actions</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  grid.innerHTML = ids.map(id => {\u001b[39m\n\u001b[31m    const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!d) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-tile\\\" onclick=\\\"hubQaRun('${id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction hubQaRun(id) {\u001b[39m\n\u001b[31m  const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m  if (d) d.fn();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openHubQaConfig() {\u001b[39m\n\u001b[31m  _hubQaTempSel = [...getHubQA()];\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const visibleDefs = HUB_QA_DEFS.filter(d => accessIds.includes(d.accessId));\u001b[39m\n\u001b[31m  const list = document.getElementById('hub-qa-config-list');\u001b[39m\n\u001b[31m  list.innerHTML = visibleDefs.map(d => {\u001b[39m\n\u001b[31m    const sel = _hubQaTempSel.includes(d.id);\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-item${sel?' selected':''}\\\" id=\\\"hub-qa-item-${d.id}\\\"\u001b[39m\n\u001b[31m        onclick=\\\"toggleHubQaItem('${d.id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-toggle\\\"></div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleHubQaItem(id) {\u001b[39m\n\u001b[31m  const idx = _hubQaTempSel.indexOf(id);\u001b[39m\n\u001b[31m  if (idx >= 0) {\u001b[39m\n\u001b[31m    _hubQaTempSel.splice(idx, 1);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    if (_hubQaTempSel.length >= 6) {\u001b[39m\n\u001b[31m      document.getElementById('hub-qa-cfg-err').textContent = 'Maximum 6 quick actions';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _hubQaTempSel.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  const el = document.getElementById('hub-qa-item-' + id);\u001b[39m\n\u001b[31m  if (el) el.className = 'hub-qa-item' + (_hubQaTempSel.includes(id) ? ' selected' : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction saveHubQaConfig() {\u001b[39m\n\u001b[31m  localStorage.setItem(HUB_QA_STORE, JSON.stringify(_hubQaTempSel));\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.remove('show');\u001b[39m\n\u001b[31m  renderHubQA();\u001b[39m\n\u001b[31m  toast('✅ Quick actions updated');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── KPI FETCH ── */\u001b[39m\n\u001b[31mfunction _hdr(schema) {\u001b[39m\n\u001b[31m  return { 'Content-Type': 'application/json', 'Accept-Profile': schema };\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _safe(p) { return p.catch(() => null); }·\u001b[39m\n\u001b[31masync function loadKPIs() {\u001b[39m\n\u001b[31m  const mon = firstOfMonth();\u001b[39m\n\u001b[31m  const curMonth = new Date().toISOString().slice(0, 7); // YYYY-MM\u001b[39m\n\u001b[31m  const todayIST = (() => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; })();·\u001b[39m\n\u001b[31m  // Reset sections to loading skeletons before fetching (makes refresh visible)\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  [['kpi-actions',3],['kpi-ops',4],['kpi-prod',3],['kpi-inst',3],['kpi-hr',3]].forEach(([id,n]) => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    if (el) el.innerHTML = skel.repeat(n);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  const [\u001b[39m\n\u001b[31m    sheetsRes, claimsRes, salaryRes,\u001b[39m\n\u001b[31m    cashRes,   dispRes,   recceRes,  prodRes,\u001b[39m\n\u001b[31m    instJobsRes, instCtrsRes, storeItemsRes,\u001b[39m\n\u001b[31m    hrAttRes, hrEmpRes, hrConcernRes, hrSlipsRes\u001b[39m\n\u001b[31m  ] = await Promise.all([\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/sheets?status=in.(submitted,escalated)&select=sheet_id`, { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/approved_pending_reimbursement?select=sheet_id`,         { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_register?status=in.(pending,partial)&month=eq.${curMonth}&select=record_id`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/accounts?account_id=eq.petty-cash&select=current_balance`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/dispatches?status=eq.active&select=dispatch_id`,         { headers: _hdr('dispatch') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/submissions?submitted_at=gte.${mon}&select=sub_id`,      { headers: _hdr('recce') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/orders?status=not.in.(dispatched)&select=order_id,status`, { headers: _hdr('production') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/jobs?select=job_id,status`,                              { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/counters?select=counter_id,status`,                      { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/items?active=eq.true&select=id,current_stock,min_stock`, { headers: _hdr('stores') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance?date=eq.${todayIST}&status=eq.present&select=attendance_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/employee_list?select=id`,                               { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance_concerns?status=eq.open&select=concern_id`,  { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_slips?month=eq.${curMonth}&status=eq.finalized&select=slip_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m  ]);·\u001b[39m\n\u001b[31m  const sheets    = sheetsRes?.ok    ? await sheetsRes.json()    : [];\u001b[39m\n\u001b[31m  const claims    = claimsRes?.ok    ? await claimsRes.json()    : [];\u001b[39m\n\u001b[31m  const salary    = salaryRes?.ok    ? await salaryRes.json()    : [];\u001b[39m\n\u001b[31m  const cashArr   = cashRes?.ok      ? await cashRes.json()      : [];\u001b[39m\n\u001b[31m  const disps     = dispRes?.ok      ? await dispRes.json()      : [];\u001b[39m\n\u001b[31m  const recces    = recceRes?.ok     ? await recceRes.json()     : [];\u001b[39m\n\u001b[31m  const prods     = prodRes?.ok      ? await prodRes.json()      : [];\u001b[39m\n\u001b[31m  const instJobs  = instJobsRes?.ok  ? await instJobsRes.json()  : [];\u001b[39m\n\u001b[31m  const instCtrs  = instCtrsRes?.ok  ? await instCtrsRes.json()  : [];\u001b[39m\n\u001b[31m  const storeItems = storeItemsRes?.ok ? await storeItemsRes.json() : [];\u001b[39m\n\u001b[31m  const hrAtt     = hrAttRes?.ok     ? await hrAttRes.json()     : [];\u001b[39m\n\u001b[31m  const hrEmp     = hrEmpRes?.ok     ? await hrEmpRes.json()     : [];\u001b[39m\n\u001b[31m  const hrConcern = hrConcernRes?.ok ? await hrConcernRes.json() : [];\u001b[39m\n\u001b[31m  const hrSlips   = hrSlipsRes?.ok   ? await hrSlipsRes.json()   : [];·\u001b[39m\n\u001b[31m  const cash = Number(cashArr[0]?.current_balance || 0);·\u001b[39m\n\u001b[31m  const actionsEl = document.getElementById('kpi-actions');\u001b[39m\n\u001b[31m  const opsEl     = document.getElementById('kpi-ops');\u001b[39m\n\u001b[31m  const prodEl    = document.getElementById('kpi-prod');\u001b[39m\n\u001b[31m  const instEl    = document.getElementById('kpi-inst');\u001b[39m\n\u001b[31m  const hrEl      = document.getElementById('kpi-hr');\u001b[39m\n\u001b[31m  if (!actionsEl || !opsEl || !prodEl || !instEl) return;·\u001b[39m\n\u001b[31m  // ── Actions Required ────────────────────────────────────────────\u001b[39m\n\u001b[31m  actionsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${sheets.length ? 'alert' : 'ok'}\\\" data-href=\\\"/expense/?tab=approvals\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${sheets.length ? 'red' : 'green'}\\\">${sheets.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Expense Approvals</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${claims.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=claims\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${claims.length ? 'amber' : 'green'}\\\">${claims.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Claims to Reimburse</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${salary.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=salary\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${salary.length ? 'amber' : 'green'}\\\">${salary.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Salary Due This Month</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Operations ──────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const cashColor = cash < 2000 ? 'red' : cash < 5000 ? 'amber' : 'green';\u001b[39m\n\u001b[31m  const cashCard  = cash < 2000 ? 'alert' : cash < 5000 ? 'warn' : 'ok';\u001b[39m\n\u001b[31m  const lowStock = storeItems.filter(i => i.current_stock < i.min_stock).length;\u001b[39m\n\u001b[31m  opsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${cashCard}\\\" data-href=\\\"/finance/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${cashColor}\\\">${fmtINR(cash)}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">${cash < 0 ? '⚠️ ' : ''}Petty Cash</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/dispatch/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${disps.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Dispatches</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/recce/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${recces.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Recce This Month</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${lowStock > 0 ? 'alert' : 'ok'}\\\" data-href=\\\"/stores/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${lowStock > 0 ? 'red' : 'green'}\\\">${lowStock}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Low Stock Items</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Production ───────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const prodPending = prods.filter(o => o.status === 'pending').length;\u001b[39m\n\u001b[31m  const prodActive  = prods.filter(o => o.status === 'in_production' || o.status === 'quality_check').length;\u001b[39m\n\u001b[31m  const prodReady   = prods.filter(o => o.status === 'ready').length;\u001b[39m\n\u001b[31m  prodEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodPending ? 'warn' : 'ok'}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodPending ? 'amber' : 'green'}\\\">${prodPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Pending Orders</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${prodActive}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">In Production</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodReady ? 'ok' : ''}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodReady ? 'green' : ''}\\\">${prodReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Ready to Dispatch</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Installation ─────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const activeJobs  = instJobs.filter(j => j.status === 'active').length;\u001b[39m\n\u001b[31m  const ctrsPending = instCtrs.filter(c => c.status === 'recce_pending' || c.status === 'mockup_pending' || c.status === 'unclear').length;\u001b[39m\n\u001b[31m  const ctrsReady   = instCtrs.filter(c => c.status === 'ready').length;\u001b[39m\n\u001b[31m  instEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${activeJobs ? 'info' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${activeJobs ? 'blue' : 'green'}\\\">${activeJobs}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Jobs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsPending ? 'warn' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsPending ? 'amber' : 'green'}\\\">${ctrsPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Pending</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsReady ? 'ok' : ''}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsReady ? 'green' : ''}\\\">${ctrsReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Ready</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── HR ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31m  if (hrEl) {\u001b[39m\n\u001b[31m    const totalEmp   = hrEmp.length || 0;\u001b[39m\n\u001b[31m    const presentCnt = hrAtt.length;\u001b[39m\n\u001b[31m    const attPct     = totalEmp > 0 ? Math.round(presentCnt / totalEmp * 100) : 0;\u001b[39m\n\u001b[31m    const attColor   = attPct >= 80 ? 'green' : attPct >= 50 ? 'amber' : 'red';\u001b[39m\n\u001b[31m    const attCard    = attPct >= 80 ? 'ok' : attPct >= 50 ? 'warn' : 'alert';\u001b[39m\n\u001b[31m    const concernCnt = hrConcern.length;\u001b[39m\n\u001b[31m    const slipsDue   = hrSlips.length;\u001b[39m\n\u001b[31m    hrEl.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${attCard}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${attColor}\\\">${attPct}%</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Attendance Today</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${concernCnt ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${concernCnt ? 'amber' : 'green'}\\\">${concernCnt}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Open Concerns</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${slipsDue ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${slipsDue ? 'amber' : 'green'}\\\">${slipsDue}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Slips to Pay</div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Wire tap-to-navigate on all KPI cards\u001b[39m\n\u001b[31m  document.querySelectorAll('.kpi-card[data-href]').forEach(card => {\u001b[39m\n\u001b[31m    card.addEventListener('click', () => window.location.href = card.dataset.href);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction firstOfMonth() {\u001b[39m\n\u001b[31m  const d = new Date();\u001b[39m\n\u001b[31m  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGOUT ── */\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  pinBuffer   = '';\u001b[39m\n\u001b[31m  pendingShare = false;\u001b[39m\n\u001b[31m  _loginAttempts = 0; _lockoutUntil = 0; _loginInFlight = false;\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  document.getElementById('login-name').value = '';\u001b[39m\n\u001b[31m  document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m  showScreen('login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── TEAM PINs ── */\u001b[39m\n\u001b[31masync function enterTeamPins() {\u001b[39m\n\u001b[31m  showScreen('team');\u001b[39m\n\u001b[31m  await loadTeamPins();\u001b[39m\n\u001b[31m  // Show icon admin section for harish/pramod\u001b[39m\n\u001b[31m  const sec = document.getElementById('icon-admin-section');\u001b[39m\n\u001b[31m  if (sec) {\u001b[39m\n\u001b[31m    sec.style.display = MGMT.includes(currentUser.id) ? 'block' : 'none';\u001b[39m\n\u001b[31m    if (MGMT.includes(currentUser.id)) refreshSecretDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── ICON ADMIN ────────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst ICON_SECRET_KEY    = 'lm360_icon_secret';\u001b[39m\n\u001b[31mconst ICON_SECRET_INITIAL = 'nlr8Zsqw6XsD0rEXPQ_RfpYiOeNhH-9VglYKoIsElHE';·\u001b[39m\n\u001b[31mfunction getIconSecret() {\u001b[39m\n\u001b[31m  return localStorage.getItem(ICON_SECRET_KEY) || ICON_SECRET_INITIAL;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction setIconSecret(s) {\u001b[39m\n\u001b[31m  localStorage.setItem(ICON_SECRET_KEY, s);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet secretVisible = false;\u001b[39m\n\u001b[31mfunction refreshSecretDisplay() {\u001b[39m\n\u001b[31m  const el = document.getElementById('icon-secret-display');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  const s = getIconSecret();\u001b[39m\n\u001b[31m  el.textContent = secretVisible ? s : s.slice(0,4) + '••••••••••••••••' + s.slice(-4);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleSecretVisible() {\u001b[39m\n\u001b[31m  secretVisible = !secretVisible;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-secret-show');\u001b[39m\n\u001b[31m  if (btn) btn.textContent = secretVisible ? 'Hide' : 'Show';\u001b[39m\n\u001b[31m  refreshSecretDisplay();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function changeIconSecret() {\u001b[39m\n\u001b[31m  const newSecret = document.getElementById('icon-new-secret').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-secret-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (newSecret.length < 16) { errEl.textContent = 'Minimum 16 characters'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/change-secret', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret(), 'X-New-Secret': newSecret }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Failed');\u001b[39m\n\u001b[31m    setIconSecret(newSecret);\u001b[39m\n\u001b[31m    document.getElementById('icon-new-secret').value = '';\u001b[39m\n\u001b[31m    refreshSecretDisplay();\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = '✓ Secret updated';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mdocument.addEventListener('DOMContentLoaded', () => {\u001b[39m\n\u001b[31m  const fileInput = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  if (fileInput) {\u001b[39m\n\u001b[31m    fileInput.addEventListener('change', function() {\u001b[39m\n\u001b[31m      const wrap = document.getElementById('icon-preview-wrap');\u001b[39m\n\u001b[31m      const img  = document.getElementById('icon-preview-img');\u001b[39m\n\u001b[31m      if (this.files[0]) { img.src = URL.createObjectURL(this.files[0]); wrap.style.display = 'block'; }\u001b[39m\n\u001b[31m      else { wrap.style.display = 'none'; }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function uploadIcon() {\u001b[39m\n\u001b[31m  const pwa   = document.getElementById('icon-pwa-select').value;\u001b[39m\n\u001b[31m  const size  = document.getElementById('icon-size-select').value;\u001b[39m\n\u001b[31m  const input = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-upload-err');\u001b[39m\n\u001b[31m  errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (!input.files[0]) { errEl.textContent = 'Choose an image file first'; return; }\u001b[39m\n\u001b[31m  const formData = new FormData();\u001b[39m\n\u001b[31m  formData.append('pwa', pwa);\u001b[39m\n\u001b[31m  formData.append('size', size);\u001b[39m\n\u001b[31m  formData.append('icon', input.files[0]);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret() },\u001b[39m\n\u001b[31m      body: formData\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Upload failed');\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = `✓ Icon updated for ${pwa} (${size}px). Clear browser cache / reinstall to see new icon.`;\u001b[39m\n\u001b[31m    input.value = '';\u001b[39m\n\u001b[31m    document.getElementById('icon-preview-wrap').style.display = 'none';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadTeamPins() {\u001b[39m\n\u001b[31m  const list = document.getElementById('team-list');\u001b[39m\n\u001b[31m  list.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    list.innerHTML = '';\u001b[39m\n\u001b[31m    emps.forEach(emp => {\u001b[39m\n\u001b[31m      const row = document.createElement('div');\u001b[39m\n\u001b[31m      row.className = 'emp-row';\u001b[39m\n\u001b[31m      row.innerHTML = `\u001b[39m\n\u001b[31m        <div style=\\\"min-width:0;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:15px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.name}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.role}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-secondary btn-sm\\\" data-id=\\\"${emp.id}\\\" data-name=\\\"${emp.name}\\\">Set PIN</button>`;\u001b[39m\n\u001b[31m      row.querySelector('button').addEventListener('click', () => openSetPin(emp.id, emp.name));\u001b[39m\n\u001b[31m      list.appendChild(row);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    list.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openSetPin(targetId, targetName) {\u001b[39m\n\u001b[31m  setPinTarget = targetId;\u001b[39m\n\u001b[31m  document.getElementById('setpin-target-name').textContent = targetName;\u001b[39m\n\u001b[31m  ['setpin-new','setpin-confirm','setpin-auth'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-setpin').classList.add('show');\u001b[39m\n\u001b[31m  setTimeout(() => document.getElementById('setpin-new').focus(), 50);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function doSetPin() {\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('setpin-new').value.trim();\u001b[39m\n\u001b[31m  const conf    = document.getElementById('setpin-confirm').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('setpin-auth').value.trim();\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('setpin-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  if (!newPin || !conf || !authPin) { errEl.textContent = 'All fields are required'; return; }\u001b[39m\n\u001b[31m  if (newPin !== conf)              { errEl.textContent = 'New PINs do not match'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))     { errEl.textContent = 'PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-setpin-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_admin_id:  currentUser.id,\u001b[39m\n\u001b[31m        p_admin_pin: authPin,\u001b[39m\n\u001b[31m        p_target_id: setPinTarget,\u001b[39m\n\u001b[31m        p_new_pin:   newPin,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const err = await resp.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Failed to set PIN';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m    toast('✅ PIN updated for ' + document.getElementById('setpin-target-name').textContent);\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Set PIN';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ═══════════════════════════════════════════\u001b[39m\n\u001b[31m   JOBS — Campaign + Tour management\u001b[39m\n\u001b[31m   Access: harish, pramod (campaigns + tours)\u001b[39m\n\u001b[31m           rakesh (tours only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ */\u001b[39m\n\u001b[31mconst INST_RD = { 'Accept-Profile': 'installation' };\u001b[39m\n\u001b[31mconst INST_WR = { 'Content-Type':'application/json','Accept-Profile':'installation','Content-Profile':'installation' };·\u001b[39m\n\u001b[31mlet _currentCampaignId = null;\u001b[39m\n\u001b[31mlet _allEmployees      = [];\u001b[39m\n\u001b[31mlet _editTourId        = null;  // null = create mode, string = edit mode·\u001b[39m\n\u001b[31m// ── Helpers ──────────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _slugify(s) {\u001b[39m\n\u001b[31m  return s.toUpperCase().trim()\u001b[39m\n\u001b[31m    .replace(/[^A-Z0-9]+/g, '_')\u001b[39m\n\u001b[31m    .replace(/^_|_$/g, '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _buildCampaignId(year, brand, client, campaign, phase) {\u001b[39m\n\u001b[31m  const nn = String(phase || 1).padStart(2, '0');\u001b[39m\n\u001b[31m  return `${year}-${_slugify(brand)}-${_slugify(client)}-${_slugify(campaign)}-${nn}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _campStatusBadge(s) {\u001b[39m\n\u001b[31m  const map = { active:'ok', completed:'', on_hold:'warn' };\u001b[39m\n\u001b[31m  const col = { active:'green', completed:'', on_hold:'amber' };\u001b[39m\n\u001b[31m  return `<span class=\\\"kpi-val ${col[s]||''}\\\" style=\\\"font-size:12px;font-weight:700\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _tourStatusBadge(s) {\u001b[39m\n\u001b[31m  const colors = { active:'var(--success)', planned:'var(--info)', completed:'var(--muted)', cancelled:'var(--danger)' };\u001b[39m\n\u001b[31m  return `<span style=\\\"font-size:11px;font-weight:700;padding:2px 8px;border-radius:10px;\u001b[39m\n\u001b[31m    background:var(--card);color:${colors[s]||'var(--muted)'}\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _canCreateCampaign() { return ['harish','pramod'].includes(currentUser?.id); }\u001b[39m\n\u001b[31mfunction _canCreateTour()     { return ['harish','pramod','rakesh'].includes(currentUser?.id); }·\u001b[39m\n\u001b[31m// ── Jobs screen ───────────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterJobs() {\u001b[39m\n\u001b[31m  showScreen('jobs');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').style.display = _canCreateCampaign() ? '' : 'none';\u001b[39m\n\u001b[31m  await loadJobs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadJobs() {\u001b[39m\n\u001b[31m  const el = document.getElementById('jobs-body');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?order=created_at.desc`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?select=tour_id,campaign_id,status`, { headers: INST_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const campaigns = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours     = tResp.ok ? await tResp.json() : [];·\u001b[39m\n\u001b[31m    if (!campaigns.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty\\\"><div style=\\\"font-size:32px\\\">📋</div><div>No campaigns yet</div></div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    el.innerHTML = campaigns.map(c => {\u001b[39m\n\u001b[31m      const cTours  = tours.filter(t => t.campaign_id === c.campaign_id);\u001b[39m\n\u001b[31m      const active  = cTours.filter(t => t.status === 'active').length;\u001b[39m\n\u001b[31m      const total   = cTours.length;\u001b[39m\n\u001b[31m      const statusColors = { active:'var(--success)', on_hold:'var(--accent2)', completed:'var(--muted)' };\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card tappable\\\" style=\\\"padding:14px\\\" onclick=\\\"enterCampaign('${c.campaign_id}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:flex-start;gap:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:15px;font-weight:700\\\">${c.brand} — ${c.campaign_name}\u001b[39m\n\u001b[31m                ${c.phase > 1 ? `<span style=\\\"font-size:11px;color:var(--muted)\\\"> Phase ${c.phase}</span>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:2px\\\">${c.client} · ${c.year}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:4px\\\">\u001b[39m\n\u001b[31m                ${total} tour${total!==1?'s':''} · ${active} active\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;align-items:flex-end;gap:6px\\\">\u001b[39m\n\u001b[31m              <span style=\\\"font-size:11px;font-weight:700;color:${statusColors[c.status]||'var(--muted)'}\\\">\u001b[39m\n\u001b[31m                ${(c.status||'').replace('_',' ')}\u001b[39m\n\u001b[31m              </span>\u001b[39m\n\u001b[31m              <span style=\\\"font-size:18px;color:var(--muted)\\\">›</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-top:8px;word-break:break-all\\\">${c.campaign_id}</div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign detail ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterCampaign(campaignId) {\u001b[39m\n\u001b[31m  _currentCampaignId = campaignId;\u001b[39m\n\u001b[31m  showScreen('campaign');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').style.display = _canCreateTour() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-sub').textContent   = '';\u001b[39m\n\u001b[31m  await loadCampaign(campaignId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCampaign(campaignId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('campaign-body');\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp, empResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(campaignId)}&order=start_date`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const camps = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours = tResp.ok ? await tResp.json() : [];\u001b[39m\n\u001b[31m    _allEmployees = empResp.ok ? await empResp.json() : [];·\u001b[39m\n\u001b[31m    if (!camps.length) { body.innerHTML = '<div class=\\\"empty\\\">Campaign not found</div>'; return; }\u001b[39m\n\u001b[31m    const c = camps[0];·\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-title').textContent = `${c.brand} — ${c.campaign_name}`;\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-sub').textContent   = `${c.client} · ${c.year}${c.phase>1?' · Phase '+c.phase:''}`;·\u001b[39m\n\u001b[31m    const empMap = {};\u001b[39m\n\u001b[31m    _allEmployees.forEach(e => empMap[e.id] = e.name);·\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"padding:12px 14px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Campaign ID</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:10px;color:var(--muted);word-break:break-all;text-align:right;max-width:65%\\\">${c.campaign_id}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Client</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:13px;font-weight:600\\\">${c.client}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${_canCreateCampaign() ? `\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div class=\\\"section-title\\\" style=\\\"margin-bottom:6px\\\">Campaign Status</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m            ${['active','on_hold','completed'].map(s =>\u001b[39m\n\u001b[31m              `<button class=\\\"status-chip${c.status===s?' sel':''}\\\" onclick=\\\"updateCampaignStatus('${c.campaign_id}','${s}')\\\">\u001b[39m\n\u001b[31m                ${s.replace('_',' ')}\u001b[39m\n\u001b[31m              </button>`).join('')}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>` : ''}\u001b[39m\n\u001b[31m      </div>·\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:4px\\\">Tours (${tours.length})</div>\u001b[39m\n\u001b[31m      ${tours.length === 0 ? `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\">\u001b[39m\n\u001b[31m        <div>No tours yet</div>\u001b[39m\n\u001b[31m        ${_canCreateTour() ? '<div style=\\\"font-size:12px\\\">Tap ＋ to add the first tour</div>' : ''}\u001b[39m\n\u001b[31m      </div>` :\u001b[39m\n\u001b[31m      tours.map(t => {\u001b[39m\n\u001b[31m        const empNames = (t.assigned_employees || []).map(id => empMap[id] || id).join(', ') || 'No one assigned';\u001b[39m\n\u001b[31m        const dates    = t.start_date ? `${t.start_date} → ${t.end_date||'?'}` : 'Dates not set';\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m          <div class=\\\"card\\\" style=\\\"padding:14px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;align-items:flex-start;gap:8px\\\">\u001b[39m\n\u001b[31m              <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m                <div style=\\\"font-size:14px;font-weight:700\\\">${t.title || t.tour_id}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">📅 ${dates}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">👥 ${empNames}</div>\u001b[39m\n\u001b[31m                ${t.notes ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">📝 ${t.notes}</div>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              ${_tourStatusBadge(t.status)}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            ${_canCreateTour() ? `\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;gap:6px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m              ${['planned','active','completed'].map(s =>\u001b[39m\n\u001b[31m                `<button class=\\\"status-chip${t.status===s?' sel':''}\\\"\u001b[39m\n\u001b[31m                  onclick=\\\"updateTourStatus('${t.tour_id}','${s}')\\\">${s}</button>`).join('')}\u001b[39m\n\u001b[31m              <button class=\\\"status-chip\\\" onclick=\\\"openEditTour('${t.tour_id}')\\\"\u001b[39m\n\u001b[31m                style=\\\"margin-left:auto;background:var(--surface);border-color:#3a3a6e;color:#8888ff\\\">\u001b[39m\n\u001b[31m                ✏️ Edit\u001b[39m\n\u001b[31m              </button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m            <div style=\\\"font-size:10px;color:var(--muted);margin-top:6px\\\">${t.tour_id}</div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m      <div style=\\\"height:20px\\\"></div>`;\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateTourStatus(tourId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Tour marked ${status}`);\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateCampaignStatus(campaignId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Campaign marked ${status.replace('_',' ')}`);\u001b[39m\n\u001b[31m    loadCampaign(campaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Campaign modal ────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewCampaign() {\u001b[39m\n\u001b[31m  if (!_canCreateCampaign()) { toast('Only Harish or Pramod can create campaigns'); return; }\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('nc-year').value    = new Date().getFullYear();\u001b[39m\n\u001b[31m  document.getElementById('nc-phase').value   = '1';\u001b[39m\n\u001b[31m  document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  document.getElementById('nc-err').textContent     = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-new-campaign').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateCampaignPreview() {\u001b[39m\n\u001b[31m  const year  = document.getElementById('nc-year').value;\u001b[39m\n\u001b[31m  const brand = document.getElementById('nc-brand').value;\u001b[39m\n\u001b[31m  const clt   = document.getElementById('nc-client').value;\u001b[39m\n\u001b[31m  const camp  = document.getElementById('nc-campaign').value;\u001b[39m\n\u001b[31m  const phase = document.getElementById('nc-phase').value;\u001b[39m\n\u001b[31m  if (brand && clt && camp) {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = _buildCampaignId(year, brand, clt, camp, phase);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewCampaign() {\u001b[39m\n\u001b[31m  const year   = Number(document.getElementById('nc-year').value);\u001b[39m\n\u001b[31m  const brand  = document.getElementById('nc-brand').value.trim();\u001b[39m\n\u001b[31m  const client = document.getElementById('nc-client').value.trim();\u001b[39m\n\u001b[31m  const cname  = document.getElementById('nc-campaign').value.trim();\u001b[39m\n\u001b[31m  const phase  = Number(document.getElementById('nc-phase').value) || 1;\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('nc-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const brandEl = document.getElementById('nc-brand');\u001b[39m\n\u001b[31m  const clientEl = document.getElementById('nc-client');\u001b[39m\n\u001b[31m  const cnameEl = document.getElementById('nc-campaign');\u001b[39m\n\u001b[31m  [brandEl, clientEl, cnameEl].forEach(el => el.style.borderColor = '');\u001b[39m\n\u001b[31m  let _missingField = false;\u001b[39m\n\u001b[31m  if (!brand)  { brandEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!client) { clientEl.style.borderColor = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!cname)  { cnameEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (_missingField) { errEl.textContent = 'Brand, Client and Campaign Name are required'; return; }\u001b[39m\n\u001b[31m  const campaignId = _buildCampaignId(year, brand, client, cname, phase);·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nc-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/campaigns`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ campaign_id:campaignId, year, brand, client, campaign_name:cname, phase, created_by:currentUser.id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || 'Failed to create';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show');\u001b[39m\n\u001b[31m    toast(`✅ Campaign created`);\u001b[39m\n\u001b[31m    loadJobs();\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Create'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Tour modal ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _buildEmpCheckboxes(assigned = []) {\u001b[39m\n\u001b[31m  return _allEmployees.map(e =>\u001b[39m\n\u001b[31m    `<label style=\\\"display:flex;align-items:center;gap:10px;padding:8px 10px;background:var(--card);\u001b[39m\n\u001b[31m      border:1px solid var(--border);border-radius:8px;cursor:pointer\\\">\u001b[39m\n\u001b[31m      <input type=\\\"checkbox\\\" value=\\\"${e.id}\\\" ${assigned.includes(e.id)?'checked':''} style=\\\"width:16px;height:16px;accent-color:var(--accent)\\\">\u001b[39m\n\u001b[31m      <span style=\\\"font-size:14px;font-weight:600\\\">${e.name}</span>\u001b[39m\n\u001b[31m      <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${e.role}</span>\u001b[39m\n\u001b[31m    </label>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openNewTour() {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = null;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '🗺️ New Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Create Tour';\u001b[39m\n\u001b[31m  document.getElementById('nt-title').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-start').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-end').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-notes').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(_currentCampaignId)}&select=tour_id`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const existing = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    const nn = String(existing.length + 1).padStart(2, '0');\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T${nn}`;\u001b[39m\n\u001b[31m  } catch(_) {\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T01`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (_allEmployees.length)\u001b[39m\n\u001b[31m    document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes();·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEditTour(tourId) {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = tourId;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '✏️ Edit Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Save Changes';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const tours = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    if (!tours.length) { toast('Tour not found'); return; }\u001b[39m\n\u001b[31m    const t = tours[0];\u001b[39m\n\u001b[31m    document.getElementById('nt-title').value = t.title || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-start').value = t.start_date || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-end').value   = t.end_date   || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-notes').value = t.notes      || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = tourId;\u001b[39m\n\u001b[31m    if (_allEmployees.length)\u001b[39m\n\u001b[31m      document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes(t.assigned_employees || []);\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to load tour'); return; }·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewTour() {\u001b[39m\n\u001b[31m  const title = document.getElementById('nt-title').value.trim();\u001b[39m\n\u001b[31m  const start = document.getElementById('nt-start').value || null;\u001b[39m\n\u001b[31m  const end   = document.getElementById('nt-end').value   || null;\u001b[39m\n\u001b[31m  const notes = document.getElementById('nt-notes').value.trim() || null;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('nt-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const checked = [...document.querySelectorAll('#nt-employees input:checked')].map(cb => cb.value);\u001b[39m\n\u001b[31m  if (!checked.length) { errEl.textContent = 'Assign at least one employee'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nt-save');\u001b[39m\n\u001b[31m  const wasEdit = !!_editTourId;\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = wasEdit ? 'Saving…' : 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editTourId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(_editTourId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      const tourId = document.getElementById('nt-id-preview').textContent;\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          tour_id: tourId, campaign_id: _currentCampaignId,\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes, status: 'planned',\u001b[39m\n\u001b[31m          created_by: currentUser.id,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || (_editTourId ? 'Failed to save' : 'Failed to create');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show');\u001b[39m\n\u001b[31m    toast(_editTourId ? '✅ Tour updated' : '✅ Tour created');\u001b[39m\n\u001b[31m    _editTourId = null;\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = wasEdit ? 'Save Changes' : 'Create Tour';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── EVENT WIRING ── */\u001b[39m\n\u001b[31mfunction wireEvents() {\u001b[39m\n\u001b[31m  // Jobs screens\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-back').addEventListener('click',     () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-refresh').addEventListener('click',  loadJobs);\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').addEventListener('click',  openNewCampaign);\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-back').addEventListener('click', () => showScreen('jobs'));\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-refresh').addEventListener('click', () => loadCampaign(_currentCampaignId));\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').addEventListener('click',      openNewTour);·\u001b[39m\n\u001b[31m  // New Campaign modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-save').addEventListener('click', saveNewCampaign);\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign','nc-year','nc-phase'].forEach(id =>\u001b[39m\n\u001b[31m    document.getElementById(id).addEventListener('input', updateCampaignPreview));·\u001b[39m\n\u001b[31m  // New Tour modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').addEventListener('click', saveNewTour);·\u001b[39m\n\u001b[31m  document.getElementById('btn-team-back').addEventListener('click', () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-team-refresh').addEventListener('click', loadTeamPins);\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-cancel').addEventListener('click', () => {\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-save').addEventListener('click', doSetPin);·\u001b[39m\n\u001b[31m  // Set PIN — progressive field reveal\u001b[39m\n\u001b[31m  document.getElementById('setpin-new').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const val = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const showConf = val.length === 4;\u001b[39m\n\u001b[31m    document.getElementById('setpin-confirm-group').style.display = showConf ? '' : 'none';\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m    if (!showConf) { document.getElementById('setpin-confirm').value = ''; document.getElementById('setpin-auth').value = ''; }\u001b[39m\n\u001b[31m    if (showConf) setTimeout(() => document.getElementById('setpin-confirm').focus(), 20);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const newVal = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const conf   = document.getElementById('setpin-confirm').value;\u001b[39m\n\u001b[31m    const showAuth = conf.length === 4 && conf === newVal;\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = showAuth ? '' : 'none';\u001b[39m\n\u001b[31m    if (!showAuth) document.getElementById('setpin-auth').value = '';\u001b[39m\n\u001b[31m    if (showAuth) setTimeout(() => document.getElementById('setpin-auth').focus(), 20);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // Hub AI config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-save').addEventListener('click',   saveHubAiConfig);\u001b[39m\n\u001b[31m  document.getElementById('btn-check-keys').addEventListener('click',    checkApiKeyStatus);·\u001b[39m\n\u001b[31m  // Hub QA config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-qa').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-save').addEventListener('click', saveHubQaConfig);·\u001b[39m\n\u001b[31m  // Share picker\u001b[39m\n\u001b[31m  document.getElementById('share-to-expense').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/expense/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-recce').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/recce/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-installation').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/installation/current/?share=1';\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-04: employee dropdown change resets PIN buffer and lockout\u001b[39m\n\u001b[31m  document.getElementById('login-name').addEventListener('change', () => {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-07: ESC key closes any open modal\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (e.key === 'Escape') {\u001b[39m\n\u001b[31m      document.querySelectorAll('.modal-overlay.show').forEach(m => m.classList.remove('show'));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-11: keyboard digit/backspace input on login screen\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (!document.getElementById('screen-login').classList.contains('active')) return;\u001b[39m\n\u001b[31m    if (e.key >= '0' && e.key <= '9') { handlePinKey(e.key); }\u001b[39m\n\u001b[31m    else if (e.key === 'Backspace') { e.preventDefault(); handlePinKey('B'); }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-02/B-08: re-check session TTL when user returns to the tab\u001b[39m\n\u001b[31m  document.addEventListener('visibilitychange', () => {\u001b[39m\n\u001b[31m    if (document.visibilityState === 'visible' && currentUser) {\u001b[39m\n\u001b[31m      if (!loadSession()) doLogout();\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── BOOT ── */\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  wireEvents();·\u001b[39m\n\u001b[31m  if (location.search.includes('share=1')) {\u001b[39m\n\u001b[31m    pendingShare = true;\u001b[39m\n\u001b[31m    history.replaceState({}, '', '/hub/');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Always populate the login dropdown (needed if user logs out later)\u001b[39m\n\u001b[31m  initLogin(); // fire-and-forget — does not block home screen path·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess) {\u001b[39m\n\u001b[31m    currentUser = { id: sess.empId, name: sess.name, role: sess.role };\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  setTimeout(() => showScreen('login'), 1500);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SERVICE WORKER: install + update ── */\u001b[39m\n\u001b[31mif ('serviceWorker' in navigator) {\u001b[39m\n\u001b[31m  navigator.serviceWorker.register('/hub/sw.js').then(reg => {·\u001b[39m\n\u001b[31m    // Install banner: show when browser signals app is installable\u001b[39m\n\u001b[31m    let deferredInstall = null;\u001b[39m\n\u001b[31m    window.addEventListener('beforeinstallprompt', e => {\u001b[39m\n\u001b[31m      e.preventDefault();\u001b[39m\n\u001b[31m      deferredInstall = e;\u001b[39m\n\u001b[31m      if (!window.matchMedia('(display-mode: standalone)').matches) {\u001b[39m\n\u001b[31m        const delay = document.getElementById('update-banner').classList.contains('show') ? 3500 : 0;\u001b[39m\n\u001b[31m        setTimeout(() => document.getElementById('install-banner').classList.add('show'), delay);\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    window.addEventListener('appinstalled', () => {\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install').addEventListener('click', async () => {\u001b[39m\n\u001b[31m      if (!deferredInstall) return;\u001b[39m\n\u001b[31m      deferredInstall.prompt();\u001b[39m\n\u001b[31m      await deferredInstall.userChoice;\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m    // Update banner: show when a new SW version is waiting to activate\u001b[39m\n\u001b[31m    function showUpdateBanner() {\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.add('show');\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (reg.waiting) showUpdateBanner();·\u001b[39m\n\u001b[31m    reg.addEventListener('updatefound', () => {\u001b[39m\n\u001b[31m      const newSW = reg.installing;\u001b[39m\n\u001b[31m      newSW.addEventListener('statechange', () => {\u001b[39m\n\u001b[31m        if (newSW.state === 'installed' && navigator.serviceWorker.controller) {\u001b[39m\n\u001b[31m          showUpdateBanner();\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    document.getElementById('btn-update').addEventListener('click', () => {\u001b[39m\n\u001b[31m      if (reg.waiting) {\u001b[39m\n\u001b[31m        reg.waiting.postMessage({ type: 'SKIP_WAITING' });\u001b[39m\n\u001b[31m        navigator.serviceWorker.addEventListener('controllerchange', () => {\u001b[39m\n\u001b[31m          window.location.reload();\u001b[39m\n\u001b[31m        }, { once: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-update-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m  }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mboot();\u001b[39m\n\u001b[31m</script>··\u001b[39m\n\u001b[31m</body></html>\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"recce-v10\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html><html lang=\\\"en\\\"><head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#FF6B35\\\">\u001b[39m\n\u001b[31m<meta name=\\\"mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-status-bar-style\\\" content=\\\"black-translucent\\\">\u001b[39m\n\u001b[31m<title>360LM</title>\u001b[39m\n\u001b[31m<meta name=\\\"description\\\" content=\\\"360LM Operations Hub — employee tools\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/hub/manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"icon\\\" href=\\\"/hub/icons/icon-192.png\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--surface:#161616;--card:#1e1e1e;--border:#2a2a2a;\u001b[39m\n\u001b[31m  --accent:#FF6B35;--accent2:#FFB800;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --danger:#e74c3c;--success:#2ecc71;--info:#3498db;\u001b[39m\n\u001b[31m  --radius:12px;--font:'Segoe UI',system-ui,sans-serif;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:var(--font);min-height:100vh;overflow-x:hidden}\u001b[39m\n\u001b[31m.screen{display:none;min-height:100vh;flex-direction:column}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── HEADER ── */\u001b[39m\n\u001b[31m.header{background:var(--surface);border-bottom:1px solid var(--border);padding:14px 16px;\u001b[39m\n\u001b[31m  display:flex;align-items:center;gap:12px;position:sticky;top:0;z-index:10;flex-shrink:0}\u001b[39m\n\u001b[31m.header h1{font-size:18px;font-weight:700;color:var(--accent);flex:1}\u001b[39m\n\u001b[31m.header .sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.btn-icon{background:none;border:none;color:var(--muted);font-size:22px;cursor:pointer;\u001b[39m\n\u001b[31m  padding:4px;border-radius:8px;line-height:1;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m.btn-icon:active{background:var(--border)}·\u001b[39m\n\u001b[31m/* ── BODY ── */\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:16px}·\u001b[39m\n\u001b[31m/* ── SPLASH ── */\u001b[39m\n\u001b[31m#screen-splash{align-items:center;justify-content:center;gap:8px}\u001b[39m\n\u001b[31m.splash-logo{font-size:72px;font-weight:900;color:var(--accent);letter-spacing:-2px;line-height:1}\u001b[39m\n\u001b[31m.splash-sub{font-size:15px;color:var(--muted);letter-spacing:2px;text-transform:uppercase}\u001b[39m\n\u001b[31m.splash-dot{width:8px;height:8px;border-radius:50%;background:var(--accent);\u001b[39m\n\u001b[31m  animation:pulse 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes pulse{0%,100%{opacity:.3;transform:scale(.8)}50%{opacity:1;transform:scale(1)}}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  min-height:100dvh;padding:24px;gap:20px;overflow-y:auto}\u001b[39m\n\u001b[31m.login-logo{font-size:44px;font-weight:900;color:var(--accent);letter-spacing:-1px}\u001b[39m\n\u001b[31m.login-sub{font-size:13px;color:var(--muted);text-transform:uppercase;letter-spacing:2px}\u001b[39m\n\u001b[31m.form-group{width:100%;max-width:360px;display:flex;flex-direction:column;gap:8px}\u001b[39m\n\u001b[31m.form-group label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px}\u001b[39m\n\u001b[31mselect{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31mselect:focus{border-color:var(--accent)}·\u001b[39m\n\u001b[31m/* ── PIN PAD ── */\u001b[39m\n\u001b[31m.pin-display{display:flex;gap:14px;justify-content:center;margin:4px 0}\u001b[39m\n\u001b[31m.pin-dot{width:16px;height:16px;border-radius:50%;border:2px solid var(--muted);background:transparent;transition:.15s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.pin-pad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;max-width:260px;margin:0 auto;width:100%}\u001b[39m\n\u001b[31m.pin-key{background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  font-size:22px;font-weight:600;padding:16px;border-radius:var(--radius);cursor:pointer;\u001b[39m\n\u001b[31m  text-align:center;line-height:1;user-select:none}\u001b[39m\n\u001b[31m.pin-key:active{background:var(--border)}\u001b[39m\n\u001b[31m.pin-key.clr{color:var(--danger);font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.pin-key.back{color:var(--accent2)}\u001b[39m\n\u001b[31m.login-err{font-size:13px;color:var(--danger);text-align:center;min-height:18px}\u001b[39m\n\u001b[31m@keyframes shake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-6px)}40%,80%{transform:translateX(6px)}}\u001b[39m\n\u001b[31m.pin-display.shake{animation:shake .4s ease}·\u001b[39m\n\u001b[31m/* ── KPI CARDS ── */\u001b[39m\n\u001b[31m.kpi-row{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\u001b[39m\n\u001b[31m.kpi-4col{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m#home-body{gap:10px}\u001b[39m\n\u001b[31m.kpi-card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:10px 8px;text-align:center;border-left:3px solid var(--accent);cursor:default}\u001b[39m\n\u001b[31m.kpi-card.alert{border-left-color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-card.warn{border-left-color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-card.ok{border-left-color:var(--success)}\u001b[39m\n\u001b[31m.kpi-card.info{border-left-color:var(--info)}\u001b[39m\n\u001b[31m.kpi-val{font-size:18px;font-weight:900;color:var(--accent);line-height:1.2}\u001b[39m\n\u001b[31m.kpi-val.red{color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-val.amber{color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-val.green{color:var(--success)}\u001b[39m\n\u001b[31m.kpi-val.blue{color:var(--info)}\u001b[39m\n\u001b[31m.kpi-lbl{font-size:10px;color:var(--muted);margin-top:4px;text-transform:uppercase;letter-spacing:.4px;line-height:1.3}\u001b[39m\n\u001b[31m.kpi-skel{background:var(--border);border-radius:4px;height:24px;margin-bottom:4px;\u001b[39m\n\u001b[31m  animation:shimmer 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes shimmer{0%,100%{opacity:.5}50%{opacity:1}}·\u001b[39m\n\u001b[31m/* ── SECTION TITLE ── */\u001b[39m\n\u001b[31m.section-title{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.8px}·\u001b[39m\n\u001b[31m/* ── MGMT HOME: MAIN TILES ── */\u001b[39m\n\u001b[31m.hub-main-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px}\u001b[39m\n\u001b[31m.hub-main-tile{background:var(--card);border:2px solid var(--border);border-radius:18px;\u001b[39m\n\u001b[31m  padding:36px 12px 28px;text-align:center;cursor:pointer;transition:background .15s,border-color .15s}\u001b[39m\n\u001b[31m.hub-main-tile:active{background:#1a2a1a;border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-main-icon{font-size:40px;line-height:1;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-main-label{font-size:15px;font-weight:800;color:var(--text)}\u001b[39m\n\u001b[31m/* ── MGMT HOME: QUICK ACTIONS ── */\u001b[39m\n\u001b[31m.hub-qa-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-qa-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(88px,1fr));gap:10px}\u001b[39m\n\u001b[31m.hub-qa-tile{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px 8px;text-align:center;cursor:pointer;display:flex;flex-direction:column;\u001b[39m\n\u001b[31m  align-items:center;gap:6px;transition:background .15s}\u001b[39m\n\u001b[31m.hub-qa-tile:active{background:var(--border)}\u001b[39m\n\u001b[31m.hub-qa-icon{font-size:26px;line-height:1}\u001b[39m\n\u001b[31m.hub-qa-label{font-size:11px;font-weight:700;color:var(--text);line-height:1.3}\u001b[39m\n\u001b[31m.hub-qa-item{display:flex;align-items:center;gap:12px;padding:10px 0;\u001b[39m\n\u001b[31m  border-bottom:1px solid var(--border);cursor:pointer}\u001b[39m\n\u001b[31m.hub-qa-item:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.hub-qa-item-icon{font-size:22px;width:32px;text-align:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item-label{flex:1;font-size:14px;font-weight:600}\u001b[39m\n\u001b[31m.hub-qa-item-toggle{width:20px;height:20px;border-radius:4px;border:2px solid var(--border);\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle::after{content:'✓';color:#fff;font-weight:700;font-size:12px}·\u001b[39m\n\u001b[31m/* ── TILE GRID ── */\u001b[39m\n\u001b[31m.tile-grid{display:grid;gap:12px}\u001b[39m\n\u001b[31m.tile-grid.mgmt{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m.tile-grid.field{grid-template-columns:repeat(2,1fr)}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:18px 8px;display:flex;flex-direction:column;align-items:center;gap:8px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s;position:relative}\u001b[39m\n\u001b[31m.tile:active{background:var(--border);transform:scale(.97)}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px;line-height:1}\u001b[39m\n\u001b[31m.tile-label{font-size:11px;font-weight:700;color:var(--text);text-align:center;text-transform:uppercase;letter-spacing:.4px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-icon{font-size:24px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-label{font-size:10px}\u001b[39m\n\u001b[31m.tile.coming{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.tile.coming:active{transform:none;background:var(--card)}\u001b[39m\n\u001b[31m.soon-badge{position:absolute;top:6px;right:6px;background:var(--accent2);color:#000;\u001b[39m\n\u001b[31m  font-size:8px;font-weight:800;padding:2px 5px;border-radius:20px;text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── USER BADGE ── */\u001b[39m\n\u001b[31m.user-badge{display:flex;align-items:center;gap:12px}\u001b[39m\n\u001b[31m.avatar{width:44px;height:44px;background:var(--accent);border-radius:50%;\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:800;\u001b[39m\n\u001b[31m  color:#fff;flex-shrink:0;text-transform:uppercase}\u001b[39m\n\u001b[31m.user-name{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.user-role{font-size:12px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── TEAM PINS ── */\u001b[39m\n\u001b[31m.emp-row{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px}\u001b[39m\n\u001b[31m.btn{display:flex;align-items:center;justify-content:center;gap:6px;border:none;\u001b[39m\n\u001b[31m  border-radius:var(--radius);font-size:15px;font-weight:700;padding:12px 18px;\u001b[39m\n\u001b[31m  cursor:pointer;width:100%;transition:.15s;font-family:var(--font)}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--card);border:1px solid var(--border);color:var(--text)}\u001b[39m\n\u001b[31m.btn-sm{padding:7px 12px;font-size:12px;font-weight:700;width:auto;flex-shrink:0}\u001b[39m\n\u001b[31m.info-note{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;font-size:13px;color:var(--muted);line-height:1.5}·\u001b[39m\n\u001b[31m/* ── MODAL ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:200;\u001b[39m\n\u001b[31m  align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;\u001b[39m\n\u001b[31m  padding:24px;width:100%;max-width:480px;display:flex;flex-direction:column;gap:14px;\u001b[39m\n\u001b[31m  max-height:90dvh;overflow-y:auto;-webkit-overflow-scrolling:touch}\u001b[39m\n\u001b[31m.modal-title{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.modal-box input[type=password]{\u001b[39m\n\u001b[31m  width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31m.modal-box input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.action-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px}·\u001b[39m\n\u001b[31m/* ── SHARE PICKER ── */\u001b[39m\n\u001b[31m#screen-share .body{align-items:center;justify-content:center;gap:24px}\u001b[39m\n\u001b[31m.share-prompt{font-size:15px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.share-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;width:100%;max-width:360px}\u001b[39m\n\u001b[31m.share-tile{background:var(--card);border:2px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:24px 8px;display:flex;flex-direction:column;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s}\u001b[39m\n\u001b[31m.share-tile:active{background:var(--border);transform:scale(.97);border-color:var(--accent)}\u001b[39m\n\u001b[31m.share-tile-icon{font-size:36px;line-height:1}\u001b[39m\n\u001b[31m.share-tile-label{font-size:12px;font-weight:700;color:var(--text);text-align:center;\u001b[39m\n\u001b[31m  text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── INSTALL BANNER ── */\u001b[39m\n\u001b[31m#install-banner{display:none;position:fixed;bottom:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:linear-gradient(135deg,#1a1a1a,#0d2010);\u001b[39m\n\u001b[31m  border-top:1px solid var(--accent);padding:14px 16px;\u001b[39m\n\u001b[31m  align-items:center;gap:12px;box-shadow:0 -4px 24px rgba(0,0,0,.7)}\u001b[39m\n\u001b[31m#install-banner.show{display:flex}\u001b[39m\n\u001b[31m#install-banner .ib-icon{font-size:30px;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-text{flex:1}\u001b[39m\n\u001b[31m#install-banner .ib-title{font-size:14px;font-weight:700;color:var(--text)}\u001b[39m\n\u001b[31m#install-banner .ib-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#install-banner .ib-btn{background:var(--accent);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-close{background:none;border:none;color:var(--muted);font-size:20px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── UPDATE BANNER ── */\u001b[39m\n\u001b[31m#update-banner{display:none;position:fixed;top:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:#1a2a1a;border-bottom:2px solid var(--success);\u001b[39m\n\u001b[31m  padding:12px 16px;align-items:center;gap:12px}\u001b[39m\n\u001b[31m#update-banner.show{display:flex}\u001b[39m\n\u001b[31m#update-banner .ub-text{flex:1;font-size:14px;font-weight:600;color:var(--text)}\u001b[39m\n\u001b[31m#update-banner .ub-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#update-banner .ub-btn{background:var(--success);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:8px 14px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#update-banner .ub-close{background:none;border:none;color:var(--muted);font-size:18px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#222;color:#fff;padding:10px 20px;border-radius:20px;font-size:14px;font-weight:600;\u001b[39m\n\u001b[31m  opacity:0;transition:.3s;pointer-events:none;white-space:nowrap;z-index:500}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  padding:40px;color:var(--muted);font-size:14px;gap:12px}\u001b[39m\n\u001b[31m.spinner{width:36px;height:36px;border:3px solid var(--border);border-top-color:var(--accent);\u001b[39m\n\u001b[31m  border-radius:50%;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* ── CARD (jobs / campaign screens) ── */\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius)}\u001b[39m\n\u001b[31m.card.tappable:active{border-color:var(--accent);opacity:.85}·\u001b[39m\n\u001b[31m/* ── STATUS CHIPS (tour / campaign) ── */\u001b[39m\n\u001b[31m.status-chip{font-size:11px;padding:4px 11px;border-radius:8px;cursor:pointer;\u001b[39m\n\u001b[31m  background:var(--card);color:var(--text);border:1px solid var(--border);font-weight:600;\u001b[39m\n\u001b[31m  font-family:var(--font);transition:.15s}\u001b[39m\n\u001b[31m.status-chip.sel{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.status-chip:active{opacity:.8}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SPLASH\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-splash\\\" class=\\\"screen active\\\">\u001b[39m\n\u001b[31m  <div class=\\\"splash-logo\\\">360LM</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-sub\\\">Operations Hub</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-dot\\\" style=\\\"margin-top:16px\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     LOGIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-login\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-wrap\\\">\u001b[39m\n\u001b[31m    <div class=\\\"login-logo\\\">360LM</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">Operations Hub</div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Select Your Name</label>\u001b[39m\n\u001b[31m      <select id=\\\"login-name\\\"><option value=\\\"\\\">— Choose employee —</option><option value=\\\"amit\\\">Amit</option><option value=\\\"ashok\\\">Ashok</option><option value=\\\"gurpreet\\\">Gurpreet</option><option value=\\\"harish\\\">Harish Kumar Lal</option><option value=\\\"mukesh\\\">Mukesh</option><option value=\\\"narender\\\">Narender</option><option value=\\\"pramod\\\">Pramod Narang</option><option value=\\\"rakesh\\\">Rakesh</option><option value=\\\"sachin\\\">Sachin</option><option value=\\\"shambhu\\\">Shambhu</option><option value=\\\"umashankar\\\">Umashankar</option></select>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m      <label style=\\\"text-align:center\\\">Enter PIN</label>\u001b[39m\n\u001b[31m      <div class=\\\"pin-display\\\" id=\\\"pin-display\\\">\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd0\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd1\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd2\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd3\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"login-err\\\" id=\\\"login-err\\\"></div>\u001b[39m\n\u001b[31m      <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-1\\\" data-k=\\\"1\\\" aria-label=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-2\\\" data-k=\\\"2\\\" aria-label=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-3\\\" data-k=\\\"3\\\" aria-label=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-4\\\" data-k=\\\"4\\\" aria-label=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-5\\\" data-k=\\\"5\\\" aria-label=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-6\\\" data-k=\\\"6\\\" aria-label=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-7\\\" data-k=\\\"7\\\" aria-label=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-8\\\" data-k=\\\"8\\\" aria-label=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-9\\\" data-k=\\\"9\\\" aria-label=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key clr\\\" id=\\\"pk-C\\\" data-k=\\\"C\\\" aria-label=\\\"Clear\\\">CLR</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-0\\\" data-k=\\\"0\\\" aria-label=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key back\\\" id=\\\"pk-B\\\" data-k=\\\"B\\\" aria-label=\\\"Backspace\\\">⌫</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     HOME  (built dynamically)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\" id=\\\"home-header\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"home-body\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SHARE PICKER\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-share\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>Share Image To</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">Choose where to send this photo</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"share-prompt\\\">Which tool should receive this image?</div>\u001b[39m\n\u001b[31m    <div class=\\\"share-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-expense\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🧾</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Expenses</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-recce\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">📍</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Recce</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-installation\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🔧</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Installation</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     TEAM PINs  (admin only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-team\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-back\\\" style=\\\"font-size:20px\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Team PINs</h1><div class=\\\"sub\\\">Set employee login PINs</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-refresh\\\" title=\\\"Refresh\\\">🔄</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"info-note\\\">PINs are stored encrypted — current PIN cannot be viewed. You can set a new PIN for any employee below.</div>\u001b[39m\n\u001b[31m    <div id=\\\"team-list\\\"></div>·\u001b[39m\n\u001b[31m    <!-- Icon Admin — Harish & Pramod only -->\u001b[39m\n\u001b[31m    <div id=\\\"icon-admin-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:8px\\\">🎨 PWA Icons (Admin)</div>\u001b[39m\n\u001b[31m      <div class=\\\"info-note\\\" style=\\\"margin-bottom:10px\\\">Upload a new icon for any PWA. Upload both 192×192 and 512×512 sizes. PNG/JPG/WEBP accepted — auto-resized on server.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;flex-direction:column;gap:10px\\\">·\u001b[39m\n\u001b[31m        <!-- Secret management -->\u001b[39m\n\u001b[31m        <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:12px;padding:14px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px\\\">🔑 Admin Secret</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:center;gap:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"font-family:monospace;font-size:13px;color:var(--accent);background:var(--bg);\u001b[39m\n\u001b[31m              border-radius:6px;padding:8px 12px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\" id=\\\"icon-secret-display\\\">—</div>\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"toggleSecretVisible()\\\" id=\\\"btn-secret-show\\\">Show</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:10px\\\">Change secret (min 16 chars):</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m            <input id=\\\"icon-new-secret\\\" type=\\\"text\\\" placeholder=\\\"Enter new secret…\\\" style=\\\"flex:1;background:var(--bg);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m              padding:10px 12px;border-radius:8px;font-size:13px;font-family:monospace;outline:none\\\">\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"changeIconSecret()\\\" style=\\\"white-space:nowrap\\\">Update</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div id=\\\"icon-secret-err\\\" style=\\\"font-size:11px;color:var(--danger);min-height:14px;margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>·\u001b[39m\n\u001b[31m        <!-- Upload form -->\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Select PWA</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-pwa-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"hub\\\">Hub</option>\u001b[39m\n\u001b[31m            <option value=\\\"expense\\\">Expenses</option>\u001b[39m\n\u001b[31m            <option value=\\\"finance\\\">Finance</option>\u001b[39m\n\u001b[31m            <option value=\\\"installation\\\">Installation</option>\u001b[39m\n\u001b[31m            <option value=\\\"recce\\\">Recce</option>\u001b[39m\n\u001b[31m            <option value=\\\"dispatch\\\">Dispatch</option>\u001b[39m\n\u001b[31m            <option value=\\\"production\\\">Production</option>\u001b[39m\n\u001b[31m            <option value=\\\"vehicle\\\">Vehicle Log</option>\u001b[39m\n\u001b[31m            <option value=\\\"vrs\\\">Vehicle Request</option>\u001b[39m\n\u001b[31m            <option value=\\\"sales\\\">Sales</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Icon Size</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-size-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"192\\\">192 × 192 (standard)</option>\u001b[39m\n\u001b[31m            <option value=\\\"512\\\">512 × 512 (large)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Choose Image File</label>\u001b[39m\n\u001b[31m          <input type=\\\"file\\\" id=\\\"icon-file-input\\\" accept=\\\"image/png,image/jpeg,image/webp\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:14px;width:100%\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-preview-wrap\\\" style=\\\"display:none;text-align:center\\\">\u001b[39m\n\u001b[31m          <img id=\\\"icon-preview-img\\\" style=\\\"width:96px;height:96px;border-radius:16px;border:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-upload-err\\\" style=\\\"font-size:12px;color:var(--danger);min-height:16px\\\"></div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" onclick=\\\"uploadIcon()\\\">Upload Icon</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     MODAL: Admin set PIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-setpin\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🔑 Set PIN — <span id=\\\"setpin-target-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>New PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Enter new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-confirm-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Confirm New PIN</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-confirm\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Repeat new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-auth-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Your own current PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"setpin-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-setpin-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-setpin-save\\\">Set PIN</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     JOBS — Campaign list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-jobs\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Jobs</h1><div class=\\\"sub\\\">Campaigns &amp; Tours</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh jobs\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-campaign\\\" title=\\\"New Campaign\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"jobs-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     CAMPAIGN DETAIL — Tour list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-campaign\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 id=\\\"campaign-hdr-title\\\">Campaign</h1><div class=\\\"sub\\\" id=\\\"campaign-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh campaign\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-tour\\\" title=\\\"New Tour\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"campaign-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Campaign -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-campaign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">📋 New Campaign</div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Year</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-year\\\" min=\\\"2024\\\" max=\\\"2030\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Phase #</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-phase\\\" value=\\\"1\\\" min=\\\"1\\\" max=\\\"99\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Brand <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-brand\\\" placeholder=\\\"e.g. Lenovo\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Client <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-client\\\" placeholder=\\\"e.g. Lenovo India\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Campaign Name <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-campaign\\\" placeholder=\\\"e.g. Chandigarh BTL\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Generated Campaign ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nc-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nc-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nc-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nc-save\\\">Create</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Tour -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-tour\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px;max-height:88vh;overflow-y:auto\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🗺️ New Tour</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Tour Title</div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nt-title\\\" placeholder=\\\"e.g. Chandigarh Week 1\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Start Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-start\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">End Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-end\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:6px\\\">Assign Employees <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-employees\\\" style=\\\"display:flex;flex-direction:column;gap:6px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"color:var(--muted);font-size:13px\\\">Loading…</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Notes</div>\u001b[39m\n\u001b[31m      <textarea id=\\\"nt-notes\\\" rows=\\\"2\\\" placeholder=\\\"Optional instructions or remarks\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px;font-family:inherit;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Tour ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-id-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nt-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nt-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nt-save\\\">Create Tour</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- UPDATE BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"update-banner\\\">\u001b[39m\n\u001b[31m  <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ub-text\\\">App update available</div>\u001b[39m\n\u001b[31m    <div class=\\\"ub-sub\\\">Tap to refresh and get the latest version</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ub-btn\\\" id=\\\"btn-update\\\">Update now</button>\u001b[39m\n\u001b[31m  <button class=\\\"ub-close\\\" id=\\\"btn-update-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- INSTALL BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"install-banner\\\">\u001b[39m\n\u001b[31m  <div class=\\\"ib-icon\\\">📲</div>\u001b[39m\n\u001b[31m  <div class=\\\"ib-text\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ib-title\\\">Install 360LM Hub</div>\u001b[39m\n\u001b[31m    <div class=\\\"ib-sub\\\">Add to home screen for the best experience</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ib-btn\\\" id=\\\"btn-install\\\">Install</button>\u001b[39m\n\u001b[31m  <button class=\\\"ib-close\\\" id=\\\"btn-install-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: AI Config (mgmt only) -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-ai\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🤖 AI Config — Screenshot Extraction</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Tesseract OCR</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-tesseract\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Extraction Fallback</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-enabled\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Model</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-model\\\">\u001b[39m\n\u001b[31m        <option value=\\\"haiku\\\">Haiku (fast, cost-effective)</option>\u001b[39m\n\u001b[31m        <option value=\\\"sonnet\\\">Sonnet (higher accuracy)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-ai-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-ai-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"border-top:1px solid var(--border);padding-top:12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">API Key Health</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px;margin-bottom:10px\\\">Live check — result is always the same for all users</div>\u001b[39m\n\u001b[31m      <div id=\\\"api-key-status-wrap\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-check-keys\\\" style=\\\"margin-top:10px;width:100%\\\">Check API Keys</button>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:14px;border-top:1px solid var(--border);padding-top:12px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">View Usage &amp; Billing</div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Anthropic</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/usage\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Google Maps</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/google/maps-apis/api-list\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: Hub Quick-Action Config -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-qa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">⚡ Quick Actions</div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-config-list\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-cfg-err\\\" style=\\\"color:var(--danger);font-size:13px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-qa-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-qa-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SCRIPT\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31m/* ── CONFIG ── */\u001b[39m\n\u001b[31mconst CFG = {\u001b[39m\n\u001b[31m  api:        '/db',\u001b[39m\n\u001b[31m  apiHeaders: { 'Content-Type': 'application/json', 'Accept-Profile': 'expense' },\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31m/* ── SERVER ACCESS MAP (fetched from hub-access.json, overrides ACCESS for non-mgmt) ── */\u001b[39m\n\u001b[31mlet _serverAccess = {};\u001b[39m\n\u001b[31masync function loadHubAccess() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/hub/hub-access.json?t=' + Date.now());\u001b[39m\n\u001b[31m    if (r.ok) _serverAccess = await r.json();\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── ROLE ACCESS MAP (hardcoded fallback) ── */\u001b[39m\n\u001b[31mconst ACCESS = {\u001b[39m\n\u001b[31m  harish:     ['expense','installation','finance','recce','dispatch','production','jobs','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  pramod:     ['expense','installation','finance','recce','dispatch','production','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  rakesh:     ['installation','expense','production','jobs','vehicle','vrs','sales','hr'],\u001b[39m\n\u001b[31m  ashok:      ['expense','production','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  shambhu:    ['finance','expense','vehicle','vrs','stores','activity','hr'],\u001b[39m\n\u001b[31m  gurpreet:   ['expense','dispatch','vehicle','vrs','stores','hr'],\u001b[39m\n\u001b[31m  sachin:     ['installation','expense','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  mukesh:     ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  umashankar: ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  narender:   ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  amit:       ['vehicle','vrs','hr'],\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst TILES = [\u001b[39m\n\u001b[31m  { id:'expense',      label:'Expenses',     icon:'🧾', url:'/expense/',               live:true  },\u001b[39m\n\u001b[31m  { id:'installation', label:'Installation', icon:'🔧', url:'/installation/current/',  live:true  },\u001b[39m\n\u001b[31m  { id:'finance',      label:'Finance',      icon:'💰', url:'/finance/',               live:true  },\u001b[39m\n\u001b[31m  { id:'recce',        label:'Recce',        icon:'📍', url:'/recce/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'dispatch',     label:'Dispatch',     icon:'🚚', url:'/dispatch/',              live:true  },\u001b[39m\n\u001b[31m  { id:'production',   label:'Production',   icon:'⚙️', url:'/production/',            live:true  },\u001b[39m\n\u001b[31m  { id:'jobs',         label:'Jobs',         icon:'📋', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'vehicle',      label:'Vehicle Log',  icon:'🚐', url:'/vehicle/',               live:true  },\u001b[39m\n\u001b[31m  { id:'vrs',          label:'Veh. Request', icon:'📋', url:'/vrs/',                   live:true  },\u001b[39m\n\u001b[31m  { id:'sales',        label:'Sales',        icon:'💼', url:'/sales/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'stores',       label:'Stores',       icon:'📦', url:'/stores/',                live:true  },\u001b[39m\n\u001b[31m  { id:'client',       label:'Client',       icon:'👤', url:'/client/',                live:true  },\u001b[39m\n\u001b[31m  { id:'team-pins',    label:'Team PINs',    icon:'🔑', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'admin',        label:'Admin',        icon:'🛠️', url:'/admin/',                  live:true  },\u001b[39m\n\u001b[31m  { id:'activity',     label:'Activity',     icon:'🎯', url:'/activity/',               live:true  },\u001b[39m\n\u001b[31m  { id:'hr',           label:'HR',           icon:'👥', url:'/hr/',                     live:true  },\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31mconst MGMT = ['harish', 'pramod'];·\u001b[39m\n\u001b[31m/* ── STATE ── */\u001b[39m\n\u001b[31mlet currentUser     = null;\u001b[39m\n\u001b[31mlet pinBuffer       = '';\u001b[39m\n\u001b[31mlet setPinTarget    = null;\u001b[39m\n\u001b[31mlet pendingShare    = false;\u001b[39m\n\u001b[31mlet _loginAttempts  = 0;\u001b[39m\n\u001b[31mlet _lockoutUntil   = 0;\u001b[39m\n\u001b[31mlet _loginInFlight  = false;·\u001b[39m\n\u001b[31m/* ── UTILS ── */\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => { s.classList.remove('active'); s.style.display = ''; });\u001b[39m\n\u001b[31m  document.getElementById('screen-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet toastTimer;\u001b[39m\n\u001b[31mfunction toast(msg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.classList.add('show');\u001b[39m\n\u001b[31m  clearTimeout(toastTimer);\u001b[39m\n\u001b[31m  toastTimer = setTimeout(() => el.classList.remove('show'), 2800);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fmtINR(n) {\u001b[39m\n\u001b[31m  if (n >= 100000) return '₹' + (n / 100000).toFixed(1) + 'L';\u001b[39m\n\u001b[31m  if (n >= 1000)   return '₹' + (n / 1000).toFixed(1) + 'K';\u001b[39m\n\u001b[31m  return '₹' + Math.round(n);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SESSION ── */\u001b[39m\n\u001b[31mconst SESSION_KEY  = 'lm360-session';\u001b[39m\n\u001b[31mconst EXP_SES_KEY  = '360exp-session';\u001b[39m\n\u001b[31mconst SESSION_TTL  = 12 * 3600 * 1000;·\u001b[39m\n\u001b[31mfunction saveSession(user) {\u001b[39m\n\u001b[31m  const payload = { empId: user.id, name: user.name, role: user.role, loginAt: new Date().toISOString(), source: 'hub' };\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m  localStorage.setItem(EXP_SES_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearSession() {\u001b[39m\n\u001b[31m  localStorage.removeItem(SESSION_KEY);\u001b[39m\n\u001b[31m  localStorage.removeItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const raw = localStorage.getItem(SESSION_KEY) || localStorage.getItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m    if (!raw) return null;\u001b[39m\n\u001b[31m    const s = JSON.parse(raw);\u001b[39m\n\u001b[31m    if (!s.empId || !s.name || !s.role) return null;\u001b[39m\n\u001b[31m    if (Date.now() - new Date(s.loginAt).getTime() > SESSION_TTL) { clearSession(); return null; }\u001b[39m\n\u001b[31m    return s;\u001b[39m\n\u001b[31m  } catch (_) { return null; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGIN FLOW ── */\u001b[39m\n\u001b[31masync function initLogin() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('login-name');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— Choose employee —</option>'; // clear before repopulating\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    emps.forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    [\u001b[39m\n\u001b[31m      { id:'amit',        name:'Amit'             },\u001b[39m\n\u001b[31m      { id:'ashok',       name:'Ashok'            },\u001b[39m\n\u001b[31m      { id:'gurpreet',    name:'Gurpreet'         },\u001b[39m\n\u001b[31m      { id:'harish',      name:'Harish Kumar Lal' },\u001b[39m\n\u001b[31m      { id:'mukesh',      name:'Mukesh'           },\u001b[39m\n\u001b[31m      { id:'narender',    name:'Narender'         },\u001b[39m\n\u001b[31m      { id:'pramod',      name:'Pramod Narang'    },\u001b[39m\n\u001b[31m      { id:'rakesh',      name:'Rakesh'           },\u001b[39m\n\u001b[31m      { id:'sachin',      name:'Sachin'           },\u001b[39m\n\u001b[31m      { id:'shambhu',     name:'Shambhu'          },\u001b[39m\n\u001b[31m      { id:'umashankar',  name:'Umashankar'       },\u001b[39m\n\u001b[31m    ].forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  document.querySelectorAll('.pin-key').forEach(btn =>\u001b[39m\n\u001b[31m    btn.addEventListener('click', () => handlePinKey(btn.dataset.k))\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handlePinKey(k) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (_loginInFlight) return;\u001b[39m\n\u001b[31m  // Lockout guard\u001b[39m\n\u001b[31m  if (_lockoutUntil && Date.now() < _lockoutUntil) {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    errEl.textContent = `Too many attempts — wait ${secs}s`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (_lockoutUntil) { _lockoutUntil = 0; _loginAttempts = 0; }\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (k === 'C') { pinBuffer = ''; }\u001b[39m\n\u001b[31m  else if (k === 'B') { pinBuffer = pinBuffer.slice(0, -1); }\u001b[39m\n\u001b[31m  else {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (!empId) { toast('Select your name first'); return; }\u001b[39m\n\u001b[31m    if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  if (pinBuffer.length === 4) {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (empId) attemptLogin(empId, pinBuffer);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updatePinDisplay() {\u001b[39m\n\u001b[31m  for (let i = 0; i < 4; i++)\u001b[39m\n\u001b[31m    document.getElementById('pd' + i).classList.toggle('filled', i < pinBuffer.length);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _shakePin() {\u001b[39m\n\u001b[31m  const pd = document.getElementById('pin-display');\u001b[39m\n\u001b[31m  pd.classList.add('shake');\u001b[39m\n\u001b[31m  setTimeout(() => pd.classList.remove('shake'), 400);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _startLockout() {\u001b[39m\n\u001b[31m  _lockoutUntil = Date.now() + 30000;\u001b[39m\n\u001b[31m  _loginAttempts = 0;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  const tick = () => {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    if (secs > 0) { errEl.textContent = `Too many attempts — wait ${secs}s`; setTimeout(tick, 500); }\u001b[39m\n\u001b[31m    else { _lockoutUntil = 0; errEl.textContent = ''; }\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  tick();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function attemptLogin(empId, pin) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  _loginInFlight = true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pin }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      _loginAttempts++;\u001b[39m\n\u001b[31m      _shakePin();\u001b[39m\n\u001b[31m      if (_loginAttempts >= 5) { _startLockout(); return; }\u001b[39m\n\u001b[31m      errEl.textContent = `Wrong PIN — ${5 - _loginAttempts} attempt${5 - _loginAttempts !== 1 ? 's' : ''} left`;\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    currentUser = { id: user.id, name: user.name, role: user.role };\u001b[39m\n\u001b[31m    saveSession(currentUser);\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — check connection';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    _loginInFlight = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── HOME SCREEN ── */\u001b[39m\n\u001b[31masync function buildHome() {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const body   = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  // Management users always use hardcoded ACCESS; others prefer server-side hub-access.json\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const tiles  = accessIds.map(id => TILES.find(t => t.id === id)).filter(Boolean);·\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"avatar\\\">${initials}</div>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1 style=\\\"font-size:16px\\\">${currentUser.name}</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">${currentUser.role}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${isMgmt ? '<button class=\\\"btn-icon\\\" id=\\\"btn-hub-ai\\\" title=\\\"AI Config\\\" style=\\\"font-size:18px\\\">🤖</button>' : ''}\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m  if (isMgmt) document.getElementById('btn-hub-ai').addEventListener('click', openHubAiConfig);·\u001b[39m\n\u001b[31m  if (isMgmt) {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"hub-main-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-dash\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">📊</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">Dashboard</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-tools\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">🧰</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">All Tools</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-header\\\">\u001b[39m\n\u001b[31m        <div class=\\\"section-title\\\" style=\\\"margin:0\\\">Quick Actions</div>\u001b[39m\n\u001b[31m        <button class=\\\"btn-icon\\\" id=\\\"btn-hub-qa-cfg\\\" title=\\\"Configure Quick Actions\\\" style=\\\"font-size:18px;padding:8px;min-width:44px;min-height:44px\\\">⚙️</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-grid\\\" id=\\\"hub-qa-grid\\\"></div>`;\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-dash').addEventListener('click',  () => enterHubDash(tiles));\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-tools').addEventListener('click', () => enterHubTools(tiles));\u001b[39m\n\u001b[31m    document.getElementById('btn-hub-qa-cfg').addEventListener('click', openHubQaConfig);\u001b[39m\n\u001b[31m    renderHubQA();\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"tile-grid field\\\" id=\\\"tile-grid\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"text-align:center;font-size:12px;color:var(--muted);padding-top:8px\\\">Tap a tile to open your tool</div>`;\u001b[39m\n\u001b[31m    renderTiles(tiles, 'field');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderTiles(tiles, mode) {\u001b[39m\n\u001b[31m  const grid = document.getElementById('tile-grid');\u001b[39m\n\u001b[31m  tiles.forEach(tile => {\u001b[39m\n\u001b[31m    const div = document.createElement('div');\u001b[39m\n\u001b[31m    div.className = 'tile ' + mode + (tile.live ? '' : ' coming');\u001b[39m\n\u001b[31m    div.innerHTML = `<div class=\\\"tile-icon\\\">${tile.icon}</div><div class=\\\"tile-label\\\">${tile.label}</div>` +\u001b[39m\n\u001b[31m      (tile.live ? '' : '<div class=\\\"soon-badge\\\">Soon</div>');\u001b[39m\n\u001b[31m    div.addEventListener('click', () => handleTile(tile));\u001b[39m\n\u001b[31m    grid.appendChild(div);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handleTile(tile) {\u001b[39m\n\u001b[31m  if (!tile.live) { toast('Coming soon'); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'team-pins') { enterTeamPins(); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'jobs')      { enterJobs();     return; }\u001b[39m\n\u001b[31m  window.location.href = tile.url;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT SUB-VIEWS ── */\u001b[39m\n\u001b[31mfunction _mgmtHeader(title) {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-subview-back\\\" style=\\\"font-size:20px;font-weight:900\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 style=\\\"font-size:16px\\\">${title}</h1></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-subview-back').addEventListener('click', () => buildHome());\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubDash(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('Dashboard');\u001b[39m\n\u001b[31m  const hdr = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const refreshBtn = document.createElement('button');\u001b[39m\n\u001b[31m  refreshBtn.className = 'btn-icon';\u001b[39m\n\u001b[31m  refreshBtn.title = 'Refresh dashboard';\u001b[39m\n\u001b[31m  refreshBtn.setAttribute('aria-label', 'Refresh dashboard');\u001b[39m\n\u001b[31m  refreshBtn.style.cssText = 'font-size:18px';\u001b[39m\n\u001b[31m  refreshBtn.textContent = '🔄';\u001b[39m\n\u001b[31m  refreshBtn.addEventListener('click', loadKPIs);\u001b[39m\n\u001b[31m  hdr.insertBefore(refreshBtn, hdr.querySelector('#btn-logout'));\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Actions Required</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-actions\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Operations</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row kpi-4col\\\" id=\\\"kpi-ops\\\">${skel}${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Production</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-prod\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Installation</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-inst\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">HR</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-hr\\\">${skel}${skel}${skel}</div>`;\u001b[39m\n\u001b[31m  loadKPIs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubTools(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('All Tools');\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  body.innerHTML = `<div class=\\\"tile-grid mgmt\\\" id=\\\"tile-grid\\\"></div>`;\u001b[39m\n\u001b[31m  renderTiles(tiles, 'mgmt');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT AI CONFIG ── */\u001b[39m\n\u001b[31mconst HUB_FIN_HDR = { 'Content-Type': 'application/json', 'Accept-Profile': 'finance' };·\u001b[39m\n\u001b[31masync function openHubAiConfig() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r    = await fetch(`${CFG.api}/upi_config`, { headers: HUB_FIN_HDR });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const get  = key => rows.find(x => x.key === key)?.value;\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-tesseract').value = get('tesseract_enabled') || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-enabled').value   = get('ai_enabled')        || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-model').value     = get('ai_model')           || 'haiku';\u001b[39m\n\u001b[31m  } catch(_) { toast('Could not load AI config'); return; }\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-ai').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveHubAiConfig() {\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-hub-ai-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  const pairs = [\u001b[39m\n\u001b[31m    { key:'tesseract_enabled', value: document.getElementById('hub-ai-tesseract').value },\u001b[39m\n\u001b[31m    { key:'ai_enabled',        value: document.getElementById('hub-ai-enabled').value },\u001b[39m\n\u001b[31m    { key:'ai_model',          value: document.getElementById('hub-ai-model').value },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const results = await Promise.all(pairs.map(p =>\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/upi_config?key=eq.${p.key}`, {\u001b[39m\n\u001b[31m        method: 'PATCH',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'finance', 'Prefer': 'return=minimal' },\u001b[39m\n\u001b[31m        body: JSON.stringify({ value: p.value }),\u001b[39m\n\u001b[31m      })\u001b[39m\n\u001b[31m    ));\u001b[39m\n\u001b[31m    if (results.some(r => !r.ok)) throw new Error('partial failure');\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show');\u001b[39m\n\u001b[31m    toast('✅ AI config saved');\u001b[39m\n\u001b[31m  } catch(_) { toast('Save failed'); }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Save'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function checkApiKeyStatus() {\u001b[39m\n\u001b[31m  const btn  = document.getElementById('btn-check-keys');\u001b[39m\n\u001b[31m  const wrap = document.getElementById('api-key-status-wrap');\u001b[39m\n\u001b[31m  btn.textContent = 'Checking…';\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  wrap.innerHTML = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/tour-ai/key-status');\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Proxy error ' + r.status);\u001b[39m\n\u001b[31m    const d = await r.json();\u001b[39m\n\u001b[31m    function mkBadge(info, label, link) {\u001b[39m\n\u001b[31m      const col  = info.ok ? 'var(--success)' : 'var(--danger)';\u001b[39m\n\u001b[31m      const icon = info.ok ? '✅' : '❌';\u001b[39m\n\u001b[31m      const note = info.ok ? (info.model || 'Valid') : (info.error || 'Error');\u001b[39m\n\u001b[31m      const hint = info.hint ? ' · key ' + info.hint : '';\u001b[39m\n\u001b[31m      return `<div style=\\\"background:var(--card);border:1px solid var(--border);border-left:3px solid ${col};border-radius:8px;padding:10px 12px;display:flex;align-items:center;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:13px;font-weight:600;color:var(--text)\\\">${icon} ${label}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${note}${hint}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <a href=\\\"${link}\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:11px;color:var(--info);text-decoration:none;white-space:nowrap\\\">Billing ↗</a>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    wrap.innerHTML =\u001b[39m\n\u001b[31m      mkBadge(d.anthropic, 'Anthropic API',    'https://console.anthropic.com/settings/billing') +\u001b[39m\n\u001b[31m      mkBadge(d.maps,      'Google Maps API',  'https://console.cloud.google.com/billing');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    wrap.innerHTML = `<div style=\\\"font-size:12px;color:var(--danger)\\\">Error: ${e.message}</div>`;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.textContent = 'Check Again';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT QUICK ACTIONS ── */\u001b[39m\n\u001b[31mconst HUB_QA_DEFS = [\u001b[39m\n\u001b[31m  { id:'offer',       icon:'📋', label:'New Offer',    accessId:'sales',        fn:() => { window.location.href='/sales/'; } },\u001b[39m\n\u001b[31m  { id:'paytm',       icon:'📱', label:'UPI Pay',       accessId:'finance',      fn:() => { window.location.href='/finance/?qa=upi'; } },\u001b[39m\n\u001b[31m  { id:'expense',     icon:'🧾', label:'Expenses',      accessId:'expense',      fn:() => { window.location.href='/expense/'; } },\u001b[39m\n\u001b[31m  { id:'finance',     icon:'💰', label:'Finance',       accessId:'finance',      fn:() => { window.location.href='/finance/'; } },\u001b[39m\n\u001b[31m  { id:'dispatch',    icon:'🚚', label:'Dispatch',      accessId:'dispatch',     fn:() => { window.location.href='/dispatch/'; } },\u001b[39m\n\u001b[31m  { id:'stores',      icon:'📦', label:'Stores',        accessId:'stores',       fn:() => { window.location.href='/stores/'; } },\u001b[39m\n\u001b[31m  { id:'recce',       icon:'📍', label:'Recce',         accessId:'recce',        fn:() => { window.location.href='/recce/'; } },\u001b[39m\n\u001b[31m  { id:'production',  icon:'⚙️', label:'Production',   accessId:'production',   fn:() => { window.location.href='/production/'; } },\u001b[39m\n\u001b[31m  { id:'tours',       icon:'🗺️', label:'Tour Plan',   accessId:'installation', fn:() => { window.location.href='/tour-planner/'; } },\u001b[39m\n\u001b[31m  { id:'installation',icon:'🔧', label:'Installation',  accessId:'installation', fn:() => { window.location.href='/installation/current/'; } },\u001b[39m\n\u001b[31m  { id:'activity',    icon:'🎯', label:'Activity',      accessId:'activity',     fn:() => { window.location.href='/activity/'; } },\u001b[39m\n\u001b[31m];\u001b[39m\n\u001b[31mconst HUB_QA_DEFAULT = ['offer', 'paytm'];\u001b[39m\n\u001b[31mconst HUB_QA_STORE   = '360lm-hub-qa';\u001b[39m\n\u001b[31mlet   _hubQaTempSel  = [];·\u001b[39m\n\u001b[31mfunction getHubQA() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(HUB_QA_STORE));\u001b[39m\n\u001b[31m    if (Array.isArray(s) && s.length) return s;\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m  return [...HUB_QA_DEFAULT];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHubQA() {\u001b[39m\n\u001b[31m  const ids  = getHubQA();\u001b[39m\n\u001b[31m  const grid = document.getElementById('hub-qa-grid');\u001b[39m\n\u001b[31m  if (!grid) return;\u001b[39m\n\u001b[31m  if (!ids.length) {\u001b[39m\n\u001b[31m    grid.innerHTML = `<div style=\\\"color:var(--muted);font-size:13px;grid-column:1/-1;\u001b[39m\n\u001b[31m      text-align:center;padding:16px\\\">Tap ⚙️ to add quick actions</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  grid.innerHTML = ids.map(id => {\u001b[39m\n\u001b[31m    const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!d) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-tile\\\" onclick=\\\"hubQaRun('${id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction hubQaRun(id) {\u001b[39m\n\u001b[31m  const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m  if (d) d.fn();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openHubQaConfig() {\u001b[39m\n\u001b[31m  _hubQaTempSel = [...getHubQA()];\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const visibleDefs = HUB_QA_DEFS.filter(d => accessIds.includes(d.accessId));\u001b[39m\n\u001b[31m  const list = document.getElementById('hub-qa-config-list');\u001b[39m\n\u001b[31m  list.innerHTML = visibleDefs.map(d => {\u001b[39m\n\u001b[31m    const sel = _hubQaTempSel.includes(d.id);\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-item${sel?' selected':''}\\\" id=\\\"hub-qa-item-${d.id}\\\"\u001b[39m\n\u001b[31m        onclick=\\\"toggleHubQaItem('${d.id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-toggle\\\"></div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleHubQaItem(id) {\u001b[39m\n\u001b[31m  const idx = _hubQaTempSel.indexOf(id);\u001b[39m\n\u001b[31m  if (idx >= 0) {\u001b[39m\n\u001b[31m    _hubQaTempSel.splice(idx, 1);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    if (_hubQaTempSel.length >= 6) {\u001b[39m\n\u001b[31m      document.getElementById('hub-qa-cfg-err').textContent = 'Maximum 6 quick actions';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _hubQaTempSel.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  const el = document.getElementById('hub-qa-item-' + id);\u001b[39m\n\u001b[31m  if (el) el.className = 'hub-qa-item' + (_hubQaTempSel.includes(id) ? ' selected' : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction saveHubQaConfig() {\u001b[39m\n\u001b[31m  localStorage.setItem(HUB_QA_STORE, JSON.stringify(_hubQaTempSel));\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.remove('show');\u001b[39m\n\u001b[31m  renderHubQA();\u001b[39m\n\u001b[31m  toast('✅ Quick actions updated');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── KPI FETCH ── */\u001b[39m\n\u001b[31mfunction _hdr(schema) {\u001b[39m\n\u001b[31m  return { 'Content-Type': 'application/json', 'Accept-Profile': schema };\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _safe(p) { return p.catch(() => null); }·\u001b[39m\n\u001b[31masync function loadKPIs() {\u001b[39m\n\u001b[31m  const mon = firstOfMonth();\u001b[39m\n\u001b[31m  const curMonth = new Date().toISOString().slice(0, 7); // YYYY-MM\u001b[39m\n\u001b[31m  const todayIST = (() => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; })();·\u001b[39m\n\u001b[31m  // Reset sections to loading skeletons before fetching (makes refresh visible)\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  [['kpi-actions',3],['kpi-ops',4],['kpi-prod',3],['kpi-inst',3],['kpi-hr',3]].forEach(([id,n]) => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    if (el) el.innerHTML = skel.repeat(n);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  const [\u001b[39m\n\u001b[31m    sheetsRes, claimsRes, salaryRes,\u001b[39m\n\u001b[31m    cashRes,   dispRes,   recceRes,  prodRes,\u001b[39m\n\u001b[31m    instJobsRes, instCtrsRes, storeItemsRes,\u001b[39m\n\u001b[31m    hrAttRes, hrEmpRes, hrConcernRes, hrSlipsRes\u001b[39m\n\u001b[31m  ] = await Promise.all([\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/sheets?status=in.(submitted,escalated)&select=sheet_id`, { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/approved_pending_reimbursement?select=sheet_id`,         { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_register?status=in.(pending,partial)&month=eq.${curMonth}&select=record_id`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/accounts?account_id=eq.petty-cash&select=current_balance`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/dispatches?status=eq.active&select=dispatch_id`,         { headers: _hdr('dispatch') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/submissions?submitted_at=gte.${mon}&select=sub_id`,      { headers: _hdr('recce') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/orders?status=not.in.(dispatched)&select=order_id,status`, { headers: _hdr('production') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/jobs?select=job_id,status`,                              { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/counters?select=counter_id,status`,                      { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/items?active=eq.true&select=id,current_stock,min_stock`, { headers: _hdr('stores') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance?date=eq.${todayIST}&status=eq.present&select=attendance_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/employee_list?select=id`,                               { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance_concerns?status=eq.open&select=concern_id`,  { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_slips?month=eq.${curMonth}&status=eq.finalized&select=slip_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m  ]);·\u001b[39m\n\u001b[31m  const sheets    = sheetsRes?.ok    ? await sheetsRes.json()    : [];\u001b[39m\n\u001b[31m  const claims    = claimsRes?.ok    ? await claimsRes.json()    : [];\u001b[39m\n\u001b[31m  const salary    = salaryRes?.ok    ? await salaryRes.json()    : [];\u001b[39m\n\u001b[31m  const cashArr   = cashRes?.ok      ? await cashRes.json()      : [];\u001b[39m\n\u001b[31m  const disps     = dispRes?.ok      ? await dispRes.json()      : [];\u001b[39m\n\u001b[31m  const recces    = recceRes?.ok     ? await recceRes.json()     : [];\u001b[39m\n\u001b[31m  const prods     = prodRes?.ok      ? await prodRes.json()      : [];\u001b[39m\n\u001b[31m  const instJobs  = instJobsRes?.ok  ? await instJobsRes.json()  : [];\u001b[39m\n\u001b[31m  const instCtrs  = instCtrsRes?.ok  ? await instCtrsRes.json()  : [];\u001b[39m\n\u001b[31m  const storeItems = storeItemsRes?.ok ? await storeItemsRes.json() : [];\u001b[39m\n\u001b[31m  const hrAtt     = hrAttRes?.ok     ? await hrAttRes.json()     : [];\u001b[39m\n\u001b[31m  const hrEmp     = hrEmpRes?.ok     ? await hrEmpRes.json()     : [];\u001b[39m\n\u001b[31m  const hrConcern = hrConcernRes?.ok ? await hrConcernRes.json() : [];\u001b[39m\n\u001b[31m  const hrSlips   = hrSlipsRes?.ok   ? await hrSlipsRes.json()   : [];·\u001b[39m\n\u001b[31m  const cash = Number(cashArr[0]?.current_balance || 0);·\u001b[39m\n\u001b[31m  const actionsEl = document.getElementById('kpi-actions');\u001b[39m\n\u001b[31m  const opsEl     = document.getElementById('kpi-ops');\u001b[39m\n\u001b[31m  const prodEl    = document.getElementById('kpi-prod');\u001b[39m\n\u001b[31m  const instEl    = document.getElementById('kpi-inst');\u001b[39m\n\u001b[31m  const hrEl      = document.getElementById('kpi-hr');\u001b[39m\n\u001b[31m  if (!actionsEl || !opsEl || !prodEl || !instEl) return;·\u001b[39m\n\u001b[31m  // ── Actions Required ────────────────────────────────────────────\u001b[39m\n\u001b[31m  actionsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${sheets.length ? 'alert' : 'ok'}\\\" data-href=\\\"/expense/?tab=approvals\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${sheets.length ? 'red' : 'green'}\\\">${sheets.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Expense Approvals</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${claims.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=claims\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${claims.length ? 'amber' : 'green'}\\\">${claims.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Claims to Reimburse</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${salary.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=salary\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${salary.length ? 'amber' : 'green'}\\\">${salary.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Salary Due This Month</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Operations ──────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const cashColor = cash < 2000 ? 'red' : cash < 5000 ? 'amber' : 'green';\u001b[39m\n\u001b[31m  const cashCard  = cash < 2000 ? 'alert' : cash < 5000 ? 'warn' : 'ok';\u001b[39m\n\u001b[31m  const lowStock = storeItems.filter(i => i.current_stock < i.min_stock).length;\u001b[39m\n\u001b[31m  opsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${cashCard}\\\" data-href=\\\"/finance/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${cashColor}\\\">${fmtINR(cash)}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">${cash < 0 ? '⚠️ ' : ''}Petty Cash</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/dispatch/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${disps.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Dispatches</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/recce/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${recces.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Recce This Month</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${lowStock > 0 ? 'alert' : 'ok'}\\\" data-href=\\\"/stores/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${lowStock > 0 ? 'red' : 'green'}\\\">${lowStock}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Low Stock Items</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Production ───────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const prodPending = prods.filter(o => o.status === 'pending').length;\u001b[39m\n\u001b[31m  const prodActive  = prods.filter(o => o.status === 'in_production' || o.status === 'quality_check').length;\u001b[39m\n\u001b[31m  const prodReady   = prods.filter(o => o.status === 'ready').length;\u001b[39m\n\u001b[31m  prodEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodPending ? 'warn' : 'ok'}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodPending ? 'amber' : 'green'}\\\">${prodPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Pending Orders</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${prodActive}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">In Production</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodReady ? 'ok' : ''}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodReady ? 'green' : ''}\\\">${prodReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Ready to Dispatch</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Installation ─────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const activeJobs  = instJobs.filter(j => j.status === 'active').length;\u001b[39m\n\u001b[31m  const ctrsPending = instCtrs.filter(c => c.status === 'recce_pending' || c.status === 'mockup_pending' || c.status === 'unclear').length;\u001b[39m\n\u001b[31m  const ctrsReady   = instCtrs.filter(c => c.status === 'ready').length;\u001b[39m\n\u001b[31m  instEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${activeJobs ? 'info' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${activeJobs ? 'blue' : 'green'}\\\">${activeJobs}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Jobs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsPending ? 'warn' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsPending ? 'amber' : 'green'}\\\">${ctrsPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Pending</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsReady ? 'ok' : ''}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsReady ? 'green' : ''}\\\">${ctrsReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Ready</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── HR ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31m  if (hrEl) {\u001b[39m\n\u001b[31m    const totalEmp   = hrEmp.length || 0;\u001b[39m\n\u001b[31m    const presentCnt = hrAtt.length;\u001b[39m\n\u001b[31m    const attPct     = totalEmp > 0 ? Math.round(presentCnt / totalEmp * 100) : 0;\u001b[39m\n\u001b[31m    const attColor   = attPct >= 80 ? 'green' : attPct >= 50 ? 'amber' : 'red';\u001b[39m\n\u001b[31m    const attCard    = attPct >= 80 ? 'ok' : attPct >= 50 ? 'warn' : 'alert';\u001b[39m\n\u001b[31m    const concernCnt = hrConcern.length;\u001b[39m\n\u001b[31m    const slipsDue   = hrSlips.length;\u001b[39m\n\u001b[31m    hrEl.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${attCard}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${attColor}\\\">${attPct}%</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Attendance Today</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${concernCnt ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${concernCnt ? 'amber' : 'green'}\\\">${concernCnt}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Open Concerns</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${slipsDue ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${slipsDue ? 'amber' : 'green'}\\\">${slipsDue}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Slips to Pay</div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Wire tap-to-navigate on all KPI cards\u001b[39m\n\u001b[31m  document.querySelectorAll('.kpi-card[data-href]').forEach(card => {\u001b[39m\n\u001b[31m    card.addEventListener('click', () => window.location.href = card.dataset.href);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction firstOfMonth() {\u001b[39m\n\u001b[31m  const d = new Date();\u001b[39m\n\u001b[31m  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGOUT ── */\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  pinBuffer   = '';\u001b[39m\n\u001b[31m  pendingShare = false;\u001b[39m\n\u001b[31m  _loginAttempts = 0; _lockoutUntil = 0; _loginInFlight = false;\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  document.getElementById('login-name').value = '';\u001b[39m\n\u001b[31m  document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m  showScreen('login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── TEAM PINs ── */\u001b[39m\n\u001b[31masync function enterTeamPins() {\u001b[39m\n\u001b[31m  showScreen('team');\u001b[39m\n\u001b[31m  await loadTeamPins();\u001b[39m\n\u001b[31m  // Show icon admin section for harish/pramod\u001b[39m\n\u001b[31m  const sec = document.getElementById('icon-admin-section');\u001b[39m\n\u001b[31m  if (sec) {\u001b[39m\n\u001b[31m    sec.style.display = MGMT.includes(currentUser.id) ? 'block' : 'none';\u001b[39m\n\u001b[31m    if (MGMT.includes(currentUser.id)) refreshSecretDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── ICON ADMIN ────────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst ICON_SECRET_KEY    = 'lm360_icon_secret';\u001b[39m\n\u001b[31mconst ICON_SECRET_INITIAL = 'nlr8Zsqw6XsD0rEXPQ_RfpYiOeNhH-9VglYKoIsElHE';·\u001b[39m\n\u001b[31mfunction getIconSecret() {\u001b[39m\n\u001b[31m  return localStorage.getItem(ICON_SECRET_KEY) || ICON_SECRET_INITIAL;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction setIconSecret(s) {\u001b[39m\n\u001b[31m  localStorage.setItem(ICON_SECRET_KEY, s);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet secretVisible = false;\u001b[39m\n\u001b[31mfunction refreshSecretDisplay() {\u001b[39m\n\u001b[31m  const el = document.getElementById('icon-secret-display');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  const s = getIconSecret();\u001b[39m\n\u001b[31m  el.textContent = secretVisible ? s : s.slice(0,4) + '••••••••••••••••' + s.slice(-4);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleSecretVisible() {\u001b[39m\n\u001b[31m  secretVisible = !secretVisible;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-secret-show');\u001b[39m\n\u001b[31m  if (btn) btn.textContent = secretVisible ? 'Hide' : 'Show';\u001b[39m\n\u001b[31m  refreshSecretDisplay();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function changeIconSecret() {\u001b[39m\n\u001b[31m  const newSecret = document.getElementById('icon-new-secret').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-secret-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (newSecret.length < 16) { errEl.textContent = 'Minimum 16 characters'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/change-secret', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret(), 'X-New-Secret': newSecret }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Failed');\u001b[39m\n\u001b[31m    setIconSecret(newSecret);\u001b[39m\n\u001b[31m    document.getElementById('icon-new-secret').value = '';\u001b[39m\n\u001b[31m    refreshSecretDisplay();\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = '✓ Secret updated';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mdocument.addEventListener('DOMContentLoaded', () => {\u001b[39m\n\u001b[31m  const fileInput = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  if (fileInput) {\u001b[39m\n\u001b[31m    fileInput.addEventListener('change', function() {\u001b[39m\n\u001b[31m      const wrap = document.getElementById('icon-preview-wrap');\u001b[39m\n\u001b[31m      const img  = document.getElementById('icon-preview-img');\u001b[39m\n\u001b[31m      if (this.files[0]) { img.src = URL.createObjectURL(this.files[0]); wrap.style.display = 'block'; }\u001b[39m\n\u001b[31m      else { wrap.style.display = 'none'; }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function uploadIcon() {\u001b[39m\n\u001b[31m  const pwa   = document.getElementById('icon-pwa-select').value;\u001b[39m\n\u001b[31m  const size  = document.getElementById('icon-size-select').value;\u001b[39m\n\u001b[31m  const input = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-upload-err');\u001b[39m\n\u001b[31m  errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (!input.files[0]) { errEl.textContent = 'Choose an image file first'; return; }\u001b[39m\n\u001b[31m  const formData = new FormData();\u001b[39m\n\u001b[31m  formData.append('pwa', pwa);\u001b[39m\n\u001b[31m  formData.append('size', size);\u001b[39m\n\u001b[31m  formData.append('icon', input.files[0]);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret() },\u001b[39m\n\u001b[31m      body: formData\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Upload failed');\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = `✓ Icon updated for ${pwa} (${size}px). Clear browser cache / reinstall to see new icon.`;\u001b[39m\n\u001b[31m    input.value = '';\u001b[39m\n\u001b[31m    document.getElementById('icon-preview-wrap').style.display = 'none';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadTeamPins() {\u001b[39m\n\u001b[31m  const list = document.getElementById('team-list');\u001b[39m\n\u001b[31m  list.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    list.innerHTML = '';\u001b[39m\n\u001b[31m    emps.forEach(emp => {\u001b[39m\n\u001b[31m      const row = document.createElement('div');\u001b[39m\n\u001b[31m      row.className = 'emp-row';\u001b[39m\n\u001b[31m      row.innerHTML = `\u001b[39m\n\u001b[31m        <div style=\\\"min-width:0;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:15px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.name}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.role}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-secondary btn-sm\\\" data-id=\\\"${emp.id}\\\" data-name=\\\"${emp.name}\\\">Set PIN</button>`;\u001b[39m\n\u001b[31m      row.querySelector('button').addEventListener('click', () => openSetPin(emp.id, emp.name));\u001b[39m\n\u001b[31m      list.appendChild(row);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    list.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openSetPin(targetId, targetName) {\u001b[39m\n\u001b[31m  setPinTarget = targetId;\u001b[39m\n\u001b[31m  document.getElementById('setpin-target-name').textContent = targetName;\u001b[39m\n\u001b[31m  ['setpin-new','setpin-confirm','setpin-auth'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-setpin').classList.add('show');\u001b[39m\n\u001b[31m  setTimeout(() => document.getElementById('setpin-new').focus(), 50);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function doSetPin() {\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('setpin-new').value.trim();\u001b[39m\n\u001b[31m  const conf    = document.getElementById('setpin-confirm').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('setpin-auth').value.trim();\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('setpin-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  if (!newPin || !conf || !authPin) { errEl.textContent = 'All fields are required'; return; }\u001b[39m\n\u001b[31m  if (newPin !== conf)              { errEl.textContent = 'New PINs do not match'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))     { errEl.textContent = 'PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-setpin-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_admin_id:  currentUser.id,\u001b[39m\n\u001b[31m        p_admin_pin: authPin,\u001b[39m\n\u001b[31m        p_target_id: setPinTarget,\u001b[39m\n\u001b[31m        p_new_pin:   newPin,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const err = await resp.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Failed to set PIN';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m    toast('✅ PIN updated for ' + document.getElementById('setpin-target-name').textContent);\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Set PIN';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ═══════════════════════════════════════════\u001b[39m\n\u001b[31m   JOBS — Campaign + Tour management\u001b[39m\n\u001b[31m   Access: harish, pramod (campaigns + tours)\u001b[39m\n\u001b[31m           rakesh (tours only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ */\u001b[39m\n\u001b[31mconst INST_RD = { 'Accept-Profile': 'installation' };\u001b[39m\n\u001b[31mconst INST_WR = { 'Content-Type':'application/json','Accept-Profile':'installation','Content-Profile':'installation' };·\u001b[39m\n\u001b[31mlet _currentCampaignId = null;\u001b[39m\n\u001b[31mlet _allEmployees      = [];\u001b[39m\n\u001b[31mlet _editTourId        = null;  // null = create mode, string = edit mode·\u001b[39m\n\u001b[31m// ── Helpers ──────────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _slugify(s) {\u001b[39m\n\u001b[31m  return s.toUpperCase().trim()\u001b[39m\n\u001b[31m    .replace(/[^A-Z0-9]+/g, '_')\u001b[39m\n\u001b[31m    .replace(/^_|_$/g, '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _buildCampaignId(year, brand, client, campaign, phase) {\u001b[39m\n\u001b[31m  const nn = String(phase || 1).padStart(2, '0');\u001b[39m\n\u001b[31m  return `${year}-${_slugify(brand)}-${_slugify(client)}-${_slugify(campaign)}-${nn}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _campStatusBadge(s) {\u001b[39m\n\u001b[31m  const map = { active:'ok', completed:'', on_hold:'warn' };\u001b[39m\n\u001b[31m  const col = { active:'green', completed:'', on_hold:'amber' };\u001b[39m\n\u001b[31m  return `<span class=\\\"kpi-val ${col[s]||''}\\\" style=\\\"font-size:12px;font-weight:700\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _tourStatusBadge(s) {\u001b[39m\n\u001b[31m  const colors = { active:'var(--success)', planned:'var(--info)', completed:'var(--muted)', cancelled:'var(--danger)' };\u001b[39m\n\u001b[31m  return `<span style=\\\"font-size:11px;font-weight:700;padding:2px 8px;border-radius:10px;\u001b[39m\n\u001b[31m    background:var(--card);color:${colors[s]||'var(--muted)'}\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _canCreateCampaign() { return ['harish','pramod'].includes(currentUser?.id); }\u001b[39m\n\u001b[31mfunction _canCreateTour()     { return ['harish','pramod','rakesh'].includes(currentUser?.id); }·\u001b[39m\n\u001b[31m// ── Jobs screen ───────────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterJobs() {\u001b[39m\n\u001b[31m  showScreen('jobs');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').style.display = _canCreateCampaign() ? '' : 'none';\u001b[39m\n\u001b[31m  await loadJobs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadJobs() {\u001b[39m\n\u001b[31m  const el = document.getElementById('jobs-body');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?order=created_at.desc`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?select=tour_id,campaign_id,status`, { headers: INST_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const campaigns = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours     = tResp.ok ? await tResp.json() : [];·\u001b[39m\n\u001b[31m    if (!campaigns.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty\\\"><div style=\\\"font-size:32px\\\">📋</div><div>No campaigns yet</div></div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    el.innerHTML = campaigns.map(c => {\u001b[39m\n\u001b[31m      const cTours  = tours.filter(t => t.campaign_id === c.campaign_id);\u001b[39m\n\u001b[31m      const active  = cTours.filter(t => t.status === 'active').length;\u001b[39m\n\u001b[31m      const total   = cTours.length;\u001b[39m\n\u001b[31m      const statusColors = { active:'var(--success)', on_hold:'var(--accent2)', completed:'var(--muted)' };\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card tappable\\\" style=\\\"padding:14px\\\" onclick=\\\"enterCampaign('${c.campaign_id}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:flex-start;gap:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:15px;font-weight:700\\\">${c.brand} — ${c.campaign_name}\u001b[39m\n\u001b[31m                ${c.phase > 1 ? `<span style=\\\"font-size:11px;color:var(--muted)\\\"> Phase ${c.phase}</span>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:2px\\\">${c.client} · ${c.year}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:4px\\\">\u001b[39m\n\u001b[31m                ${total} tour${total!==1?'s':''} · ${active} active\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;align-items:flex-end;gap:6px\\\">\u001b[39m\n\u001b[31m              <span style=\\\"font-size:11px;font-weight:700;color:${statusColors[c.status]||'var(--muted)'}\\\">\u001b[39m\n\u001b[31m                ${(c.status||'').replace('_',' ')}\u001b[39m\n\u001b[31m              </span>\u001b[39m\n\u001b[31m              <span style=\\\"font-size:18px;color:var(--muted)\\\">›</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-top:8px;word-break:break-all\\\">${c.campaign_id}</div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign detail ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterCampaign(campaignId) {\u001b[39m\n\u001b[31m  _currentCampaignId = campaignId;\u001b[39m\n\u001b[31m  showScreen('campaign');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').style.display = _canCreateTour() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-sub').textContent   = '';\u001b[39m\n\u001b[31m  await loadCampaign(campaignId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCampaign(campaignId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('campaign-body');\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp, empResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(campaignId)}&order=start_date`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const camps = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours = tResp.ok ? await tResp.json() : [];\u001b[39m\n\u001b[31m    _allEmployees = empResp.ok ? await empResp.json() : [];·\u001b[39m\n\u001b[31m    if (!camps.length) { body.innerHTML = '<div class=\\\"empty\\\">Campaign not found</div>'; return; }\u001b[39m\n\u001b[31m    const c = camps[0];·\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-title').textContent = `${c.brand} — ${c.campaign_name}`;\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-sub').textContent   = `${c.client} · ${c.year}${c.phase>1?' · Phase '+c.phase:''}`;·\u001b[39m\n\u001b[31m    const empMap = {};\u001b[39m\n\u001b[31m    _allEmployees.forEach(e => empMap[e.id] = e.name);·\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"padding:12px 14px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Campaign ID</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:10px;color:var(--muted);word-break:break-all;text-align:right;max-width:65%\\\">${c.campaign_id}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Client</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:13px;font-weight:600\\\">${c.client}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${_canCreateCampaign() ? `\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div class=\\\"section-title\\\" style=\\\"margin-bottom:6px\\\">Campaign Status</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m            ${['active','on_hold','completed'].map(s =>\u001b[39m\n\u001b[31m              `<button class=\\\"status-chip${c.status===s?' sel':''}\\\" onclick=\\\"updateCampaignStatus('${c.campaign_id}','${s}')\\\">\u001b[39m\n\u001b[31m                ${s.replace('_',' ')}\u001b[39m\n\u001b[31m              </button>`).join('')}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>` : ''}\u001b[39m\n\u001b[31m      </div>·\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:4px\\\">Tours (${tours.length})</div>\u001b[39m\n\u001b[31m      ${tours.length === 0 ? `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\">\u001b[39m\n\u001b[31m        <div>No tours yet</div>\u001b[39m\n\u001b[31m        ${_canCreateTour() ? '<div style=\\\"font-size:12px\\\">Tap ＋ to add the first tour</div>' : ''}\u001b[39m\n\u001b[31m      </div>` :\u001b[39m\n\u001b[31m      tours.map(t => {\u001b[39m\n\u001b[31m        const empNames = (t.assigned_employees || []).map(id => empMap[id] || id).join(', ') || 'No one assigned';\u001b[39m\n\u001b[31m        const dates    = t.start_date ? `${t.start_date} → ${t.end_date||'?'}` : 'Dates not set';\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m          <div class=\\\"card\\\" style=\\\"padding:14px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;align-items:flex-start;gap:8px\\\">\u001b[39m\n\u001b[31m              <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m                <div style=\\\"font-size:14px;font-weight:700\\\">${t.title || t.tour_id}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">📅 ${dates}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">👥 ${empNames}</div>\u001b[39m\n\u001b[31m                ${t.notes ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">📝 ${t.notes}</div>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              ${_tourStatusBadge(t.status)}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            ${_canCreateTour() ? `\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;gap:6px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m              ${['planned','active','completed'].map(s =>\u001b[39m\n\u001b[31m                `<button class=\\\"status-chip${t.status===s?' sel':''}\\\"\u001b[39m\n\u001b[31m                  onclick=\\\"updateTourStatus('${t.tour_id}','${s}')\\\">${s}</button>`).join('')}\u001b[39m\n\u001b[31m              <button class=\\\"status-chip\\\" onclick=\\\"openEditTour('${t.tour_id}')\\\"\u001b[39m\n\u001b[31m                style=\\\"margin-left:auto;background:var(--surface);border-color:#3a3a6e;color:#8888ff\\\">\u001b[39m\n\u001b[31m                ✏️ Edit\u001b[39m\n\u001b[31m              </button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m            <div style=\\\"font-size:10px;color:var(--muted);margin-top:6px\\\">${t.tour_id}</div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m      <div style=\\\"height:20px\\\"></div>`;\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateTourStatus(tourId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Tour marked ${status}`);\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateCampaignStatus(campaignId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Campaign marked ${status.replace('_',' ')}`);\u001b[39m\n\u001b[31m    loadCampaign(campaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Campaign modal ────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewCampaign() {\u001b[39m\n\u001b[31m  if (!_canCreateCampaign()) { toast('Only Harish or Pramod can create campaigns'); return; }\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('nc-year').value    = new Date().getFullYear();\u001b[39m\n\u001b[31m  document.getElementById('nc-phase').value   = '1';\u001b[39m\n\u001b[31m  document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  document.getElementById('nc-err').textContent     = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-new-campaign').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateCampaignPreview() {\u001b[39m\n\u001b[31m  const year  = document.getElementById('nc-year').value;\u001b[39m\n\u001b[31m  const brand = document.getElementById('nc-brand').value;\u001b[39m\n\u001b[31m  const clt   = document.getElementById('nc-client').value;\u001b[39m\n\u001b[31m  const camp  = document.getElementById('nc-campaign').value;\u001b[39m\n\u001b[31m  const phase = document.getElementById('nc-phase').value;\u001b[39m\n\u001b[31m  if (brand && clt && camp) {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = _buildCampaignId(year, brand, clt, camp, phase);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewCampaign() {\u001b[39m\n\u001b[31m  const year   = Number(document.getElementById('nc-year').value);\u001b[39m\n\u001b[31m  const brand  = document.getElementById('nc-brand').value.trim();\u001b[39m\n\u001b[31m  const client = document.getElementById('nc-client').value.trim();\u001b[39m\n\u001b[31m  const cname  = document.getElementById('nc-campaign').value.trim();\u001b[39m\n\u001b[31m  const phase  = Number(document.getElementById('nc-phase').value) || 1;\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('nc-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const brandEl = document.getElementById('nc-brand');\u001b[39m\n\u001b[31m  const clientEl = document.getElementById('nc-client');\u001b[39m\n\u001b[31m  const cnameEl = document.getElementById('nc-campaign');\u001b[39m\n\u001b[31m  [brandEl, clientEl, cnameEl].forEach(el => el.style.borderColor = '');\u001b[39m\n\u001b[31m  let _missingField = false;\u001b[39m\n\u001b[31m  if (!brand)  { brandEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!client) { clientEl.style.borderColor = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!cname)  { cnameEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (_missingField) { errEl.textContent = 'Brand, Client and Campaign Name are required'; return; }\u001b[39m\n\u001b[31m  const campaignId = _buildCampaignId(year, brand, client, cname, phase);·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nc-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/campaigns`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ campaign_id:campaignId, year, brand, client, campaign_name:cname, phase, created_by:currentUser.id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || 'Failed to create';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show');\u001b[39m\n\u001b[31m    toast(`✅ Campaign created`);\u001b[39m\n\u001b[31m    loadJobs();\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Create'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Tour modal ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _buildEmpCheckboxes(assigned = []) {\u001b[39m\n\u001b[31m  return _allEmployees.map(e =>\u001b[39m\n\u001b[31m    `<label style=\\\"display:flex;align-items:center;gap:10px;padding:8px 10px;background:var(--card);\u001b[39m\n\u001b[31m      border:1px solid var(--border);border-radius:8px;cursor:pointer\\\">\u001b[39m\n\u001b[31m      <input type=\\\"checkbox\\\" value=\\\"${e.id}\\\" ${assigned.includes(e.id)?'checked':''} style=\\\"width:16px;height:16px;accent-color:var(--accent)\\\">\u001b[39m\n\u001b[31m      <span style=\\\"font-size:14px;font-weight:600\\\">${e.name}</span>\u001b[39m\n\u001b[31m      <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${e.role}</span>\u001b[39m\n\u001b[31m    </label>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openNewTour() {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = null;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '🗺️ New Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Create Tour';\u001b[39m\n\u001b[31m  document.getElementById('nt-title').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-start').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-end').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-notes').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(_currentCampaignId)}&select=tour_id`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const existing = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    const nn = String(existing.length + 1).padStart(2, '0');\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T${nn}`;\u001b[39m\n\u001b[31m  } catch(_) {\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T01`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (_allEmployees.length)\u001b[39m\n\u001b[31m    document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes();·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEditTour(tourId) {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = tourId;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '✏️ Edit Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Save Changes';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const tours = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    if (!tours.length) { toast('Tour not found'); return; }\u001b[39m\n\u001b[31m    const t = tours[0];\u001b[39m\n\u001b[31m    document.getElementById('nt-title').value = t.title || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-start').value = t.start_date || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-end').value   = t.end_date   || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-notes').value = t.notes      || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = tourId;\u001b[39m\n\u001b[31m    if (_allEmployees.length)\u001b[39m\n\u001b[31m      document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes(t.assigned_employees || []);\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to load tour'); return; }·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewTour() {\u001b[39m\n\u001b[31m  const title = document.getElementById('nt-title').value.trim();\u001b[39m\n\u001b[31m  const start = document.getElementById('nt-start').value || null;\u001b[39m\n\u001b[31m  const end   = document.getElementById('nt-end').value   || null;\u001b[39m\n\u001b[31m  const notes = document.getElementById('nt-notes').value.trim() || null;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('nt-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const checked = [...document.querySelectorAll('#nt-employees input:checked')].map(cb => cb.value);\u001b[39m\n\u001b[31m  if (!checked.length) { errEl.textContent = 'Assign at least one employee'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nt-save');\u001b[39m\n\u001b[31m  const wasEdit = !!_editTourId;\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = wasEdit ? 'Saving…' : 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editTourId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(_editTourId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      const tourId = document.getElementById('nt-id-preview').textContent;\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          tour_id: tourId, campaign_id: _currentCampaignId,\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes, status: 'planned',\u001b[39m\n\u001b[31m          created_by: currentUser.id,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || (_editTourId ? 'Failed to save' : 'Failed to create');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show');\u001b[39m\n\u001b[31m    toast(_editTourId ? '✅ Tour updated' : '✅ Tour created');\u001b[39m\n\u001b[31m    _editTourId = null;\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = wasEdit ? 'Save Changes' : 'Create Tour';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── EVENT WIRING ── */\u001b[39m\n\u001b[31mfunction wireEvents() {\u001b[39m\n\u001b[31m  // Jobs screens\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-back').addEventListener('click',     () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-refresh').addEventListener('click',  loadJobs);\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').addEventListener('click',  openNewCampaign);\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-back').addEventListener('click', () => showScreen('jobs'));\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-refresh').addEventListener('click', () => loadCampaign(_currentCampaignId));\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').addEventListener('click',      openNewTour);·\u001b[39m\n\u001b[31m  // New Campaign modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-save').addEventListener('click', saveNewCampaign);\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign','nc-year','nc-phase'].forEach(id =>\u001b[39m\n\u001b[31m    document.getElementById(id).addEventListener('input', updateCampaignPreview));·\u001b[39m\n\u001b[31m  // New Tour modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').addEventListener('click', saveNewTour);·\u001b[39m\n\u001b[31m  document.getElementById('btn-team-back').addEventListener('click', () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-team-refresh').addEventListener('click', loadTeamPins);\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-cancel').addEventListener('click', () => {\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-save').addEventListener('click', doSetPin);·\u001b[39m\n\u001b[31m  // Set PIN — progressive field reveal\u001b[39m\n\u001b[31m  document.getElementById('setpin-new').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const val = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const showConf = val.length === 4;\u001b[39m\n\u001b[31m    document.getElementById('setpin-confirm-group').style.display = showConf ? '' : 'none';\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m    if (!showConf) { document.getElementById('setpin-confirm').value = ''; document.getElementById('setpin-auth').value = ''; }\u001b[39m\n\u001b[31m    if (showConf) setTimeout(() => document.getElementById('setpin-confirm').focus(), 20);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const newVal = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const conf   = document.getElementById('setpin-confirm').value;\u001b[39m\n\u001b[31m    const showAuth = conf.length === 4 && conf === newVal;\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = showAuth ? '' : 'none';\u001b[39m\n\u001b[31m    if (!showAuth) document.getElementById('setpin-auth').value = '';\u001b[39m\n\u001b[31m    if (showAuth) setTimeout(() => document.getElementById('setpin-auth').focus(), 20);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // Hub AI config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-save').addEventListener('click',   saveHubAiConfig);\u001b[39m\n\u001b[31m  document.getElementById('btn-check-keys').addEventListener('click',    checkApiKeyStatus);·\u001b[39m\n\u001b[31m  // Hub QA config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-qa').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-save').addEventListener('click', saveHubQaConfig);·\u001b[39m\n\u001b[31m  // Share picker\u001b[39m\n\u001b[31m  document.getElementById('share-to-expense').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/expense/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-recce').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/recce/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-installation').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/installation/current/?share=1';\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-04: employee dropdown change resets PIN buffer and lockout\u001b[39m\n\u001b[31m  document.getElementById('login-name').addEventListener('change', () => {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-07: ESC key closes any open modal\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (e.key === 'Escape') {\u001b[39m\n\u001b[31m      document.querySelectorAll('.modal-overlay.show').forEach(m => m.classList.remove('show'));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-11: keyboard digit/backspace input on login screen\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (!document.getElementById('screen-login').classList.contains('active')) return;\u001b[39m\n\u001b[31m    if (e.key >= '0' && e.key <= '9') { handlePinKey(e.key); }\u001b[39m\n\u001b[31m    else if (e.key === 'Backspace') { e.preventDefault(); handlePinKey('B'); }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-02/B-08: re-check session TTL when user returns to the tab\u001b[39m\n\u001b[31m  document.addEventListener('visibilitychange', () => {\u001b[39m\n\u001b[31m    if (document.visibilityState === 'visible' && currentUser) {\u001b[39m\n\u001b[31m      if (!loadSession()) doLogout();\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── BOOT ── */\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  wireEvents();·\u001b[39m\n\u001b[31m  if (location.search.includes('share=1')) {\u001b[39m\n\u001b[31m    pendingShare = true;\u001b[39m\n\u001b[31m    history.replaceState({}, '', '/hub/');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Always populate the login dropdown (needed if user logs out later)\u001b[39m\n\u001b[31m  initLogin(); // fire-and-forget — does not block home screen path·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess) {\u001b[39m\n\u001b[31m    currentUser = { id: sess.empId, name: sess.name, role: sess.role };\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  setTimeout(() => showScreen('login'), 1500);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SERVICE WORKER: install + update ── */\u001b[39m\n\u001b[31mif ('serviceWorker' in navigator) {\u001b[39m\n\u001b[31m  navigator.serviceWorker.register('/hub/sw.js').then(reg => {·\u001b[39m\n\u001b[31m    // Install banner: show when browser signals app is installable\u001b[39m\n\u001b[31m    let deferredInstall = null;\u001b[39m\n\u001b[31m    window.addEventListener('beforeinstallprompt', e => {\u001b[39m\n\u001b[31m      e.preventDefault();\u001b[39m\n\u001b[31m      deferredInstall = e;\u001b[39m\n\u001b[31m      if (!window.matchMedia('(display-mode: standalone)').matches) {\u001b[39m\n\u001b[31m        const delay = document.getElementById('update-banner').classList.contains('show') ? 3500 : 0;\u001b[39m\n\u001b[31m        setTimeout(() => document.getElementById('install-banner').classList.add('show'), delay);\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    window.addEventListener('appinstalled', () => {\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install').addEventListener('click', async () => {\u001b[39m\n\u001b[31m      if (!deferredInstall) return;\u001b[39m\n\u001b[31m      deferredInstall.prompt();\u001b[39m\n\u001b[31m      await deferredInstall.userChoice;\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m    // Update banner: show when a new SW version is waiting to activate\u001b[39m\n\u001b[31m    function showUpdateBanner() {\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.add('show');\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (reg.waiting) showUpdateBanner();·\u001b[39m\n\u001b[31m    reg.addEventListener('updatefound', () => {\u001b[39m\n\u001b[31m      const newSW = reg.installing;\u001b[39m\n\u001b[31m      newSW.addEventListener('statechange', () => {\u001b[39m\n\u001b[31m        if (newSW.state === 'installed' && navigator.serviceWorker.controller) {\u001b[39m\n\u001b[31m          showUpdateBanner();\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    document.getElementById('btn-update').addEventListener('click', () => {\u001b[39m\n\u001b[31m      if (reg.waiting) {\u001b[39m\n\u001b[31m        reg.waiting.postMessage({ type: 'SKIP_WAITING' });\u001b[39m\n\u001b[31m        navigator.serviceWorker.addEventListener('controllerchange', () => {\u001b[39m\n\u001b[31m          window.location.reload();\u001b[39m\n\u001b[31m        }, { once: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-update-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m  }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mboot();\u001b[39m\n\u001b[31m</script>··\u001b[39m\n\u001b[31m</body></html>\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:331:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 331
                        },
                        "snippet": "  329 |     await page.goto(BASE_URL);\n  330 |     const src = await page.content();\n> 331 |     expect(src).toContain('recce-v10');\n      |                 ^\n  332 |     expect(src).not.toContain('recce-v9');\n  333 |   });\n  334 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 331
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"recce-v10\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html><html lang=\\\"en\\\"><head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#FF6B35\\\">\u001b[39m\n\u001b[31m<meta name=\\\"mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-status-bar-style\\\" content=\\\"black-translucent\\\">\u001b[39m\n\u001b[31m<title>360LM</title>\u001b[39m\n\u001b[31m<meta name=\\\"description\\\" content=\\\"360LM Operations Hub — employee tools\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/hub/manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"icon\\\" href=\\\"/hub/icons/icon-192.png\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--surface:#161616;--card:#1e1e1e;--border:#2a2a2a;\u001b[39m\n\u001b[31m  --accent:#FF6B35;--accent2:#FFB800;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --danger:#e74c3c;--success:#2ecc71;--info:#3498db;\u001b[39m\n\u001b[31m  --radius:12px;--font:'Segoe UI',system-ui,sans-serif;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:var(--font);min-height:100vh;overflow-x:hidden}\u001b[39m\n\u001b[31m.screen{display:none;min-height:100vh;flex-direction:column}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── HEADER ── */\u001b[39m\n\u001b[31m.header{background:var(--surface);border-bottom:1px solid var(--border);padding:14px 16px;\u001b[39m\n\u001b[31m  display:flex;align-items:center;gap:12px;position:sticky;top:0;z-index:10;flex-shrink:0}\u001b[39m\n\u001b[31m.header h1{font-size:18px;font-weight:700;color:var(--accent);flex:1}\u001b[39m\n\u001b[31m.header .sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.btn-icon{background:none;border:none;color:var(--muted);font-size:22px;cursor:pointer;\u001b[39m\n\u001b[31m  padding:4px;border-radius:8px;line-height:1;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m.btn-icon:active{background:var(--border)}·\u001b[39m\n\u001b[31m/* ── BODY ── */\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:16px}·\u001b[39m\n\u001b[31m/* ── SPLASH ── */\u001b[39m\n\u001b[31m#screen-splash{align-items:center;justify-content:center;gap:8px}\u001b[39m\n\u001b[31m.splash-logo{font-size:72px;font-weight:900;color:var(--accent);letter-spacing:-2px;line-height:1}\u001b[39m\n\u001b[31m.splash-sub{font-size:15px;color:var(--muted);letter-spacing:2px;text-transform:uppercase}\u001b[39m\n\u001b[31m.splash-dot{width:8px;height:8px;border-radius:50%;background:var(--accent);\u001b[39m\n\u001b[31m  animation:pulse 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes pulse{0%,100%{opacity:.3;transform:scale(.8)}50%{opacity:1;transform:scale(1)}}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  min-height:100dvh;padding:24px;gap:20px;overflow-y:auto}\u001b[39m\n\u001b[31m.login-logo{font-size:44px;font-weight:900;color:var(--accent);letter-spacing:-1px}\u001b[39m\n\u001b[31m.login-sub{font-size:13px;color:var(--muted);text-transform:uppercase;letter-spacing:2px}\u001b[39m\n\u001b[31m.form-group{width:100%;max-width:360px;display:flex;flex-direction:column;gap:8px}\u001b[39m\n\u001b[31m.form-group label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px}\u001b[39m\n\u001b[31mselect{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31mselect:focus{border-color:var(--accent)}·\u001b[39m\n\u001b[31m/* ── PIN PAD ── */\u001b[39m\n\u001b[31m.pin-display{display:flex;gap:14px;justify-content:center;margin:4px 0}\u001b[39m\n\u001b[31m.pin-dot{width:16px;height:16px;border-radius:50%;border:2px solid var(--muted);background:transparent;transition:.15s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.pin-pad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;max-width:260px;margin:0 auto;width:100%}\u001b[39m\n\u001b[31m.pin-key{background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  font-size:22px;font-weight:600;padding:16px;border-radius:var(--radius);cursor:pointer;\u001b[39m\n\u001b[31m  text-align:center;line-height:1;user-select:none}\u001b[39m\n\u001b[31m.pin-key:active{background:var(--border)}\u001b[39m\n\u001b[31m.pin-key.clr{color:var(--danger);font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.pin-key.back{color:var(--accent2)}\u001b[39m\n\u001b[31m.login-err{font-size:13px;color:var(--danger);text-align:center;min-height:18px}\u001b[39m\n\u001b[31m@keyframes shake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-6px)}40%,80%{transform:translateX(6px)}}\u001b[39m\n\u001b[31m.pin-display.shake{animation:shake .4s ease}·\u001b[39m\n\u001b[31m/* ── KPI CARDS ── */\u001b[39m\n\u001b[31m.kpi-row{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\u001b[39m\n\u001b[31m.kpi-4col{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m#home-body{gap:10px}\u001b[39m\n\u001b[31m.kpi-card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:10px 8px;text-align:center;border-left:3px solid var(--accent);cursor:default}\u001b[39m\n\u001b[31m.kpi-card.alert{border-left-color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-card.warn{border-left-color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-card.ok{border-left-color:var(--success)}\u001b[39m\n\u001b[31m.kpi-card.info{border-left-color:var(--info)}\u001b[39m\n\u001b[31m.kpi-val{font-size:18px;font-weight:900;color:var(--accent);line-height:1.2}\u001b[39m\n\u001b[31m.kpi-val.red{color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-val.amber{color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-val.green{color:var(--success)}\u001b[39m\n\u001b[31m.kpi-val.blue{color:var(--info)}\u001b[39m\n\u001b[31m.kpi-lbl{font-size:10px;color:var(--muted);margin-top:4px;text-transform:uppercase;letter-spacing:.4px;line-height:1.3}\u001b[39m\n\u001b[31m.kpi-skel{background:var(--border);border-radius:4px;height:24px;margin-bottom:4px;\u001b[39m\n\u001b[31m  animation:shimmer 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes shimmer{0%,100%{opacity:.5}50%{opacity:1}}·\u001b[39m\n\u001b[31m/* ── SECTION TITLE ── */\u001b[39m\n\u001b[31m.section-title{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.8px}·\u001b[39m\n\u001b[31m/* ── MGMT HOME: MAIN TILES ── */\u001b[39m\n\u001b[31m.hub-main-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px}\u001b[39m\n\u001b[31m.hub-main-tile{background:var(--card);border:2px solid var(--border);border-radius:18px;\u001b[39m\n\u001b[31m  padding:36px 12px 28px;text-align:center;cursor:pointer;transition:background .15s,border-color .15s}\u001b[39m\n\u001b[31m.hub-main-tile:active{background:#1a2a1a;border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-main-icon{font-size:40px;line-height:1;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-main-label{font-size:15px;font-weight:800;color:var(--text)}\u001b[39m\n\u001b[31m/* ── MGMT HOME: QUICK ACTIONS ── */\u001b[39m\n\u001b[31m.hub-qa-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-qa-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(88px,1fr));gap:10px}\u001b[39m\n\u001b[31m.hub-qa-tile{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px 8px;text-align:center;cursor:pointer;display:flex;flex-direction:column;\u001b[39m\n\u001b[31m  align-items:center;gap:6px;transition:background .15s}\u001b[39m\n\u001b[31m.hub-qa-tile:active{background:var(--border)}\u001b[39m\n\u001b[31m.hub-qa-icon{font-size:26px;line-height:1}\u001b[39m\n\u001b[31m.hub-qa-label{font-size:11px;font-weight:700;color:var(--text);line-height:1.3}\u001b[39m\n\u001b[31m.hub-qa-item{display:flex;align-items:center;gap:12px;padding:10px 0;\u001b[39m\n\u001b[31m  border-bottom:1px solid var(--border);cursor:pointer}\u001b[39m\n\u001b[31m.hub-qa-item:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.hub-qa-item-icon{font-size:22px;width:32px;text-align:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item-label{flex:1;font-size:14px;font-weight:600}\u001b[39m\n\u001b[31m.hub-qa-item-toggle{width:20px;height:20px;border-radius:4px;border:2px solid var(--border);\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle::after{content:'✓';color:#fff;font-weight:700;font-size:12px}·\u001b[39m\n\u001b[31m/* ── TILE GRID ── */\u001b[39m\n\u001b[31m.tile-grid{display:grid;gap:12px}\u001b[39m\n\u001b[31m.tile-grid.mgmt{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m.tile-grid.field{grid-template-columns:repeat(2,1fr)}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:18px 8px;display:flex;flex-direction:column;align-items:center;gap:8px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s;position:relative}\u001b[39m\n\u001b[31m.tile:active{background:var(--border);transform:scale(.97)}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px;line-height:1}\u001b[39m\n\u001b[31m.tile-label{font-size:11px;font-weight:700;color:var(--text);text-align:center;text-transform:uppercase;letter-spacing:.4px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-icon{font-size:24px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-label{font-size:10px}\u001b[39m\n\u001b[31m.tile.coming{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.tile.coming:active{transform:none;background:var(--card)}\u001b[39m\n\u001b[31m.soon-badge{position:absolute;top:6px;right:6px;background:var(--accent2);color:#000;\u001b[39m\n\u001b[31m  font-size:8px;font-weight:800;padding:2px 5px;border-radius:20px;text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── USER BADGE ── */\u001b[39m\n\u001b[31m.user-badge{display:flex;align-items:center;gap:12px}\u001b[39m\n\u001b[31m.avatar{width:44px;height:44px;background:var(--accent);border-radius:50%;\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:800;\u001b[39m\n\u001b[31m  color:#fff;flex-shrink:0;text-transform:uppercase}\u001b[39m\n\u001b[31m.user-name{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.user-role{font-size:12px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── TEAM PINS ── */\u001b[39m\n\u001b[31m.emp-row{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px}\u001b[39m\n\u001b[31m.btn{display:flex;align-items:center;justify-content:center;gap:6px;border:none;\u001b[39m\n\u001b[31m  border-radius:var(--radius);font-size:15px;font-weight:700;padding:12px 18px;\u001b[39m\n\u001b[31m  cursor:pointer;width:100%;transition:.15s;font-family:var(--font)}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--card);border:1px solid var(--border);color:var(--text)}\u001b[39m\n\u001b[31m.btn-sm{padding:7px 12px;font-size:12px;font-weight:700;width:auto;flex-shrink:0}\u001b[39m\n\u001b[31m.info-note{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;font-size:13px;color:var(--muted);line-height:1.5}·\u001b[39m\n\u001b[31m/* ── MODAL ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:200;\u001b[39m\n\u001b[31m  align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;\u001b[39m\n\u001b[31m  padding:24px;width:100%;max-width:480px;display:flex;flex-direction:column;gap:14px;\u001b[39m\n\u001b[31m  max-height:90dvh;overflow-y:auto;-webkit-overflow-scrolling:touch}\u001b[39m\n\u001b[31m.modal-title{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.modal-box input[type=password]{\u001b[39m\n\u001b[31m  width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31m.modal-box input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.action-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px}·\u001b[39m\n\u001b[31m/* ── SHARE PICKER ── */\u001b[39m\n\u001b[31m#screen-share .body{align-items:center;justify-content:center;gap:24px}\u001b[39m\n\u001b[31m.share-prompt{font-size:15px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.share-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;width:100%;max-width:360px}\u001b[39m\n\u001b[31m.share-tile{background:var(--card);border:2px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:24px 8px;display:flex;flex-direction:column;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s}\u001b[39m\n\u001b[31m.share-tile:active{background:var(--border);transform:scale(.97);border-color:var(--accent)}\u001b[39m\n\u001b[31m.share-tile-icon{font-size:36px;line-height:1}\u001b[39m\n\u001b[31m.share-tile-label{font-size:12px;font-weight:700;color:var(--text);text-align:center;\u001b[39m\n\u001b[31m  text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── INSTALL BANNER ── */\u001b[39m\n\u001b[31m#install-banner{display:none;position:fixed;bottom:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:linear-gradient(135deg,#1a1a1a,#0d2010);\u001b[39m\n\u001b[31m  border-top:1px solid var(--accent);padding:14px 16px;\u001b[39m\n\u001b[31m  align-items:center;gap:12px;box-shadow:0 -4px 24px rgba(0,0,0,.7)}\u001b[39m\n\u001b[31m#install-banner.show{display:flex}\u001b[39m\n\u001b[31m#install-banner .ib-icon{font-size:30px;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-text{flex:1}\u001b[39m\n\u001b[31m#install-banner .ib-title{font-size:14px;font-weight:700;color:var(--text)}\u001b[39m\n\u001b[31m#install-banner .ib-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#install-banner .ib-btn{background:var(--accent);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-close{background:none;border:none;color:var(--muted);font-size:20px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── UPDATE BANNER ── */\u001b[39m\n\u001b[31m#update-banner{display:none;position:fixed;top:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:#1a2a1a;border-bottom:2px solid var(--success);\u001b[39m\n\u001b[31m  padding:12px 16px;align-items:center;gap:12px}\u001b[39m\n\u001b[31m#update-banner.show{display:flex}\u001b[39m\n\u001b[31m#update-banner .ub-text{flex:1;font-size:14px;font-weight:600;color:var(--text)}\u001b[39m\n\u001b[31m#update-banner .ub-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#update-banner .ub-btn{background:var(--success);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:8px 14px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#update-banner .ub-close{background:none;border:none;color:var(--muted);font-size:18px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#222;color:#fff;padding:10px 20px;border-radius:20px;font-size:14px;font-weight:600;\u001b[39m\n\u001b[31m  opacity:0;transition:.3s;pointer-events:none;white-space:nowrap;z-index:500}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  padding:40px;color:var(--muted);font-size:14px;gap:12px}\u001b[39m\n\u001b[31m.spinner{width:36px;height:36px;border:3px solid var(--border);border-top-color:var(--accent);\u001b[39m\n\u001b[31m  border-radius:50%;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* ── CARD (jobs / campaign screens) ── */\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius)}\u001b[39m\n\u001b[31m.card.tappable:active{border-color:var(--accent);opacity:.85}·\u001b[39m\n\u001b[31m/* ── STATUS CHIPS (tour / campaign) ── */\u001b[39m\n\u001b[31m.status-chip{font-size:11px;padding:4px 11px;border-radius:8px;cursor:pointer;\u001b[39m\n\u001b[31m  background:var(--card);color:var(--text);border:1px solid var(--border);font-weight:600;\u001b[39m\n\u001b[31m  font-family:var(--font);transition:.15s}\u001b[39m\n\u001b[31m.status-chip.sel{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.status-chip:active{opacity:.8}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SPLASH\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-splash\\\" class=\\\"screen active\\\">\u001b[39m\n\u001b[31m  <div class=\\\"splash-logo\\\">360LM</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-sub\\\">Operations Hub</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-dot\\\" style=\\\"margin-top:16px\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     LOGIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-login\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-wrap\\\">\u001b[39m\n\u001b[31m    <div class=\\\"login-logo\\\">360LM</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">Operations Hub</div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Select Your Name</label>\u001b[39m\n\u001b[31m      <select id=\\\"login-name\\\"><option value=\\\"\\\">— Choose employee —</option><option value=\\\"amit\\\">Amit</option><option value=\\\"ashok\\\">Ashok</option><option value=\\\"gurpreet\\\">Gurpreet</option><option value=\\\"harish\\\">Harish Kumar Lal</option><option value=\\\"mukesh\\\">Mukesh</option><option value=\\\"narender\\\">Narender</option><option value=\\\"pramod\\\">Pramod Narang</option><option value=\\\"rakesh\\\">Rakesh</option><option value=\\\"sachin\\\">Sachin</option><option value=\\\"shambhu\\\">Shambhu</option><option value=\\\"umashankar\\\">Umashankar</option></select>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m      <label style=\\\"text-align:center\\\">Enter PIN</label>\u001b[39m\n\u001b[31m      <div class=\\\"pin-display\\\" id=\\\"pin-display\\\">\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd0\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd1\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd2\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd3\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"login-err\\\" id=\\\"login-err\\\"></div>\u001b[39m\n\u001b[31m      <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-1\\\" data-k=\\\"1\\\" aria-label=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-2\\\" data-k=\\\"2\\\" aria-label=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-3\\\" data-k=\\\"3\\\" aria-label=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-4\\\" data-k=\\\"4\\\" aria-label=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-5\\\" data-k=\\\"5\\\" aria-label=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-6\\\" data-k=\\\"6\\\" aria-label=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-7\\\" data-k=\\\"7\\\" aria-label=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-8\\\" data-k=\\\"8\\\" aria-label=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-9\\\" data-k=\\\"9\\\" aria-label=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key clr\\\" id=\\\"pk-C\\\" data-k=\\\"C\\\" aria-label=\\\"Clear\\\">CLR</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-0\\\" data-k=\\\"0\\\" aria-label=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key back\\\" id=\\\"pk-B\\\" data-k=\\\"B\\\" aria-label=\\\"Backspace\\\">⌫</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     HOME  (built dynamically)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\" id=\\\"home-header\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"home-body\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SHARE PICKER\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-share\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>Share Image To</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">Choose where to send this photo</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"share-prompt\\\">Which tool should receive this image?</div>\u001b[39m\n\u001b[31m    <div class=\\\"share-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-expense\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🧾</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Expenses</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-recce\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">📍</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Recce</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-installation\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🔧</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Installation</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     TEAM PINs  (admin only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-team\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-back\\\" style=\\\"font-size:20px\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Team PINs</h1><div class=\\\"sub\\\">Set employee login PINs</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-refresh\\\" title=\\\"Refresh\\\">🔄</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"info-note\\\">PINs are stored encrypted — current PIN cannot be viewed. You can set a new PIN for any employee below.</div>\u001b[39m\n\u001b[31m    <div id=\\\"team-list\\\"></div>·\u001b[39m\n\u001b[31m    <!-- Icon Admin — Harish & Pramod only -->\u001b[39m\n\u001b[31m    <div id=\\\"icon-admin-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:8px\\\">🎨 PWA Icons (Admin)</div>\u001b[39m\n\u001b[31m      <div class=\\\"info-note\\\" style=\\\"margin-bottom:10px\\\">Upload a new icon for any PWA. Upload both 192×192 and 512×512 sizes. PNG/JPG/WEBP accepted — auto-resized on server.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;flex-direction:column;gap:10px\\\">·\u001b[39m\n\u001b[31m        <!-- Secret management -->\u001b[39m\n\u001b[31m        <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:12px;padding:14px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px\\\">🔑 Admin Secret</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:center;gap:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"font-family:monospace;font-size:13px;color:var(--accent);background:var(--bg);\u001b[39m\n\u001b[31m              border-radius:6px;padding:8px 12px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\" id=\\\"icon-secret-display\\\">—</div>\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"toggleSecretVisible()\\\" id=\\\"btn-secret-show\\\">Show</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:10px\\\">Change secret (min 16 chars):</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m            <input id=\\\"icon-new-secret\\\" type=\\\"text\\\" placeholder=\\\"Enter new secret…\\\" style=\\\"flex:1;background:var(--bg);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m              padding:10px 12px;border-radius:8px;font-size:13px;font-family:monospace;outline:none\\\">\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"changeIconSecret()\\\" style=\\\"white-space:nowrap\\\">Update</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div id=\\\"icon-secret-err\\\" style=\\\"font-size:11px;color:var(--danger);min-height:14px;margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>·\u001b[39m\n\u001b[31m        <!-- Upload form -->\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Select PWA</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-pwa-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"hub\\\">Hub</option>\u001b[39m\n\u001b[31m            <option value=\\\"expense\\\">Expenses</option>\u001b[39m\n\u001b[31m            <option value=\\\"finance\\\">Finance</option>\u001b[39m\n\u001b[31m            <option value=\\\"installation\\\">Installation</option>\u001b[39m\n\u001b[31m            <option value=\\\"recce\\\">Recce</option>\u001b[39m\n\u001b[31m            <option value=\\\"dispatch\\\">Dispatch</option>\u001b[39m\n\u001b[31m            <option value=\\\"production\\\">Production</option>\u001b[39m\n\u001b[31m            <option value=\\\"vehicle\\\">Vehicle Log</option>\u001b[39m\n\u001b[31m            <option value=\\\"vrs\\\">Vehicle Request</option>\u001b[39m\n\u001b[31m            <option value=\\\"sales\\\">Sales</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Icon Size</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-size-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"192\\\">192 × 192 (standard)</option>\u001b[39m\n\u001b[31m            <option value=\\\"512\\\">512 × 512 (large)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Choose Image File</label>\u001b[39m\n\u001b[31m          <input type=\\\"file\\\" id=\\\"icon-file-input\\\" accept=\\\"image/png,image/jpeg,image/webp\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:14px;width:100%\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-preview-wrap\\\" style=\\\"display:none;text-align:center\\\">\u001b[39m\n\u001b[31m          <img id=\\\"icon-preview-img\\\" style=\\\"width:96px;height:96px;border-radius:16px;border:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-upload-err\\\" style=\\\"font-size:12px;color:var(--danger);min-height:16px\\\"></div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" onclick=\\\"uploadIcon()\\\">Upload Icon</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     MODAL: Admin set PIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-setpin\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🔑 Set PIN — <span id=\\\"setpin-target-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>New PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Enter new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-confirm-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Confirm New PIN</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-confirm\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Repeat new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-auth-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Your own current PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"setpin-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-setpin-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-setpin-save\\\">Set PIN</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     JOBS — Campaign list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-jobs\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Jobs</h1><div class=\\\"sub\\\">Campaigns &amp; Tours</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh jobs\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-campaign\\\" title=\\\"New Campaign\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"jobs-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     CAMPAIGN DETAIL — Tour list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-campaign\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 id=\\\"campaign-hdr-title\\\">Campaign</h1><div class=\\\"sub\\\" id=\\\"campaign-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh campaign\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-tour\\\" title=\\\"New Tour\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"campaign-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Campaign -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-campaign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">📋 New Campaign</div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Year</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-year\\\" min=\\\"2024\\\" max=\\\"2030\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Phase #</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-phase\\\" value=\\\"1\\\" min=\\\"1\\\" max=\\\"99\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Brand <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-brand\\\" placeholder=\\\"e.g. Lenovo\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Client <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-client\\\" placeholder=\\\"e.g. Lenovo India\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Campaign Name <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-campaign\\\" placeholder=\\\"e.g. Chandigarh BTL\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Generated Campaign ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nc-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nc-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nc-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nc-save\\\">Create</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Tour -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-tour\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px;max-height:88vh;overflow-y:auto\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🗺️ New Tour</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Tour Title</div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nt-title\\\" placeholder=\\\"e.g. Chandigarh Week 1\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Start Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-start\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">End Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-end\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:6px\\\">Assign Employees <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-employees\\\" style=\\\"display:flex;flex-direction:column;gap:6px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"color:var(--muted);font-size:13px\\\">Loading…</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Notes</div>\u001b[39m\n\u001b[31m      <textarea id=\\\"nt-notes\\\" rows=\\\"2\\\" placeholder=\\\"Optional instructions or remarks\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px;font-family:inherit;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Tour ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-id-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nt-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nt-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nt-save\\\">Create Tour</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- UPDATE BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"update-banner\\\">\u001b[39m\n\u001b[31m  <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ub-text\\\">App update available</div>\u001b[39m\n\u001b[31m    <div class=\\\"ub-sub\\\">Tap to refresh and get the latest version</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ub-btn\\\" id=\\\"btn-update\\\">Update now</button>\u001b[39m\n\u001b[31m  <button class=\\\"ub-close\\\" id=\\\"btn-update-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- INSTALL BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"install-banner\\\">\u001b[39m\n\u001b[31m  <div class=\\\"ib-icon\\\">📲</div>\u001b[39m\n\u001b[31m  <div class=\\\"ib-text\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ib-title\\\">Install 360LM Hub</div>\u001b[39m\n\u001b[31m    <div class=\\\"ib-sub\\\">Add to home screen for the best experience</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ib-btn\\\" id=\\\"btn-install\\\">Install</button>\u001b[39m\n\u001b[31m  <button class=\\\"ib-close\\\" id=\\\"btn-install-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: AI Config (mgmt only) -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-ai\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🤖 AI Config — Screenshot Extraction</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Tesseract OCR</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-tesseract\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Extraction Fallback</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-enabled\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Model</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-model\\\">\u001b[39m\n\u001b[31m        <option value=\\\"haiku\\\">Haiku (fast, cost-effective)</option>\u001b[39m\n\u001b[31m        <option value=\\\"sonnet\\\">Sonnet (higher accuracy)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-ai-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-ai-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"border-top:1px solid var(--border);padding-top:12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">API Key Health</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px;margin-bottom:10px\\\">Live check — result is always the same for all users</div>\u001b[39m\n\u001b[31m      <div id=\\\"api-key-status-wrap\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-check-keys\\\" style=\\\"margin-top:10px;width:100%\\\">Check API Keys</button>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:14px;border-top:1px solid var(--border);padding-top:12px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">View Usage &amp; Billing</div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Anthropic</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/usage\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Google Maps</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/google/maps-apis/api-list\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: Hub Quick-Action Config -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-qa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">⚡ Quick Actions</div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-config-list\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-cfg-err\\\" style=\\\"color:var(--danger);font-size:13px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-qa-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-qa-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SCRIPT\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31m/* ── CONFIG ── */\u001b[39m\n\u001b[31mconst CFG = {\u001b[39m\n\u001b[31m  api:        '/db',\u001b[39m\n\u001b[31m  apiHeaders: { 'Content-Type': 'application/json', 'Accept-Profile': 'expense' },\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31m/* ── SERVER ACCESS MAP (fetched from hub-access.json, overrides ACCESS for non-mgmt) ── */\u001b[39m\n\u001b[31mlet _serverAccess = {};\u001b[39m\n\u001b[31masync function loadHubAccess() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/hub/hub-access.json?t=' + Date.now());\u001b[39m\n\u001b[31m    if (r.ok) _serverAccess = await r.json();\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── ROLE ACCESS MAP (hardcoded fallback) ── */\u001b[39m\n\u001b[31mconst ACCESS = {\u001b[39m\n\u001b[31m  harish:     ['expense','installation','finance','recce','dispatch','production','jobs','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  pramod:     ['expense','installation','finance','recce','dispatch','production','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  rakesh:     ['installation','expense','production','jobs','vehicle','vrs','sales','hr'],\u001b[39m\n\u001b[31m  ashok:      ['expense','production','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  shambhu:    ['finance','expense','vehicle','vrs','stores','activity','hr'],\u001b[39m\n\u001b[31m  gurpreet:   ['expense','dispatch','vehicle','vrs','stores','hr'],\u001b[39m\n\u001b[31m  sachin:     ['installation','expense','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  mukesh:     ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  umashankar: ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  narender:   ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  amit:       ['vehicle','vrs','hr'],\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst TILES = [\u001b[39m\n\u001b[31m  { id:'expense',      label:'Expenses',     icon:'🧾', url:'/expense/',               live:true  },\u001b[39m\n\u001b[31m  { id:'installation', label:'Installation', icon:'🔧', url:'/installation/current/',  live:true  },\u001b[39m\n\u001b[31m  { id:'finance',      label:'Finance',      icon:'💰', url:'/finance/',               live:true  },\u001b[39m\n\u001b[31m  { id:'recce',        label:'Recce',        icon:'📍', url:'/recce/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'dispatch',     label:'Dispatch',     icon:'🚚', url:'/dispatch/',              live:true  },\u001b[39m\n\u001b[31m  { id:'production',   label:'Production',   icon:'⚙️', url:'/production/',            live:true  },\u001b[39m\n\u001b[31m  { id:'jobs',         label:'Jobs',         icon:'📋', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'vehicle',      label:'Vehicle Log',  icon:'🚐', url:'/vehicle/',               live:true  },\u001b[39m\n\u001b[31m  { id:'vrs',          label:'Veh. Request', icon:'📋', url:'/vrs/',                   live:true  },\u001b[39m\n\u001b[31m  { id:'sales',        label:'Sales',        icon:'💼', url:'/sales/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'stores',       label:'Stores',       icon:'📦', url:'/stores/',                live:true  },\u001b[39m\n\u001b[31m  { id:'client',       label:'Client',       icon:'👤', url:'/client/',                live:true  },\u001b[39m\n\u001b[31m  { id:'team-pins',    label:'Team PINs',    icon:'🔑', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'admin',        label:'Admin',        icon:'🛠️', url:'/admin/',                  live:true  },\u001b[39m\n\u001b[31m  { id:'activity',     label:'Activity',     icon:'🎯', url:'/activity/',               live:true  },\u001b[39m\n\u001b[31m  { id:'hr',           label:'HR',           icon:'👥', url:'/hr/',                     live:true  },\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31mconst MGMT = ['harish', 'pramod'];·\u001b[39m\n\u001b[31m/* ── STATE ── */\u001b[39m\n\u001b[31mlet currentUser     = null;\u001b[39m\n\u001b[31mlet pinBuffer       = '';\u001b[39m\n\u001b[31mlet setPinTarget    = null;\u001b[39m\n\u001b[31mlet pendingShare    = false;\u001b[39m\n\u001b[31mlet _loginAttempts  = 0;\u001b[39m\n\u001b[31mlet _lockoutUntil   = 0;\u001b[39m\n\u001b[31mlet _loginInFlight  = false;·\u001b[39m\n\u001b[31m/* ── UTILS ── */\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => { s.classList.remove('active'); s.style.display = ''; });\u001b[39m\n\u001b[31m  document.getElementById('screen-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet toastTimer;\u001b[39m\n\u001b[31mfunction toast(msg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.classList.add('show');\u001b[39m\n\u001b[31m  clearTimeout(toastTimer);\u001b[39m\n\u001b[31m  toastTimer = setTimeout(() => el.classList.remove('show'), 2800);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fmtINR(n) {\u001b[39m\n\u001b[31m  if (n >= 100000) return '₹' + (n / 100000).toFixed(1) + 'L';\u001b[39m\n\u001b[31m  if (n >= 1000)   return '₹' + (n / 1000).toFixed(1) + 'K';\u001b[39m\n\u001b[31m  return '₹' + Math.round(n);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SESSION ── */\u001b[39m\n\u001b[31mconst SESSION_KEY  = 'lm360-session';\u001b[39m\n\u001b[31mconst EXP_SES_KEY  = '360exp-session';\u001b[39m\n\u001b[31mconst SESSION_TTL  = 12 * 3600 * 1000;·\u001b[39m\n\u001b[31mfunction saveSession(user) {\u001b[39m\n\u001b[31m  const payload = { empId: user.id, name: user.name, role: user.role, loginAt: new Date().toISOString(), source: 'hub' };\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m  localStorage.setItem(EXP_SES_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearSession() {\u001b[39m\n\u001b[31m  localStorage.removeItem(SESSION_KEY);\u001b[39m\n\u001b[31m  localStorage.removeItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const raw = localStorage.getItem(SESSION_KEY) || localStorage.getItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m    if (!raw) return null;\u001b[39m\n\u001b[31m    const s = JSON.parse(raw);\u001b[39m\n\u001b[31m    if (!s.empId || !s.name || !s.role) return null;\u001b[39m\n\u001b[31m    if (Date.now() - new Date(s.loginAt).getTime() > SESSION_TTL) { clearSession(); return null; }\u001b[39m\n\u001b[31m    return s;\u001b[39m\n\u001b[31m  } catch (_) { return null; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGIN FLOW ── */\u001b[39m\n\u001b[31masync function initLogin() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('login-name');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— Choose employee —</option>'; // clear before repopulating\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    emps.forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    [\u001b[39m\n\u001b[31m      { id:'amit',        name:'Amit'             },\u001b[39m\n\u001b[31m      { id:'ashok',       name:'Ashok'            },\u001b[39m\n\u001b[31m      { id:'gurpreet',    name:'Gurpreet'         },\u001b[39m\n\u001b[31m      { id:'harish',      name:'Harish Kumar Lal' },\u001b[39m\n\u001b[31m      { id:'mukesh',      name:'Mukesh'           },\u001b[39m\n\u001b[31m      { id:'narender',    name:'Narender'         },\u001b[39m\n\u001b[31m      { id:'pramod',      name:'Pramod Narang'    },\u001b[39m\n\u001b[31m      { id:'rakesh',      name:'Rakesh'           },\u001b[39m\n\u001b[31m      { id:'sachin',      name:'Sachin'           },\u001b[39m\n\u001b[31m      { id:'shambhu',     name:'Shambhu'          },\u001b[39m\n\u001b[31m      { id:'umashankar',  name:'Umashankar'       },\u001b[39m\n\u001b[31m    ].forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  document.querySelectorAll('.pin-key').forEach(btn =>\u001b[39m\n\u001b[31m    btn.addEventListener('click', () => handlePinKey(btn.dataset.k))\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handlePinKey(k) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (_loginInFlight) return;\u001b[39m\n\u001b[31m  // Lockout guard\u001b[39m\n\u001b[31m  if (_lockoutUntil && Date.now() < _lockoutUntil) {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    errEl.textContent = `Too many attempts — wait ${secs}s`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (_lockoutUntil) { _lockoutUntil = 0; _loginAttempts = 0; }\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (k === 'C') { pinBuffer = ''; }\u001b[39m\n\u001b[31m  else if (k === 'B') { pinBuffer = pinBuffer.slice(0, -1); }\u001b[39m\n\u001b[31m  else {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (!empId) { toast('Select your name first'); return; }\u001b[39m\n\u001b[31m    if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  if (pinBuffer.length === 4) {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (empId) attemptLogin(empId, pinBuffer);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updatePinDisplay() {\u001b[39m\n\u001b[31m  for (let i = 0; i < 4; i++)\u001b[39m\n\u001b[31m    document.getElementById('pd' + i).classList.toggle('filled', i < pinBuffer.length);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _shakePin() {\u001b[39m\n\u001b[31m  const pd = document.getElementById('pin-display');\u001b[39m\n\u001b[31m  pd.classList.add('shake');\u001b[39m\n\u001b[31m  setTimeout(() => pd.classList.remove('shake'), 400);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _startLockout() {\u001b[39m\n\u001b[31m  _lockoutUntil = Date.now() + 30000;\u001b[39m\n\u001b[31m  _loginAttempts = 0;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  const tick = () => {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    if (secs > 0) { errEl.textContent = `Too many attempts — wait ${secs}s`; setTimeout(tick, 500); }\u001b[39m\n\u001b[31m    else { _lockoutUntil = 0; errEl.textContent = ''; }\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  tick();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function attemptLogin(empId, pin) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  _loginInFlight = true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pin }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      _loginAttempts++;\u001b[39m\n\u001b[31m      _shakePin();\u001b[39m\n\u001b[31m      if (_loginAttempts >= 5) { _startLockout(); return; }\u001b[39m\n\u001b[31m      errEl.textContent = `Wrong PIN — ${5 - _loginAttempts} attempt${5 - _loginAttempts !== 1 ? 's' : ''} left`;\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    currentUser = { id: user.id, name: user.name, role: user.role };\u001b[39m\n\u001b[31m    saveSession(currentUser);\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — check connection';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    _loginInFlight = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── HOME SCREEN ── */\u001b[39m\n\u001b[31masync function buildHome() {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const body   = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  // Management users always use hardcoded ACCESS; others prefer server-side hub-access.json\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const tiles  = accessIds.map(id => TILES.find(t => t.id === id)).filter(Boolean);·\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"avatar\\\">${initials}</div>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1 style=\\\"font-size:16px\\\">${currentUser.name}</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">${currentUser.role}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${isMgmt ? '<button class=\\\"btn-icon\\\" id=\\\"btn-hub-ai\\\" title=\\\"AI Config\\\" style=\\\"font-size:18px\\\">🤖</button>' : ''}\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m  if (isMgmt) document.getElementById('btn-hub-ai').addEventListener('click', openHubAiConfig);·\u001b[39m\n\u001b[31m  if (isMgmt) {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"hub-main-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-dash\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">📊</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">Dashboard</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-tools\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">🧰</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">All Tools</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-header\\\">\u001b[39m\n\u001b[31m        <div class=\\\"section-title\\\" style=\\\"margin:0\\\">Quick Actions</div>\u001b[39m\n\u001b[31m        <button class=\\\"btn-icon\\\" id=\\\"btn-hub-qa-cfg\\\" title=\\\"Configure Quick Actions\\\" style=\\\"font-size:18px;padding:8px;min-width:44px;min-height:44px\\\">⚙️</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-grid\\\" id=\\\"hub-qa-grid\\\"></div>`;\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-dash').addEventListener('click',  () => enterHubDash(tiles));\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-tools').addEventListener('click', () => enterHubTools(tiles));\u001b[39m\n\u001b[31m    document.getElementById('btn-hub-qa-cfg').addEventListener('click', openHubQaConfig);\u001b[39m\n\u001b[31m    renderHubQA();\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"tile-grid field\\\" id=\\\"tile-grid\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"text-align:center;font-size:12px;color:var(--muted);padding-top:8px\\\">Tap a tile to open your tool</div>`;\u001b[39m\n\u001b[31m    renderTiles(tiles, 'field');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderTiles(tiles, mode) {\u001b[39m\n\u001b[31m  const grid = document.getElementById('tile-grid');\u001b[39m\n\u001b[31m  tiles.forEach(tile => {\u001b[39m\n\u001b[31m    const div = document.createElement('div');\u001b[39m\n\u001b[31m    div.className = 'tile ' + mode + (tile.live ? '' : ' coming');\u001b[39m\n\u001b[31m    div.innerHTML = `<div class=\\\"tile-icon\\\">${tile.icon}</div><div class=\\\"tile-label\\\">${tile.label}</div>` +\u001b[39m\n\u001b[31m      (tile.live ? '' : '<div class=\\\"soon-badge\\\">Soon</div>');\u001b[39m\n\u001b[31m    div.addEventListener('click', () => handleTile(tile));\u001b[39m\n\u001b[31m    grid.appendChild(div);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handleTile(tile) {\u001b[39m\n\u001b[31m  if (!tile.live) { toast('Coming soon'); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'team-pins') { enterTeamPins(); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'jobs')      { enterJobs();     return; }\u001b[39m\n\u001b[31m  window.location.href = tile.url;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT SUB-VIEWS ── */\u001b[39m\n\u001b[31mfunction _mgmtHeader(title) {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-subview-back\\\" style=\\\"font-size:20px;font-weight:900\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 style=\\\"font-size:16px\\\">${title}</h1></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-subview-back').addEventListener('click', () => buildHome());\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubDash(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('Dashboard');\u001b[39m\n\u001b[31m  const hdr = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const refreshBtn = document.createElement('button');\u001b[39m\n\u001b[31m  refreshBtn.className = 'btn-icon';\u001b[39m\n\u001b[31m  refreshBtn.title = 'Refresh dashboard';\u001b[39m\n\u001b[31m  refreshBtn.setAttribute('aria-label', 'Refresh dashboard');\u001b[39m\n\u001b[31m  refreshBtn.style.cssText = 'font-size:18px';\u001b[39m\n\u001b[31m  refreshBtn.textContent = '🔄';\u001b[39m\n\u001b[31m  refreshBtn.addEventListener('click', loadKPIs);\u001b[39m\n\u001b[31m  hdr.insertBefore(refreshBtn, hdr.querySelector('#btn-logout'));\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Actions Required</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-actions\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Operations</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row kpi-4col\\\" id=\\\"kpi-ops\\\">${skel}${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Production</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-prod\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Installation</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-inst\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">HR</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-hr\\\">${skel}${skel}${skel}</div>`;\u001b[39m\n\u001b[31m  loadKPIs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubTools(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('All Tools');\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  body.innerHTML = `<div class=\\\"tile-grid mgmt\\\" id=\\\"tile-grid\\\"></div>`;\u001b[39m\n\u001b[31m  renderTiles(tiles, 'mgmt');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT AI CONFIG ── */\u001b[39m\n\u001b[31mconst HUB_FIN_HDR = { 'Content-Type': 'application/json', 'Accept-Profile': 'finance' };·\u001b[39m\n\u001b[31masync function openHubAiConfig() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r    = await fetch(`${CFG.api}/upi_config`, { headers: HUB_FIN_HDR });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const get  = key => rows.find(x => x.key === key)?.value;\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-tesseract').value = get('tesseract_enabled') || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-enabled').value   = get('ai_enabled')        || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-model').value     = get('ai_model')           || 'haiku';\u001b[39m\n\u001b[31m  } catch(_) { toast('Could not load AI config'); return; }\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-ai').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveHubAiConfig() {\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-hub-ai-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  const pairs = [\u001b[39m\n\u001b[31m    { key:'tesseract_enabled', value: document.getElementById('hub-ai-tesseract').value },\u001b[39m\n\u001b[31m    { key:'ai_enabled',        value: document.getElementById('hub-ai-enabled').value },\u001b[39m\n\u001b[31m    { key:'ai_model',          value: document.getElementById('hub-ai-model').value },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const results = await Promise.all(pairs.map(p =>\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/upi_config?key=eq.${p.key}`, {\u001b[39m\n\u001b[31m        method: 'PATCH',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'finance', 'Prefer': 'return=minimal' },\u001b[39m\n\u001b[31m        body: JSON.stringify({ value: p.value }),\u001b[39m\n\u001b[31m      })\u001b[39m\n\u001b[31m    ));\u001b[39m\n\u001b[31m    if (results.some(r => !r.ok)) throw new Error('partial failure');\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show');\u001b[39m\n\u001b[31m    toast('✅ AI config saved');\u001b[39m\n\u001b[31m  } catch(_) { toast('Save failed'); }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Save'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function checkApiKeyStatus() {\u001b[39m\n\u001b[31m  const btn  = document.getElementById('btn-check-keys');\u001b[39m\n\u001b[31m  const wrap = document.getElementById('api-key-status-wrap');\u001b[39m\n\u001b[31m  btn.textContent = 'Checking…';\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  wrap.innerHTML = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/tour-ai/key-status');\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Proxy error ' + r.status);\u001b[39m\n\u001b[31m    const d = await r.json();\u001b[39m\n\u001b[31m    function mkBadge(info, label, link) {\u001b[39m\n\u001b[31m      const col  = info.ok ? 'var(--success)' : 'var(--danger)';\u001b[39m\n\u001b[31m      const icon = info.ok ? '✅' : '❌';\u001b[39m\n\u001b[31m      const note = info.ok ? (info.model || 'Valid') : (info.error || 'Error');\u001b[39m\n\u001b[31m      const hint = info.hint ? ' · key ' + info.hint : '';\u001b[39m\n\u001b[31m      return `<div style=\\\"background:var(--card);border:1px solid var(--border);border-left:3px solid ${col};border-radius:8px;padding:10px 12px;display:flex;align-items:center;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:13px;font-weight:600;color:var(--text)\\\">${icon} ${label}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${note}${hint}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <a href=\\\"${link}\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:11px;color:var(--info);text-decoration:none;white-space:nowrap\\\">Billing ↗</a>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    wrap.innerHTML =\u001b[39m\n\u001b[31m      mkBadge(d.anthropic, 'Anthropic API',    'https://console.anthropic.com/settings/billing') +\u001b[39m\n\u001b[31m      mkBadge(d.maps,      'Google Maps API',  'https://console.cloud.google.com/billing');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    wrap.innerHTML = `<div style=\\\"font-size:12px;color:var(--danger)\\\">Error: ${e.message}</div>`;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.textContent = 'Check Again';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT QUICK ACTIONS ── */\u001b[39m\n\u001b[31mconst HUB_QA_DEFS = [\u001b[39m\n\u001b[31m  { id:'offer',       icon:'📋', label:'New Offer',    accessId:'sales',        fn:() => { window.location.href='/sales/'; } },\u001b[39m\n\u001b[31m  { id:'paytm',       icon:'📱', label:'UPI Pay',       accessId:'finance',      fn:() => { window.location.href='/finance/?qa=upi'; } },\u001b[39m\n\u001b[31m  { id:'expense',     icon:'🧾', label:'Expenses',      accessId:'expense',      fn:() => { window.location.href='/expense/'; } },\u001b[39m\n\u001b[31m  { id:'finance',     icon:'💰', label:'Finance',       accessId:'finance',      fn:() => { window.location.href='/finance/'; } },\u001b[39m\n\u001b[31m  { id:'dispatch',    icon:'🚚', label:'Dispatch',      accessId:'dispatch',     fn:() => { window.location.href='/dispatch/'; } },\u001b[39m\n\u001b[31m  { id:'stores',      icon:'📦', label:'Stores',        accessId:'stores',       fn:() => { window.location.href='/stores/'; } },\u001b[39m\n\u001b[31m  { id:'recce',       icon:'📍', label:'Recce',         accessId:'recce',        fn:() => { window.location.href='/recce/'; } },\u001b[39m\n\u001b[31m  { id:'production',  icon:'⚙️', label:'Production',   accessId:'production',   fn:() => { window.location.href='/production/'; } },\u001b[39m\n\u001b[31m  { id:'tours',       icon:'🗺️', label:'Tour Plan',   accessId:'installation', fn:() => { window.location.href='/tour-planner/'; } },\u001b[39m\n\u001b[31m  { id:'installation',icon:'🔧', label:'Installation',  accessId:'installation', fn:() => { window.location.href='/installation/current/'; } },\u001b[39m\n\u001b[31m  { id:'activity',    icon:'🎯', label:'Activity',      accessId:'activity',     fn:() => { window.location.href='/activity/'; } },\u001b[39m\n\u001b[31m];\u001b[39m\n\u001b[31mconst HUB_QA_DEFAULT = ['offer', 'paytm'];\u001b[39m\n\u001b[31mconst HUB_QA_STORE   = '360lm-hub-qa';\u001b[39m\n\u001b[31mlet   _hubQaTempSel  = [];·\u001b[39m\n\u001b[31mfunction getHubQA() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(HUB_QA_STORE));\u001b[39m\n\u001b[31m    if (Array.isArray(s) && s.length) return s;\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m  return [...HUB_QA_DEFAULT];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHubQA() {\u001b[39m\n\u001b[31m  const ids  = getHubQA();\u001b[39m\n\u001b[31m  const grid = document.getElementById('hub-qa-grid');\u001b[39m\n\u001b[31m  if (!grid) return;\u001b[39m\n\u001b[31m  if (!ids.length) {\u001b[39m\n\u001b[31m    grid.innerHTML = `<div style=\\\"color:var(--muted);font-size:13px;grid-column:1/-1;\u001b[39m\n\u001b[31m      text-align:center;padding:16px\\\">Tap ⚙️ to add quick actions</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  grid.innerHTML = ids.map(id => {\u001b[39m\n\u001b[31m    const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!d) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-tile\\\" onclick=\\\"hubQaRun('${id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction hubQaRun(id) {\u001b[39m\n\u001b[31m  const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m  if (d) d.fn();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openHubQaConfig() {\u001b[39m\n\u001b[31m  _hubQaTempSel = [...getHubQA()];\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const visibleDefs = HUB_QA_DEFS.filter(d => accessIds.includes(d.accessId));\u001b[39m\n\u001b[31m  const list = document.getElementById('hub-qa-config-list');\u001b[39m\n\u001b[31m  list.innerHTML = visibleDefs.map(d => {\u001b[39m\n\u001b[31m    const sel = _hubQaTempSel.includes(d.id);\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-item${sel?' selected':''}\\\" id=\\\"hub-qa-item-${d.id}\\\"\u001b[39m\n\u001b[31m        onclick=\\\"toggleHubQaItem('${d.id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-toggle\\\"></div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleHubQaItem(id) {\u001b[39m\n\u001b[31m  const idx = _hubQaTempSel.indexOf(id);\u001b[39m\n\u001b[31m  if (idx >= 0) {\u001b[39m\n\u001b[31m    _hubQaTempSel.splice(idx, 1);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    if (_hubQaTempSel.length >= 6) {\u001b[39m\n\u001b[31m      document.getElementById('hub-qa-cfg-err').textContent = 'Maximum 6 quick actions';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _hubQaTempSel.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  const el = document.getElementById('hub-qa-item-' + id);\u001b[39m\n\u001b[31m  if (el) el.className = 'hub-qa-item' + (_hubQaTempSel.includes(id) ? ' selected' : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction saveHubQaConfig() {\u001b[39m\n\u001b[31m  localStorage.setItem(HUB_QA_STORE, JSON.stringify(_hubQaTempSel));\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.remove('show');\u001b[39m\n\u001b[31m  renderHubQA();\u001b[39m\n\u001b[31m  toast('✅ Quick actions updated');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── KPI FETCH ── */\u001b[39m\n\u001b[31mfunction _hdr(schema) {\u001b[39m\n\u001b[31m  return { 'Content-Type': 'application/json', 'Accept-Profile': schema };\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _safe(p) { return p.catch(() => null); }·\u001b[39m\n\u001b[31masync function loadKPIs() {\u001b[39m\n\u001b[31m  const mon = firstOfMonth();\u001b[39m\n\u001b[31m  const curMonth = new Date().toISOString().slice(0, 7); // YYYY-MM\u001b[39m\n\u001b[31m  const todayIST = (() => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; })();·\u001b[39m\n\u001b[31m  // Reset sections to loading skeletons before fetching (makes refresh visible)\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  [['kpi-actions',3],['kpi-ops',4],['kpi-prod',3],['kpi-inst',3],['kpi-hr',3]].forEach(([id,n]) => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    if (el) el.innerHTML = skel.repeat(n);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  const [\u001b[39m\n\u001b[31m    sheetsRes, claimsRes, salaryRes,\u001b[39m\n\u001b[31m    cashRes,   dispRes,   recceRes,  prodRes,\u001b[39m\n\u001b[31m    instJobsRes, instCtrsRes, storeItemsRes,\u001b[39m\n\u001b[31m    hrAttRes, hrEmpRes, hrConcernRes, hrSlipsRes\u001b[39m\n\u001b[31m  ] = await Promise.all([\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/sheets?status=in.(submitted,escalated)&select=sheet_id`, { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/approved_pending_reimbursement?select=sheet_id`,         { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_register?status=in.(pending,partial)&month=eq.${curMonth}&select=record_id`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/accounts?account_id=eq.petty-cash&select=current_balance`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/dispatches?status=eq.active&select=dispatch_id`,         { headers: _hdr('dispatch') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/submissions?submitted_at=gte.${mon}&select=sub_id`,      { headers: _hdr('recce') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/orders?status=not.in.(dispatched)&select=order_id,status`, { headers: _hdr('production') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/jobs?select=job_id,status`,                              { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/counters?select=counter_id,status`,                      { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/items?active=eq.true&select=id,current_stock,min_stock`, { headers: _hdr('stores') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance?date=eq.${todayIST}&status=eq.present&select=attendance_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/employee_list?select=id`,                               { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance_concerns?status=eq.open&select=concern_id`,  { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_slips?month=eq.${curMonth}&status=eq.finalized&select=slip_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m  ]);·\u001b[39m\n\u001b[31m  const sheets    = sheetsRes?.ok    ? await sheetsRes.json()    : [];\u001b[39m\n\u001b[31m  const claims    = claimsRes?.ok    ? await claimsRes.json()    : [];\u001b[39m\n\u001b[31m  const salary    = salaryRes?.ok    ? await salaryRes.json()    : [];\u001b[39m\n\u001b[31m  const cashArr   = cashRes?.ok      ? await cashRes.json()      : [];\u001b[39m\n\u001b[31m  const disps     = dispRes?.ok      ? await dispRes.json()      : [];\u001b[39m\n\u001b[31m  const recces    = recceRes?.ok     ? await recceRes.json()     : [];\u001b[39m\n\u001b[31m  const prods     = prodRes?.ok      ? await prodRes.json()      : [];\u001b[39m\n\u001b[31m  const instJobs  = instJobsRes?.ok  ? await instJobsRes.json()  : [];\u001b[39m\n\u001b[31m  const instCtrs  = instCtrsRes?.ok  ? await instCtrsRes.json()  : [];\u001b[39m\n\u001b[31m  const storeItems = storeItemsRes?.ok ? await storeItemsRes.json() : [];\u001b[39m\n\u001b[31m  const hrAtt     = hrAttRes?.ok     ? await hrAttRes.json()     : [];\u001b[39m\n\u001b[31m  const hrEmp     = hrEmpRes?.ok     ? await hrEmpRes.json()     : [];\u001b[39m\n\u001b[31m  const hrConcern = hrConcernRes?.ok ? await hrConcernRes.json() : [];\u001b[39m\n\u001b[31m  const hrSlips   = hrSlipsRes?.ok   ? await hrSlipsRes.json()   : [];·\u001b[39m\n\u001b[31m  const cash = Number(cashArr[0]?.current_balance || 0);·\u001b[39m\n\u001b[31m  const actionsEl = document.getElementById('kpi-actions');\u001b[39m\n\u001b[31m  const opsEl     = document.getElementById('kpi-ops');\u001b[39m\n\u001b[31m  const prodEl    = document.getElementById('kpi-prod');\u001b[39m\n\u001b[31m  const instEl    = document.getElementById('kpi-inst');\u001b[39m\n\u001b[31m  const hrEl      = document.getElementById('kpi-hr');\u001b[39m\n\u001b[31m  if (!actionsEl || !opsEl || !prodEl || !instEl) return;·\u001b[39m\n\u001b[31m  // ── Actions Required ────────────────────────────────────────────\u001b[39m\n\u001b[31m  actionsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${sheets.length ? 'alert' : 'ok'}\\\" data-href=\\\"/expense/?tab=approvals\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${sheets.length ? 'red' : 'green'}\\\">${sheets.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Expense Approvals</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${claims.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=claims\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${claims.length ? 'amber' : 'green'}\\\">${claims.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Claims to Reimburse</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${salary.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=salary\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${salary.length ? 'amber' : 'green'}\\\">${salary.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Salary Due This Month</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Operations ──────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const cashColor = cash < 2000 ? 'red' : cash < 5000 ? 'amber' : 'green';\u001b[39m\n\u001b[31m  const cashCard  = cash < 2000 ? 'alert' : cash < 5000 ? 'warn' : 'ok';\u001b[39m\n\u001b[31m  const lowStock = storeItems.filter(i => i.current_stock < i.min_stock).length;\u001b[39m\n\u001b[31m  opsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${cashCard}\\\" data-href=\\\"/finance/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${cashColor}\\\">${fmtINR(cash)}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">${cash < 0 ? '⚠️ ' : ''}Petty Cash</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/dispatch/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${disps.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Dispatches</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/recce/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${recces.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Recce This Month</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${lowStock > 0 ? 'alert' : 'ok'}\\\" data-href=\\\"/stores/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${lowStock > 0 ? 'red' : 'green'}\\\">${lowStock}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Low Stock Items</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Production ───────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const prodPending = prods.filter(o => o.status === 'pending').length;\u001b[39m\n\u001b[31m  const prodActive  = prods.filter(o => o.status === 'in_production' || o.status === 'quality_check').length;\u001b[39m\n\u001b[31m  const prodReady   = prods.filter(o => o.status === 'ready').length;\u001b[39m\n\u001b[31m  prodEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodPending ? 'warn' : 'ok'}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodPending ? 'amber' : 'green'}\\\">${prodPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Pending Orders</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${prodActive}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">In Production</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodReady ? 'ok' : ''}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodReady ? 'green' : ''}\\\">${prodReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Ready to Dispatch</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Installation ─────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const activeJobs  = instJobs.filter(j => j.status === 'active').length;\u001b[39m\n\u001b[31m  const ctrsPending = instCtrs.filter(c => c.status === 'recce_pending' || c.status === 'mockup_pending' || c.status === 'unclear').length;\u001b[39m\n\u001b[31m  const ctrsReady   = instCtrs.filter(c => c.status === 'ready').length;\u001b[39m\n\u001b[31m  instEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${activeJobs ? 'info' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${activeJobs ? 'blue' : 'green'}\\\">${activeJobs}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Jobs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsPending ? 'warn' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsPending ? 'amber' : 'green'}\\\">${ctrsPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Pending</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsReady ? 'ok' : ''}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsReady ? 'green' : ''}\\\">${ctrsReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Ready</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── HR ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31m  if (hrEl) {\u001b[39m\n\u001b[31m    const totalEmp   = hrEmp.length || 0;\u001b[39m\n\u001b[31m    const presentCnt = hrAtt.length;\u001b[39m\n\u001b[31m    const attPct     = totalEmp > 0 ? Math.round(presentCnt / totalEmp * 100) : 0;\u001b[39m\n\u001b[31m    const attColor   = attPct >= 80 ? 'green' : attPct >= 50 ? 'amber' : 'red';\u001b[39m\n\u001b[31m    const attCard    = attPct >= 80 ? 'ok' : attPct >= 50 ? 'warn' : 'alert';\u001b[39m\n\u001b[31m    const concernCnt = hrConcern.length;\u001b[39m\n\u001b[31m    const slipsDue   = hrSlips.length;\u001b[39m\n\u001b[31m    hrEl.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${attCard}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${attColor}\\\">${attPct}%</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Attendance Today</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${concernCnt ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${concernCnt ? 'amber' : 'green'}\\\">${concernCnt}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Open Concerns</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${slipsDue ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${slipsDue ? 'amber' : 'green'}\\\">${slipsDue}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Slips to Pay</div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Wire tap-to-navigate on all KPI cards\u001b[39m\n\u001b[31m  document.querySelectorAll('.kpi-card[data-href]').forEach(card => {\u001b[39m\n\u001b[31m    card.addEventListener('click', () => window.location.href = card.dataset.href);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction firstOfMonth() {\u001b[39m\n\u001b[31m  const d = new Date();\u001b[39m\n\u001b[31m  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGOUT ── */\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  pinBuffer   = '';\u001b[39m\n\u001b[31m  pendingShare = false;\u001b[39m\n\u001b[31m  _loginAttempts = 0; _lockoutUntil = 0; _loginInFlight = false;\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  document.getElementById('login-name').value = '';\u001b[39m\n\u001b[31m  document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m  showScreen('login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── TEAM PINs ── */\u001b[39m\n\u001b[31masync function enterTeamPins() {\u001b[39m\n\u001b[31m  showScreen('team');\u001b[39m\n\u001b[31m  await loadTeamPins();\u001b[39m\n\u001b[31m  // Show icon admin section for harish/pramod\u001b[39m\n\u001b[31m  const sec = document.getElementById('icon-admin-section');\u001b[39m\n\u001b[31m  if (sec) {\u001b[39m\n\u001b[31m    sec.style.display = MGMT.includes(currentUser.id) ? 'block' : 'none';\u001b[39m\n\u001b[31m    if (MGMT.includes(currentUser.id)) refreshSecretDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── ICON ADMIN ────────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst ICON_SECRET_KEY    = 'lm360_icon_secret';\u001b[39m\n\u001b[31mconst ICON_SECRET_INITIAL = 'nlr8Zsqw6XsD0rEXPQ_RfpYiOeNhH-9VglYKoIsElHE';·\u001b[39m\n\u001b[31mfunction getIconSecret() {\u001b[39m\n\u001b[31m  return localStorage.getItem(ICON_SECRET_KEY) || ICON_SECRET_INITIAL;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction setIconSecret(s) {\u001b[39m\n\u001b[31m  localStorage.setItem(ICON_SECRET_KEY, s);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet secretVisible = false;\u001b[39m\n\u001b[31mfunction refreshSecretDisplay() {\u001b[39m\n\u001b[31m  const el = document.getElementById('icon-secret-display');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  const s = getIconSecret();\u001b[39m\n\u001b[31m  el.textContent = secretVisible ? s : s.slice(0,4) + '••••••••••••••••' + s.slice(-4);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleSecretVisible() {\u001b[39m\n\u001b[31m  secretVisible = !secretVisible;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-secret-show');\u001b[39m\n\u001b[31m  if (btn) btn.textContent = secretVisible ? 'Hide' : 'Show';\u001b[39m\n\u001b[31m  refreshSecretDisplay();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function changeIconSecret() {\u001b[39m\n\u001b[31m  const newSecret = document.getElementById('icon-new-secret').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-secret-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (newSecret.length < 16) { errEl.textContent = 'Minimum 16 characters'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/change-secret', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret(), 'X-New-Secret': newSecret }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Failed');\u001b[39m\n\u001b[31m    setIconSecret(newSecret);\u001b[39m\n\u001b[31m    document.getElementById('icon-new-secret').value = '';\u001b[39m\n\u001b[31m    refreshSecretDisplay();\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = '✓ Secret updated';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mdocument.addEventListener('DOMContentLoaded', () => {\u001b[39m\n\u001b[31m  const fileInput = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  if (fileInput) {\u001b[39m\n\u001b[31m    fileInput.addEventListener('change', function() {\u001b[39m\n\u001b[31m      const wrap = document.getElementById('icon-preview-wrap');\u001b[39m\n\u001b[31m      const img  = document.getElementById('icon-preview-img');\u001b[39m\n\u001b[31m      if (this.files[0]) { img.src = URL.createObjectURL(this.files[0]); wrap.style.display = 'block'; }\u001b[39m\n\u001b[31m      else { wrap.style.display = 'none'; }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function uploadIcon() {\u001b[39m\n\u001b[31m  const pwa   = document.getElementById('icon-pwa-select').value;\u001b[39m\n\u001b[31m  const size  = document.getElementById('icon-size-select').value;\u001b[39m\n\u001b[31m  const input = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-upload-err');\u001b[39m\n\u001b[31m  errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (!input.files[0]) { errEl.textContent = 'Choose an image file first'; return; }\u001b[39m\n\u001b[31m  const formData = new FormData();\u001b[39m\n\u001b[31m  formData.append('pwa', pwa);\u001b[39m\n\u001b[31m  formData.append('size', size);\u001b[39m\n\u001b[31m  formData.append('icon', input.files[0]);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret() },\u001b[39m\n\u001b[31m      body: formData\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Upload failed');\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = `✓ Icon updated for ${pwa} (${size}px). Clear browser cache / reinstall to see new icon.`;\u001b[39m\n\u001b[31m    input.value = '';\u001b[39m\n\u001b[31m    document.getElementById('icon-preview-wrap').style.display = 'none';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadTeamPins() {\u001b[39m\n\u001b[31m  const list = document.getElementById('team-list');\u001b[39m\n\u001b[31m  list.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    list.innerHTML = '';\u001b[39m\n\u001b[31m    emps.forEach(emp => {\u001b[39m\n\u001b[31m      const row = document.createElement('div');\u001b[39m\n\u001b[31m      row.className = 'emp-row';\u001b[39m\n\u001b[31m      row.innerHTML = `\u001b[39m\n\u001b[31m        <div style=\\\"min-width:0;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:15px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.name}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.role}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-secondary btn-sm\\\" data-id=\\\"${emp.id}\\\" data-name=\\\"${emp.name}\\\">Set PIN</button>`;\u001b[39m\n\u001b[31m      row.querySelector('button').addEventListener('click', () => openSetPin(emp.id, emp.name));\u001b[39m\n\u001b[31m      list.appendChild(row);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    list.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openSetPin(targetId, targetName) {\u001b[39m\n\u001b[31m  setPinTarget = targetId;\u001b[39m\n\u001b[31m  document.getElementById('setpin-target-name').textContent = targetName;\u001b[39m\n\u001b[31m  ['setpin-new','setpin-confirm','setpin-auth'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-setpin').classList.add('show');\u001b[39m\n\u001b[31m  setTimeout(() => document.getElementById('setpin-new').focus(), 50);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function doSetPin() {\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('setpin-new').value.trim();\u001b[39m\n\u001b[31m  const conf    = document.getElementById('setpin-confirm').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('setpin-auth').value.trim();\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('setpin-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  if (!newPin || !conf || !authPin) { errEl.textContent = 'All fields are required'; return; }\u001b[39m\n\u001b[31m  if (newPin !== conf)              { errEl.textContent = 'New PINs do not match'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))     { errEl.textContent = 'PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-setpin-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_admin_id:  currentUser.id,\u001b[39m\n\u001b[31m        p_admin_pin: authPin,\u001b[39m\n\u001b[31m        p_target_id: setPinTarget,\u001b[39m\n\u001b[31m        p_new_pin:   newPin,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const err = await resp.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Failed to set PIN';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m    toast('✅ PIN updated for ' + document.getElementById('setpin-target-name').textContent);\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Set PIN';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ═══════════════════════════════════════════\u001b[39m\n\u001b[31m   JOBS — Campaign + Tour management\u001b[39m\n\u001b[31m   Access: harish, pramod (campaigns + tours)\u001b[39m\n\u001b[31m           rakesh (tours only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ */\u001b[39m\n\u001b[31mconst INST_RD = { 'Accept-Profile': 'installation' };\u001b[39m\n\u001b[31mconst INST_WR = { 'Content-Type':'application/json','Accept-Profile':'installation','Content-Profile':'installation' };·\u001b[39m\n\u001b[31mlet _currentCampaignId = null;\u001b[39m\n\u001b[31mlet _allEmployees      = [];\u001b[39m\n\u001b[31mlet _editTourId        = null;  // null = create mode, string = edit mode·\u001b[39m\n\u001b[31m// ── Helpers ──────────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _slugify(s) {\u001b[39m\n\u001b[31m  return s.toUpperCase().trim()\u001b[39m\n\u001b[31m    .replace(/[^A-Z0-9]+/g, '_')\u001b[39m\n\u001b[31m    .replace(/^_|_$/g, '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _buildCampaignId(year, brand, client, campaign, phase) {\u001b[39m\n\u001b[31m  const nn = String(phase || 1).padStart(2, '0');\u001b[39m\n\u001b[31m  return `${year}-${_slugify(brand)}-${_slugify(client)}-${_slugify(campaign)}-${nn}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _campStatusBadge(s) {\u001b[39m\n\u001b[31m  const map = { active:'ok', completed:'', on_hold:'warn' };\u001b[39m\n\u001b[31m  const col = { active:'green', completed:'', on_hold:'amber' };\u001b[39m\n\u001b[31m  return `<span class=\\\"kpi-val ${col[s]||''}\\\" style=\\\"font-size:12px;font-weight:700\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _tourStatusBadge(s) {\u001b[39m\n\u001b[31m  const colors = { active:'var(--success)', planned:'var(--info)', completed:'var(--muted)', cancelled:'var(--danger)' };\u001b[39m\n\u001b[31m  return `<span style=\\\"font-size:11px;font-weight:700;padding:2px 8px;border-radius:10px;\u001b[39m\n\u001b[31m    background:var(--card);color:${colors[s]||'var(--muted)'}\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _canCreateCampaign() { return ['harish','pramod'].includes(currentUser?.id); }\u001b[39m\n\u001b[31mfunction _canCreateTour()     { return ['harish','pramod','rakesh'].includes(currentUser?.id); }·\u001b[39m\n\u001b[31m// ── Jobs screen ───────────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterJobs() {\u001b[39m\n\u001b[31m  showScreen('jobs');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').style.display = _canCreateCampaign() ? '' : 'none';\u001b[39m\n\u001b[31m  await loadJobs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadJobs() {\u001b[39m\n\u001b[31m  const el = document.getElementById('jobs-body');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?order=created_at.desc`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?select=tour_id,campaign_id,status`, { headers: INST_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const campaigns = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours     = tResp.ok ? await tResp.json() : [];·\u001b[39m\n\u001b[31m    if (!campaigns.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty\\\"><div style=\\\"font-size:32px\\\">📋</div><div>No campaigns yet</div></div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    el.innerHTML = campaigns.map(c => {\u001b[39m\n\u001b[31m      const cTours  = tours.filter(t => t.campaign_id === c.campaign_id);\u001b[39m\n\u001b[31m      const active  = cTours.filter(t => t.status === 'active').length;\u001b[39m\n\u001b[31m      const total   = cTours.length;\u001b[39m\n\u001b[31m      const statusColors = { active:'var(--success)', on_hold:'var(--accent2)', completed:'var(--muted)' };\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card tappable\\\" style=\\\"padding:14px\\\" onclick=\\\"enterCampaign('${c.campaign_id}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:flex-start;gap:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:15px;font-weight:700\\\">${c.brand} — ${c.campaign_name}\u001b[39m\n\u001b[31m                ${c.phase > 1 ? `<span style=\\\"font-size:11px;color:var(--muted)\\\"> Phase ${c.phase}</span>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:2px\\\">${c.client} · ${c.year}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:4px\\\">\u001b[39m\n\u001b[31m                ${total} tour${total!==1?'s':''} · ${active} active\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;align-items:flex-end;gap:6px\\\">\u001b[39m\n\u001b[31m              <span style=\\\"font-size:11px;font-weight:700;color:${statusColors[c.status]||'var(--muted)'}\\\">\u001b[39m\n\u001b[31m                ${(c.status||'').replace('_',' ')}\u001b[39m\n\u001b[31m              </span>\u001b[39m\n\u001b[31m              <span style=\\\"font-size:18px;color:var(--muted)\\\">›</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-top:8px;word-break:break-all\\\">${c.campaign_id}</div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign detail ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterCampaign(campaignId) {\u001b[39m\n\u001b[31m  _currentCampaignId = campaignId;\u001b[39m\n\u001b[31m  showScreen('campaign');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').style.display = _canCreateTour() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-sub').textContent   = '';\u001b[39m\n\u001b[31m  await loadCampaign(campaignId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCampaign(campaignId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('campaign-body');\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp, empResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(campaignId)}&order=start_date`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const camps = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours = tResp.ok ? await tResp.json() : [];\u001b[39m\n\u001b[31m    _allEmployees = empResp.ok ? await empResp.json() : [];·\u001b[39m\n\u001b[31m    if (!camps.length) { body.innerHTML = '<div class=\\\"empty\\\">Campaign not found</div>'; return; }\u001b[39m\n\u001b[31m    const c = camps[0];·\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-title').textContent = `${c.brand} — ${c.campaign_name}`;\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-sub').textContent   = `${c.client} · ${c.year}${c.phase>1?' · Phase '+c.phase:''}`;·\u001b[39m\n\u001b[31m    const empMap = {};\u001b[39m\n\u001b[31m    _allEmployees.forEach(e => empMap[e.id] = e.name);·\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"padding:12px 14px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Campaign ID</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:10px;color:var(--muted);word-break:break-all;text-align:right;max-width:65%\\\">${c.campaign_id}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Client</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:13px;font-weight:600\\\">${c.client}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${_canCreateCampaign() ? `\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div class=\\\"section-title\\\" style=\\\"margin-bottom:6px\\\">Campaign Status</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m            ${['active','on_hold','completed'].map(s =>\u001b[39m\n\u001b[31m              `<button class=\\\"status-chip${c.status===s?' sel':''}\\\" onclick=\\\"updateCampaignStatus('${c.campaign_id}','${s}')\\\">\u001b[39m\n\u001b[31m                ${s.replace('_',' ')}\u001b[39m\n\u001b[31m              </button>`).join('')}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>` : ''}\u001b[39m\n\u001b[31m      </div>·\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:4px\\\">Tours (${tours.length})</div>\u001b[39m\n\u001b[31m      ${tours.length === 0 ? `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\">\u001b[39m\n\u001b[31m        <div>No tours yet</div>\u001b[39m\n\u001b[31m        ${_canCreateTour() ? '<div style=\\\"font-size:12px\\\">Tap ＋ to add the first tour</div>' : ''}\u001b[39m\n\u001b[31m      </div>` :\u001b[39m\n\u001b[31m      tours.map(t => {\u001b[39m\n\u001b[31m        const empNames = (t.assigned_employees || []).map(id => empMap[id] || id).join(', ') || 'No one assigned';\u001b[39m\n\u001b[31m        const dates    = t.start_date ? `${t.start_date} → ${t.end_date||'?'}` : 'Dates not set';\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m          <div class=\\\"card\\\" style=\\\"padding:14px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;align-items:flex-start;gap:8px\\\">\u001b[39m\n\u001b[31m              <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m                <div style=\\\"font-size:14px;font-weight:700\\\">${t.title || t.tour_id}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">📅 ${dates}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">👥 ${empNames}</div>\u001b[39m\n\u001b[31m                ${t.notes ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">📝 ${t.notes}</div>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              ${_tourStatusBadge(t.status)}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            ${_canCreateTour() ? `\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;gap:6px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m              ${['planned','active','completed'].map(s =>\u001b[39m\n\u001b[31m                `<button class=\\\"status-chip${t.status===s?' sel':''}\\\"\u001b[39m\n\u001b[31m                  onclick=\\\"updateTourStatus('${t.tour_id}','${s}')\\\">${s}</button>`).join('')}\u001b[39m\n\u001b[31m              <button class=\\\"status-chip\\\" onclick=\\\"openEditTour('${t.tour_id}')\\\"\u001b[39m\n\u001b[31m                style=\\\"margin-left:auto;background:var(--surface);border-color:#3a3a6e;color:#8888ff\\\">\u001b[39m\n\u001b[31m                ✏️ Edit\u001b[39m\n\u001b[31m              </button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m            <div style=\\\"font-size:10px;color:var(--muted);margin-top:6px\\\">${t.tour_id}</div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m      <div style=\\\"height:20px\\\"></div>`;\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateTourStatus(tourId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Tour marked ${status}`);\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateCampaignStatus(campaignId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Campaign marked ${status.replace('_',' ')}`);\u001b[39m\n\u001b[31m    loadCampaign(campaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Campaign modal ────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewCampaign() {\u001b[39m\n\u001b[31m  if (!_canCreateCampaign()) { toast('Only Harish or Pramod can create campaigns'); return; }\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('nc-year').value    = new Date().getFullYear();\u001b[39m\n\u001b[31m  document.getElementById('nc-phase').value   = '1';\u001b[39m\n\u001b[31m  document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  document.getElementById('nc-err').textContent     = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-new-campaign').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateCampaignPreview() {\u001b[39m\n\u001b[31m  const year  = document.getElementById('nc-year').value;\u001b[39m\n\u001b[31m  const brand = document.getElementById('nc-brand').value;\u001b[39m\n\u001b[31m  const clt   = document.getElementById('nc-client').value;\u001b[39m\n\u001b[31m  const camp  = document.getElementById('nc-campaign').value;\u001b[39m\n\u001b[31m  const phase = document.getElementById('nc-phase').value;\u001b[39m\n\u001b[31m  if (brand && clt && camp) {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = _buildCampaignId(year, brand, clt, camp, phase);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewCampaign() {\u001b[39m\n\u001b[31m  const year   = Number(document.getElementById('nc-year').value);\u001b[39m\n\u001b[31m  const brand  = document.getElementById('nc-brand').value.trim();\u001b[39m\n\u001b[31m  const client = document.getElementById('nc-client').value.trim();\u001b[39m\n\u001b[31m  const cname  = document.getElementById('nc-campaign').value.trim();\u001b[39m\n\u001b[31m  const phase  = Number(document.getElementById('nc-phase').value) || 1;\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('nc-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const brandEl = document.getElementById('nc-brand');\u001b[39m\n\u001b[31m  const clientEl = document.getElementById('nc-client');\u001b[39m\n\u001b[31m  const cnameEl = document.getElementById('nc-campaign');\u001b[39m\n\u001b[31m  [brandEl, clientEl, cnameEl].forEach(el => el.style.borderColor = '');\u001b[39m\n\u001b[31m  let _missingField = false;\u001b[39m\n\u001b[31m  if (!brand)  { brandEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!client) { clientEl.style.borderColor = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!cname)  { cnameEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (_missingField) { errEl.textContent = 'Brand, Client and Campaign Name are required'; return; }\u001b[39m\n\u001b[31m  const campaignId = _buildCampaignId(year, brand, client, cname, phase);·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nc-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/campaigns`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ campaign_id:campaignId, year, brand, client, campaign_name:cname, phase, created_by:currentUser.id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || 'Failed to create';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show');\u001b[39m\n\u001b[31m    toast(`✅ Campaign created`);\u001b[39m\n\u001b[31m    loadJobs();\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Create'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Tour modal ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _buildEmpCheckboxes(assigned = []) {\u001b[39m\n\u001b[31m  return _allEmployees.map(e =>\u001b[39m\n\u001b[31m    `<label style=\\\"display:flex;align-items:center;gap:10px;padding:8px 10px;background:var(--card);\u001b[39m\n\u001b[31m      border:1px solid var(--border);border-radius:8px;cursor:pointer\\\">\u001b[39m\n\u001b[31m      <input type=\\\"checkbox\\\" value=\\\"${e.id}\\\" ${assigned.includes(e.id)?'checked':''} style=\\\"width:16px;height:16px;accent-color:var(--accent)\\\">\u001b[39m\n\u001b[31m      <span style=\\\"font-size:14px;font-weight:600\\\">${e.name}</span>\u001b[39m\n\u001b[31m      <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${e.role}</span>\u001b[39m\n\u001b[31m    </label>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openNewTour() {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = null;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '🗺️ New Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Create Tour';\u001b[39m\n\u001b[31m  document.getElementById('nt-title').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-start').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-end').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-notes').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(_currentCampaignId)}&select=tour_id`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const existing = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    const nn = String(existing.length + 1).padStart(2, '0');\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T${nn}`;\u001b[39m\n\u001b[31m  } catch(_) {\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T01`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (_allEmployees.length)\u001b[39m\n\u001b[31m    document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes();·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEditTour(tourId) {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = tourId;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '✏️ Edit Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Save Changes';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const tours = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    if (!tours.length) { toast('Tour not found'); return; }\u001b[39m\n\u001b[31m    const t = tours[0];\u001b[39m\n\u001b[31m    document.getElementById('nt-title').value = t.title || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-start').value = t.start_date || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-end').value   = t.end_date   || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-notes').value = t.notes      || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = tourId;\u001b[39m\n\u001b[31m    if (_allEmployees.length)\u001b[39m\n\u001b[31m      document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes(t.assigned_employees || []);\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to load tour'); return; }·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewTour() {\u001b[39m\n\u001b[31m  const title = document.getElementById('nt-title').value.trim();\u001b[39m\n\u001b[31m  const start = document.getElementById('nt-start').value || null;\u001b[39m\n\u001b[31m  const end   = document.getElementById('nt-end').value   || null;\u001b[39m\n\u001b[31m  const notes = document.getElementById('nt-notes').value.trim() || null;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('nt-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const checked = [...document.querySelectorAll('#nt-employees input:checked')].map(cb => cb.value);\u001b[39m\n\u001b[31m  if (!checked.length) { errEl.textContent = 'Assign at least one employee'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nt-save');\u001b[39m\n\u001b[31m  const wasEdit = !!_editTourId;\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = wasEdit ? 'Saving…' : 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editTourId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(_editTourId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      const tourId = document.getElementById('nt-id-preview').textContent;\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          tour_id: tourId, campaign_id: _currentCampaignId,\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes, status: 'planned',\u001b[39m\n\u001b[31m          created_by: currentUser.id,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || (_editTourId ? 'Failed to save' : 'Failed to create');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show');\u001b[39m\n\u001b[31m    toast(_editTourId ? '✅ Tour updated' : '✅ Tour created');\u001b[39m\n\u001b[31m    _editTourId = null;\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = wasEdit ? 'Save Changes' : 'Create Tour';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── EVENT WIRING ── */\u001b[39m\n\u001b[31mfunction wireEvents() {\u001b[39m\n\u001b[31m  // Jobs screens\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-back').addEventListener('click',     () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-refresh').addEventListener('click',  loadJobs);\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').addEventListener('click',  openNewCampaign);\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-back').addEventListener('click', () => showScreen('jobs'));\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-refresh').addEventListener('click', () => loadCampaign(_currentCampaignId));\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').addEventListener('click',      openNewTour);·\u001b[39m\n\u001b[31m  // New Campaign modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-save').addEventListener('click', saveNewCampaign);\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign','nc-year','nc-phase'].forEach(id =>\u001b[39m\n\u001b[31m    document.getElementById(id).addEventListener('input', updateCampaignPreview));·\u001b[39m\n\u001b[31m  // New Tour modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').addEventListener('click', saveNewTour);·\u001b[39m\n\u001b[31m  document.getElementById('btn-team-back').addEventListener('click', () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-team-refresh').addEventListener('click', loadTeamPins);\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-cancel').addEventListener('click', () => {\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-save').addEventListener('click', doSetPin);·\u001b[39m\n\u001b[31m  // Set PIN — progressive field reveal\u001b[39m\n\u001b[31m  document.getElementById('setpin-new').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const val = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const showConf = val.length === 4;\u001b[39m\n\u001b[31m    document.getElementById('setpin-confirm-group').style.display = showConf ? '' : 'none';\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m    if (!showConf) { document.getElementById('setpin-confirm').value = ''; document.getElementById('setpin-auth').value = ''; }\u001b[39m\n\u001b[31m    if (showConf) setTimeout(() => document.getElementById('setpin-confirm').focus(), 20);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const newVal = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const conf   = document.getElementById('setpin-confirm').value;\u001b[39m\n\u001b[31m    const showAuth = conf.length === 4 && conf === newVal;\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = showAuth ? '' : 'none';\u001b[39m\n\u001b[31m    if (!showAuth) document.getElementById('setpin-auth').value = '';\u001b[39m\n\u001b[31m    if (showAuth) setTimeout(() => document.getElementById('setpin-auth').focus(), 20);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // Hub AI config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-save').addEventListener('click',   saveHubAiConfig);\u001b[39m\n\u001b[31m  document.getElementById('btn-check-keys').addEventListener('click',    checkApiKeyStatus);·\u001b[39m\n\u001b[31m  // Hub QA config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-qa').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-save').addEventListener('click', saveHubQaConfig);·\u001b[39m\n\u001b[31m  // Share picker\u001b[39m\n\u001b[31m  document.getElementById('share-to-expense').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/expense/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-recce').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/recce/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-installation').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/installation/current/?share=1';\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-04: employee dropdown change resets PIN buffer and lockout\u001b[39m\n\u001b[31m  document.getElementById('login-name').addEventListener('change', () => {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-07: ESC key closes any open modal\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (e.key === 'Escape') {\u001b[39m\n\u001b[31m      document.querySelectorAll('.modal-overlay.show').forEach(m => m.classList.remove('show'));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-11: keyboard digit/backspace input on login screen\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (!document.getElementById('screen-login').classList.contains('active')) return;\u001b[39m\n\u001b[31m    if (e.key >= '0' && e.key <= '9') { handlePinKey(e.key); }\u001b[39m\n\u001b[31m    else if (e.key === 'Backspace') { e.preventDefault(); handlePinKey('B'); }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-02/B-08: re-check session TTL when user returns to the tab\u001b[39m\n\u001b[31m  document.addEventListener('visibilitychange', () => {\u001b[39m\n\u001b[31m    if (document.visibilityState === 'visible' && currentUser) {\u001b[39m\n\u001b[31m      if (!loadSession()) doLogout();\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── BOOT ── */\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  wireEvents();·\u001b[39m\n\u001b[31m  if (location.search.includes('share=1')) {\u001b[39m\n\u001b[31m    pendingShare = true;\u001b[39m\n\u001b[31m    history.replaceState({}, '', '/hub/');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Always populate the login dropdown (needed if user logs out later)\u001b[39m\n\u001b[31m  initLogin(); // fire-and-forget — does not block home screen path·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess) {\u001b[39m\n\u001b[31m    currentUser = { id: sess.empId, name: sess.name, role: sess.role };\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  setTimeout(() => showScreen('login'), 1500);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SERVICE WORKER: install + update ── */\u001b[39m\n\u001b[31mif ('serviceWorker' in navigator) {\u001b[39m\n\u001b[31m  navigator.serviceWorker.register('/hub/sw.js').then(reg => {·\u001b[39m\n\u001b[31m    // Install banner: show when browser signals app is installable\u001b[39m\n\u001b[31m    let deferredInstall = null;\u001b[39m\n\u001b[31m    window.addEventListener('beforeinstallprompt', e => {\u001b[39m\n\u001b[31m      e.preventDefault();\u001b[39m\n\u001b[31m      deferredInstall = e;\u001b[39m\n\u001b[31m      if (!window.matchMedia('(display-mode: standalone)').matches) {\u001b[39m\n\u001b[31m        const delay = document.getElementById('update-banner').classList.contains('show') ? 3500 : 0;\u001b[39m\n\u001b[31m        setTimeout(() => document.getElementById('install-banner').classList.add('show'), delay);\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    window.addEventListener('appinstalled', () => {\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install').addEventListener('click', async () => {\u001b[39m\n\u001b[31m      if (!deferredInstall) return;\u001b[39m\n\u001b[31m      deferredInstall.prompt();\u001b[39m\n\u001b[31m      await deferredInstall.userChoice;\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m    // Update banner: show when a new SW version is waiting to activate\u001b[39m\n\u001b[31m    function showUpdateBanner() {\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.add('show');\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (reg.waiting) showUpdateBanner();·\u001b[39m\n\u001b[31m    reg.addEventListener('updatefound', () => {\u001b[39m\n\u001b[31m      const newSW = reg.installing;\u001b[39m\n\u001b[31m      newSW.addEventListener('statechange', () => {\u001b[39m\n\u001b[31m        if (newSW.state === 'installed' && navigator.serviceWorker.controller) {\u001b[39m\n\u001b[31m          showUpdateBanner();\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    document.getElementById('btn-update').addEventListener('click', () => {\u001b[39m\n\u001b[31m      if (reg.waiting) {\u001b[39m\n\u001b[31m        reg.waiting.postMessage({ type: 'SKIP_WAITING' });\u001b[39m\n\u001b[31m        navigator.serviceWorker.addEventListener('controllerchange', () => {\u001b[39m\n\u001b[31m          window.location.reload();\u001b[39m\n\u001b[31m        }, { once: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-update-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m  }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mboot();\u001b[39m\n\u001b[31m</script>··\u001b[39m\n\u001b[31m</body></html>\"\u001b[39m\n\n  329 |     await page.goto(BASE_URL);\n  330 |     const src = await page.content();\n> 331 |     expect(src).toContain('recce-v10');\n      |                 ^\n  332 |     expect(src).not.toContain('recce-v9');\n  333 |   });\n  334 | });\n    at /var/www/360lm/tests/recce.spec.js:331:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-05-20T06:22:44.814Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-5e896-tos-SW-version-is-recce-v10-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-5e896-tos-SW-version-is-recce-v10-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 331
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-6fe0aeb95c0cda0becfb",
              "file": "recce.spec.js",
              "line": 328,
              "column": 3
            },
            {
              "title": "inside view label shows \"(optional, max 5)\"",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 10965,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:30.659Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-290e517ef1b0aa6969f1",
              "file": "recce.spec.js",
              "line": 287,
              "column": 3
            },
            {
              "title": "front view label still shows \"(min 1, max 3)\"",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 10787,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:41.659Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-c2186030fc7e5640d768",
              "file": "recce.spec.js",
              "line": 301,
              "column": 3
            },
            {
              "title": "add-inside button hidden after 3 photos (before limit) — regression guard",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 681,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:52.484Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-61a619f43cbfc0e44e4f",
              "file": "recce.spec.js",
              "line": 313,
              "column": 3
            },
            {
              "title": "SW version is recce-v10",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 808,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"recce-v10\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html><html lang=\\\"en\\\"><head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#FF6B35\\\">\u001b[39m\n\u001b[31m<meta name=\\\"mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-status-bar-style\\\" content=\\\"black-translucent\\\">\u001b[39m\n\u001b[31m<title>360LM</title>\u001b[39m\n\u001b[31m<meta name=\\\"description\\\" content=\\\"360LM Operations Hub — employee tools\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/hub/manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"icon\\\" href=\\\"/hub/icons/icon-192.png\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--surface:#161616;--card:#1e1e1e;--border:#2a2a2a;\u001b[39m\n\u001b[31m  --accent:#FF6B35;--accent2:#FFB800;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --danger:#e74c3c;--success:#2ecc71;--info:#3498db;\u001b[39m\n\u001b[31m  --radius:12px;--font:'Segoe UI',system-ui,sans-serif;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:var(--font);min-height:100vh;overflow-x:hidden}\u001b[39m\n\u001b[31m.screen{display:none;min-height:100vh;flex-direction:column}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── HEADER ── */\u001b[39m\n\u001b[31m.header{background:var(--surface);border-bottom:1px solid var(--border);padding:14px 16px;\u001b[39m\n\u001b[31m  display:flex;align-items:center;gap:12px;position:sticky;top:0;z-index:10;flex-shrink:0}\u001b[39m\n\u001b[31m.header h1{font-size:18px;font-weight:700;color:var(--accent);flex:1}\u001b[39m\n\u001b[31m.header .sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.btn-icon{background:none;border:none;color:var(--muted);font-size:22px;cursor:pointer;\u001b[39m\n\u001b[31m  padding:4px;border-radius:8px;line-height:1;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m.btn-icon:active{background:var(--border)}·\u001b[39m\n\u001b[31m/* ── BODY ── */\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:16px}·\u001b[39m\n\u001b[31m/* ── SPLASH ── */\u001b[39m\n\u001b[31m#screen-splash{align-items:center;justify-content:center;gap:8px}\u001b[39m\n\u001b[31m.splash-logo{font-size:72px;font-weight:900;color:var(--accent);letter-spacing:-2px;line-height:1}\u001b[39m\n\u001b[31m.splash-sub{font-size:15px;color:var(--muted);letter-spacing:2px;text-transform:uppercase}\u001b[39m\n\u001b[31m.splash-dot{width:8px;height:8px;border-radius:50%;background:var(--accent);\u001b[39m\n\u001b[31m  animation:pulse 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes pulse{0%,100%{opacity:.3;transform:scale(.8)}50%{opacity:1;transform:scale(1)}}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  min-height:100dvh;padding:24px;gap:20px;overflow-y:auto}\u001b[39m\n\u001b[31m.login-logo{font-size:44px;font-weight:900;color:var(--accent);letter-spacing:-1px}\u001b[39m\n\u001b[31m.login-sub{font-size:13px;color:var(--muted);text-transform:uppercase;letter-spacing:2px}\u001b[39m\n\u001b[31m.form-group{width:100%;max-width:360px;display:flex;flex-direction:column;gap:8px}\u001b[39m\n\u001b[31m.form-group label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px}\u001b[39m\n\u001b[31mselect{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31mselect:focus{border-color:var(--accent)}·\u001b[39m\n\u001b[31m/* ── PIN PAD ── */\u001b[39m\n\u001b[31m.pin-display{display:flex;gap:14px;justify-content:center;margin:4px 0}\u001b[39m\n\u001b[31m.pin-dot{width:16px;height:16px;border-radius:50%;border:2px solid var(--muted);background:transparent;transition:.15s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.pin-pad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;max-width:260px;margin:0 auto;width:100%}\u001b[39m\n\u001b[31m.pin-key{background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  font-size:22px;font-weight:600;padding:16px;border-radius:var(--radius);cursor:pointer;\u001b[39m\n\u001b[31m  text-align:center;line-height:1;user-select:none}\u001b[39m\n\u001b[31m.pin-key:active{background:var(--border)}\u001b[39m\n\u001b[31m.pin-key.clr{color:var(--danger);font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.pin-key.back{color:var(--accent2)}\u001b[39m\n\u001b[31m.login-err{font-size:13px;color:var(--danger);text-align:center;min-height:18px}\u001b[39m\n\u001b[31m@keyframes shake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-6px)}40%,80%{transform:translateX(6px)}}\u001b[39m\n\u001b[31m.pin-display.shake{animation:shake .4s ease}·\u001b[39m\n\u001b[31m/* ── KPI CARDS ── */\u001b[39m\n\u001b[31m.kpi-row{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\u001b[39m\n\u001b[31m.kpi-4col{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m#home-body{gap:10px}\u001b[39m\n\u001b[31m.kpi-card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:10px 8px;text-align:center;border-left:3px solid var(--accent);cursor:default}\u001b[39m\n\u001b[31m.kpi-card.alert{border-left-color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-card.warn{border-left-color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-card.ok{border-left-color:var(--success)}\u001b[39m\n\u001b[31m.kpi-card.info{border-left-color:var(--info)}\u001b[39m\n\u001b[31m.kpi-val{font-size:18px;font-weight:900;color:var(--accent);line-height:1.2}\u001b[39m\n\u001b[31m.kpi-val.red{color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-val.amber{color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-val.green{color:var(--success)}\u001b[39m\n\u001b[31m.kpi-val.blue{color:var(--info)}\u001b[39m\n\u001b[31m.kpi-lbl{font-size:10px;color:var(--muted);margin-top:4px;text-transform:uppercase;letter-spacing:.4px;line-height:1.3}\u001b[39m\n\u001b[31m.kpi-skel{background:var(--border);border-radius:4px;height:24px;margin-bottom:4px;\u001b[39m\n\u001b[31m  animation:shimmer 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes shimmer{0%,100%{opacity:.5}50%{opacity:1}}·\u001b[39m\n\u001b[31m/* ── SECTION TITLE ── */\u001b[39m\n\u001b[31m.section-title{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.8px}·\u001b[39m\n\u001b[31m/* ── MGMT HOME: MAIN TILES ── */\u001b[39m\n\u001b[31m.hub-main-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px}\u001b[39m\n\u001b[31m.hub-main-tile{background:var(--card);border:2px solid var(--border);border-radius:18px;\u001b[39m\n\u001b[31m  padding:36px 12px 28px;text-align:center;cursor:pointer;transition:background .15s,border-color .15s}\u001b[39m\n\u001b[31m.hub-main-tile:active{background:#1a2a1a;border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-main-icon{font-size:40px;line-height:1;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-main-label{font-size:15px;font-weight:800;color:var(--text)}\u001b[39m\n\u001b[31m/* ── MGMT HOME: QUICK ACTIONS ── */\u001b[39m\n\u001b[31m.hub-qa-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-qa-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(88px,1fr));gap:10px}\u001b[39m\n\u001b[31m.hub-qa-tile{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px 8px;text-align:center;cursor:pointer;display:flex;flex-direction:column;\u001b[39m\n\u001b[31m  align-items:center;gap:6px;transition:background .15s}\u001b[39m\n\u001b[31m.hub-qa-tile:active{background:var(--border)}\u001b[39m\n\u001b[31m.hub-qa-icon{font-size:26px;line-height:1}\u001b[39m\n\u001b[31m.hub-qa-label{font-size:11px;font-weight:700;color:var(--text);line-height:1.3}\u001b[39m\n\u001b[31m.hub-qa-item{display:flex;align-items:center;gap:12px;padding:10px 0;\u001b[39m\n\u001b[31m  border-bottom:1px solid var(--border);cursor:pointer}\u001b[39m\n\u001b[31m.hub-qa-item:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.hub-qa-item-icon{font-size:22px;width:32px;text-align:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item-label{flex:1;font-size:14px;font-weight:600}\u001b[39m\n\u001b[31m.hub-qa-item-toggle{width:20px;height:20px;border-radius:4px;border:2px solid var(--border);\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle::after{content:'✓';color:#fff;font-weight:700;font-size:12px}·\u001b[39m\n\u001b[31m/* ── TILE GRID ── */\u001b[39m\n\u001b[31m.tile-grid{display:grid;gap:12px}\u001b[39m\n\u001b[31m.tile-grid.mgmt{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m.tile-grid.field{grid-template-columns:repeat(2,1fr)}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:18px 8px;display:flex;flex-direction:column;align-items:center;gap:8px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s;position:relative}\u001b[39m\n\u001b[31m.tile:active{background:var(--border);transform:scale(.97)}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px;line-height:1}\u001b[39m\n\u001b[31m.tile-label{font-size:11px;font-weight:700;color:var(--text);text-align:center;text-transform:uppercase;letter-spacing:.4px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-icon{font-size:24px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-label{font-size:10px}\u001b[39m\n\u001b[31m.tile.coming{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.tile.coming:active{transform:none;background:var(--card)}\u001b[39m\n\u001b[31m.soon-badge{position:absolute;top:6px;right:6px;background:var(--accent2);color:#000;\u001b[39m\n\u001b[31m  font-size:8px;font-weight:800;padding:2px 5px;border-radius:20px;text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── USER BADGE ── */\u001b[39m\n\u001b[31m.user-badge{display:flex;align-items:center;gap:12px}\u001b[39m\n\u001b[31m.avatar{width:44px;height:44px;background:var(--accent);border-radius:50%;\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:800;\u001b[39m\n\u001b[31m  color:#fff;flex-shrink:0;text-transform:uppercase}\u001b[39m\n\u001b[31m.user-name{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.user-role{font-size:12px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── TEAM PINS ── */\u001b[39m\n\u001b[31m.emp-row{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px}\u001b[39m\n\u001b[31m.btn{display:flex;align-items:center;justify-content:center;gap:6px;border:none;\u001b[39m\n\u001b[31m  border-radius:var(--radius);font-size:15px;font-weight:700;padding:12px 18px;\u001b[39m\n\u001b[31m  cursor:pointer;width:100%;transition:.15s;font-family:var(--font)}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--card);border:1px solid var(--border);color:var(--text)}\u001b[39m\n\u001b[31m.btn-sm{padding:7px 12px;font-size:12px;font-weight:700;width:auto;flex-shrink:0}\u001b[39m\n\u001b[31m.info-note{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;font-size:13px;color:var(--muted);line-height:1.5}·\u001b[39m\n\u001b[31m/* ── MODAL ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:200;\u001b[39m\n\u001b[31m  align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;\u001b[39m\n\u001b[31m  padding:24px;width:100%;max-width:480px;display:flex;flex-direction:column;gap:14px;\u001b[39m\n\u001b[31m  max-height:90dvh;overflow-y:auto;-webkit-overflow-scrolling:touch}\u001b[39m\n\u001b[31m.modal-title{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.modal-box input[type=password]{\u001b[39m\n\u001b[31m  width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31m.modal-box input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.action-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px}·\u001b[39m\n\u001b[31m/* ── SHARE PICKER ── */\u001b[39m\n\u001b[31m#screen-share .body{align-items:center;justify-content:center;gap:24px}\u001b[39m\n\u001b[31m.share-prompt{font-size:15px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.share-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;width:100%;max-width:360px}\u001b[39m\n\u001b[31m.share-tile{background:var(--card);border:2px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:24px 8px;display:flex;flex-direction:column;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s}\u001b[39m\n\u001b[31m.share-tile:active{background:var(--border);transform:scale(.97);border-color:var(--accent)}\u001b[39m\n\u001b[31m.share-tile-icon{font-size:36px;line-height:1}\u001b[39m\n\u001b[31m.share-tile-label{font-size:12px;font-weight:700;color:var(--text);text-align:center;\u001b[39m\n\u001b[31m  text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── INSTALL BANNER ── */\u001b[39m\n\u001b[31m#install-banner{display:none;position:fixed;bottom:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:linear-gradient(135deg,#1a1a1a,#0d2010);\u001b[39m\n\u001b[31m  border-top:1px solid var(--accent);padding:14px 16px;\u001b[39m\n\u001b[31m  align-items:center;gap:12px;box-shadow:0 -4px 24px rgba(0,0,0,.7)}\u001b[39m\n\u001b[31m#install-banner.show{display:flex}\u001b[39m\n\u001b[31m#install-banner .ib-icon{font-size:30px;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-text{flex:1}\u001b[39m\n\u001b[31m#install-banner .ib-title{font-size:14px;font-weight:700;color:var(--text)}\u001b[39m\n\u001b[31m#install-banner .ib-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#install-banner .ib-btn{background:var(--accent);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-close{background:none;border:none;color:var(--muted);font-size:20px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── UPDATE BANNER ── */\u001b[39m\n\u001b[31m#update-banner{display:none;position:fixed;top:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:#1a2a1a;border-bottom:2px solid var(--success);\u001b[39m\n\u001b[31m  padding:12px 16px;align-items:center;gap:12px}\u001b[39m\n\u001b[31m#update-banner.show{display:flex}\u001b[39m\n\u001b[31m#update-banner .ub-text{flex:1;font-size:14px;font-weight:600;color:var(--text)}\u001b[39m\n\u001b[31m#update-banner .ub-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#update-banner .ub-btn{background:var(--success);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:8px 14px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#update-banner .ub-close{background:none;border:none;color:var(--muted);font-size:18px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#222;color:#fff;padding:10px 20px;border-radius:20px;font-size:14px;font-weight:600;\u001b[39m\n\u001b[31m  opacity:0;transition:.3s;pointer-events:none;white-space:nowrap;z-index:500}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  padding:40px;color:var(--muted);font-size:14px;gap:12px}\u001b[39m\n\u001b[31m.spinner{width:36px;height:36px;border:3px solid var(--border);border-top-color:var(--accent);\u001b[39m\n\u001b[31m  border-radius:50%;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* ── CARD (jobs / campaign screens) ── */\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius)}\u001b[39m\n\u001b[31m.card.tappable:active{border-color:var(--accent);opacity:.85}·\u001b[39m\n\u001b[31m/* ── STATUS CHIPS (tour / campaign) ── */\u001b[39m\n\u001b[31m.status-chip{font-size:11px;padding:4px 11px;border-radius:8px;cursor:pointer;\u001b[39m\n\u001b[31m  background:var(--card);color:var(--text);border:1px solid var(--border);font-weight:600;\u001b[39m\n\u001b[31m  font-family:var(--font);transition:.15s}\u001b[39m\n\u001b[31m.status-chip.sel{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.status-chip:active{opacity:.8}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SPLASH\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-splash\\\" class=\\\"screen active\\\">\u001b[39m\n\u001b[31m  <div class=\\\"splash-logo\\\">360LM</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-sub\\\">Operations Hub</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-dot\\\" style=\\\"margin-top:16px\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     LOGIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-login\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-wrap\\\">\u001b[39m\n\u001b[31m    <div class=\\\"login-logo\\\">360LM</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">Operations Hub</div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Select Your Name</label>\u001b[39m\n\u001b[31m      <select id=\\\"login-name\\\"><option value=\\\"\\\">— Choose employee —</option><option value=\\\"amit\\\">Amit</option><option value=\\\"ashok\\\">Ashok</option><option value=\\\"gurpreet\\\">Gurpreet</option><option value=\\\"harish\\\">Harish Kumar Lal</option><option value=\\\"mukesh\\\">Mukesh</option><option value=\\\"narender\\\">Narender</option><option value=\\\"pramod\\\">Pramod Narang</option><option value=\\\"rakesh\\\">Rakesh</option><option value=\\\"sachin\\\">Sachin</option><option value=\\\"shambhu\\\">Shambhu</option><option value=\\\"umashankar\\\">Umashankar</option></select>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m      <label style=\\\"text-align:center\\\">Enter PIN</label>\u001b[39m\n\u001b[31m      <div class=\\\"pin-display\\\" id=\\\"pin-display\\\">\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd0\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd1\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd2\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd3\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"login-err\\\" id=\\\"login-err\\\"></div>\u001b[39m\n\u001b[31m      <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-1\\\" data-k=\\\"1\\\" aria-label=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-2\\\" data-k=\\\"2\\\" aria-label=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-3\\\" data-k=\\\"3\\\" aria-label=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-4\\\" data-k=\\\"4\\\" aria-label=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-5\\\" data-k=\\\"5\\\" aria-label=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-6\\\" data-k=\\\"6\\\" aria-label=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-7\\\" data-k=\\\"7\\\" aria-label=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-8\\\" data-k=\\\"8\\\" aria-label=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-9\\\" data-k=\\\"9\\\" aria-label=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key clr\\\" id=\\\"pk-C\\\" data-k=\\\"C\\\" aria-label=\\\"Clear\\\">CLR</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-0\\\" data-k=\\\"0\\\" aria-label=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key back\\\" id=\\\"pk-B\\\" data-k=\\\"B\\\" aria-label=\\\"Backspace\\\">⌫</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     HOME  (built dynamically)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\" id=\\\"home-header\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"home-body\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SHARE PICKER\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-share\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>Share Image To</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">Choose where to send this photo</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"share-prompt\\\">Which tool should receive this image?</div>\u001b[39m\n\u001b[31m    <div class=\\\"share-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-expense\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🧾</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Expenses</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-recce\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">📍</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Recce</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-installation\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🔧</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Installation</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     TEAM PINs  (admin only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-team\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-back\\\" style=\\\"font-size:20px\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Team PINs</h1><div class=\\\"sub\\\">Set employee login PINs</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-refresh\\\" title=\\\"Refresh\\\">🔄</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"info-note\\\">PINs are stored encrypted — current PIN cannot be viewed. You can set a new PIN for any employee below.</div>\u001b[39m\n\u001b[31m    <div id=\\\"team-list\\\"></div>·\u001b[39m\n\u001b[31m    <!-- Icon Admin — Harish & Pramod only -->\u001b[39m\n\u001b[31m    <div id=\\\"icon-admin-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:8px\\\">🎨 PWA Icons (Admin)</div>\u001b[39m\n\u001b[31m      <div class=\\\"info-note\\\" style=\\\"margin-bottom:10px\\\">Upload a new icon for any PWA. Upload both 192×192 and 512×512 sizes. PNG/JPG/WEBP accepted — auto-resized on server.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;flex-direction:column;gap:10px\\\">·\u001b[39m\n\u001b[31m        <!-- Secret management -->\u001b[39m\n\u001b[31m        <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:12px;padding:14px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px\\\">🔑 Admin Secret</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:center;gap:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"font-family:monospace;font-size:13px;color:var(--accent);background:var(--bg);\u001b[39m\n\u001b[31m              border-radius:6px;padding:8px 12px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\" id=\\\"icon-secret-display\\\">—</div>\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"toggleSecretVisible()\\\" id=\\\"btn-secret-show\\\">Show</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:10px\\\">Change secret (min 16 chars):</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m            <input id=\\\"icon-new-secret\\\" type=\\\"text\\\" placeholder=\\\"Enter new secret…\\\" style=\\\"flex:1;background:var(--bg);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m              padding:10px 12px;border-radius:8px;font-size:13px;font-family:monospace;outline:none\\\">\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"changeIconSecret()\\\" style=\\\"white-space:nowrap\\\">Update</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div id=\\\"icon-secret-err\\\" style=\\\"font-size:11px;color:var(--danger);min-height:14px;margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>·\u001b[39m\n\u001b[31m        <!-- Upload form -->\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Select PWA</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-pwa-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"hub\\\">Hub</option>\u001b[39m\n\u001b[31m            <option value=\\\"expense\\\">Expenses</option>\u001b[39m\n\u001b[31m            <option value=\\\"finance\\\">Finance</option>\u001b[39m\n\u001b[31m            <option value=\\\"installation\\\">Installation</option>\u001b[39m\n\u001b[31m            <option value=\\\"recce\\\">Recce</option>\u001b[39m\n\u001b[31m            <option value=\\\"dispatch\\\">Dispatch</option>\u001b[39m\n\u001b[31m            <option value=\\\"production\\\">Production</option>\u001b[39m\n\u001b[31m            <option value=\\\"vehicle\\\">Vehicle Log</option>\u001b[39m\n\u001b[31m            <option value=\\\"vrs\\\">Vehicle Request</option>\u001b[39m\n\u001b[31m            <option value=\\\"sales\\\">Sales</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Icon Size</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-size-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"192\\\">192 × 192 (standard)</option>\u001b[39m\n\u001b[31m            <option value=\\\"512\\\">512 × 512 (large)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Choose Image File</label>\u001b[39m\n\u001b[31m          <input type=\\\"file\\\" id=\\\"icon-file-input\\\" accept=\\\"image/png,image/jpeg,image/webp\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:14px;width:100%\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-preview-wrap\\\" style=\\\"display:none;text-align:center\\\">\u001b[39m\n\u001b[31m          <img id=\\\"icon-preview-img\\\" style=\\\"width:96px;height:96px;border-radius:16px;border:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-upload-err\\\" style=\\\"font-size:12px;color:var(--danger);min-height:16px\\\"></div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" onclick=\\\"uploadIcon()\\\">Upload Icon</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     MODAL: Admin set PIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-setpin\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🔑 Set PIN — <span id=\\\"setpin-target-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>New PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Enter new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-confirm-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Confirm New PIN</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-confirm\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Repeat new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-auth-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Your own current PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"setpin-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-setpin-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-setpin-save\\\">Set PIN</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     JOBS — Campaign list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-jobs\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Jobs</h1><div class=\\\"sub\\\">Campaigns &amp; Tours</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh jobs\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-campaign\\\" title=\\\"New Campaign\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"jobs-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     CAMPAIGN DETAIL — Tour list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-campaign\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 id=\\\"campaign-hdr-title\\\">Campaign</h1><div class=\\\"sub\\\" id=\\\"campaign-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh campaign\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-tour\\\" title=\\\"New Tour\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"campaign-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Campaign -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-campaign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">📋 New Campaign</div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Year</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-year\\\" min=\\\"2024\\\" max=\\\"2030\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Phase #</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-phase\\\" value=\\\"1\\\" min=\\\"1\\\" max=\\\"99\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Brand <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-brand\\\" placeholder=\\\"e.g. Lenovo\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Client <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-client\\\" placeholder=\\\"e.g. Lenovo India\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Campaign Name <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-campaign\\\" placeholder=\\\"e.g. Chandigarh BTL\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Generated Campaign ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nc-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nc-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nc-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nc-save\\\">Create</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Tour -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-tour\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px;max-height:88vh;overflow-y:auto\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🗺️ New Tour</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Tour Title</div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nt-title\\\" placeholder=\\\"e.g. Chandigarh Week 1\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Start Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-start\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">End Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-end\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:6px\\\">Assign Employees <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-employees\\\" style=\\\"display:flex;flex-direction:column;gap:6px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"color:var(--muted);font-size:13px\\\">Loading…</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Notes</div>\u001b[39m\n\u001b[31m      <textarea id=\\\"nt-notes\\\" rows=\\\"2\\\" placeholder=\\\"Optional instructions or remarks\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px;font-family:inherit;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Tour ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-id-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nt-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nt-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nt-save\\\">Create Tour</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- UPDATE BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"update-banner\\\">\u001b[39m\n\u001b[31m  <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ub-text\\\">App update available</div>\u001b[39m\n\u001b[31m    <div class=\\\"ub-sub\\\">Tap to refresh and get the latest version</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ub-btn\\\" id=\\\"btn-update\\\">Update now</button>\u001b[39m\n\u001b[31m  <button class=\\\"ub-close\\\" id=\\\"btn-update-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- INSTALL BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"install-banner\\\">\u001b[39m\n\u001b[31m  <div class=\\\"ib-icon\\\">📲</div>\u001b[39m\n\u001b[31m  <div class=\\\"ib-text\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ib-title\\\">Install 360LM Hub</div>\u001b[39m\n\u001b[31m    <div class=\\\"ib-sub\\\">Add to home screen for the best experience</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ib-btn\\\" id=\\\"btn-install\\\">Install</button>\u001b[39m\n\u001b[31m  <button class=\\\"ib-close\\\" id=\\\"btn-install-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: AI Config (mgmt only) -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-ai\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🤖 AI Config — Screenshot Extraction</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Tesseract OCR</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-tesseract\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Extraction Fallback</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-enabled\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Model</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-model\\\">\u001b[39m\n\u001b[31m        <option value=\\\"haiku\\\">Haiku (fast, cost-effective)</option>\u001b[39m\n\u001b[31m        <option value=\\\"sonnet\\\">Sonnet (higher accuracy)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-ai-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-ai-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"border-top:1px solid var(--border);padding-top:12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">API Key Health</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px;margin-bottom:10px\\\">Live check — result is always the same for all users</div>\u001b[39m\n\u001b[31m      <div id=\\\"api-key-status-wrap\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-check-keys\\\" style=\\\"margin-top:10px;width:100%\\\">Check API Keys</button>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:14px;border-top:1px solid var(--border);padding-top:12px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">View Usage &amp; Billing</div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Anthropic</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/usage\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Google Maps</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/google/maps-apis/api-list\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: Hub Quick-Action Config -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-qa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">⚡ Quick Actions</div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-config-list\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-cfg-err\\\" style=\\\"color:var(--danger);font-size:13px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-qa-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-qa-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SCRIPT\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31m/* ── CONFIG ── */\u001b[39m\n\u001b[31mconst CFG = {\u001b[39m\n\u001b[31m  api:        '/db',\u001b[39m\n\u001b[31m  apiHeaders: { 'Content-Type': 'application/json', 'Accept-Profile': 'expense' },\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31m/* ── SERVER ACCESS MAP (fetched from hub-access.json, overrides ACCESS for non-mgmt) ── */\u001b[39m\n\u001b[31mlet _serverAccess = {};\u001b[39m\n\u001b[31masync function loadHubAccess() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/hub/hub-access.json?t=' + Date.now());\u001b[39m\n\u001b[31m    if (r.ok) _serverAccess = await r.json();\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── ROLE ACCESS MAP (hardcoded fallback) ── */\u001b[39m\n\u001b[31mconst ACCESS = {\u001b[39m\n\u001b[31m  harish:     ['expense','installation','finance','recce','dispatch','production','jobs','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  pramod:     ['expense','installation','finance','recce','dispatch','production','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  rakesh:     ['installation','expense','production','jobs','vehicle','vrs','sales','hr'],\u001b[39m\n\u001b[31m  ashok:      ['expense','production','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  shambhu:    ['finance','expense','vehicle','vrs','stores','activity','hr'],\u001b[39m\n\u001b[31m  gurpreet:   ['expense','dispatch','vehicle','vrs','stores','hr'],\u001b[39m\n\u001b[31m  sachin:     ['installation','expense','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  mukesh:     ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  umashankar: ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  narender:   ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  amit:       ['vehicle','vrs','hr'],\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst TILES = [\u001b[39m\n\u001b[31m  { id:'expense',      label:'Expenses',     icon:'🧾', url:'/expense/',               live:true  },\u001b[39m\n\u001b[31m  { id:'installation', label:'Installation', icon:'🔧', url:'/installation/current/',  live:true  },\u001b[39m\n\u001b[31m  { id:'finance',      label:'Finance',      icon:'💰', url:'/finance/',               live:true  },\u001b[39m\n\u001b[31m  { id:'recce',        label:'Recce',        icon:'📍', url:'/recce/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'dispatch',     label:'Dispatch',     icon:'🚚', url:'/dispatch/',              live:true  },\u001b[39m\n\u001b[31m  { id:'production',   label:'Production',   icon:'⚙️', url:'/production/',            live:true  },\u001b[39m\n\u001b[31m  { id:'jobs',         label:'Jobs',         icon:'📋', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'vehicle',      label:'Vehicle Log',  icon:'🚐', url:'/vehicle/',               live:true  },\u001b[39m\n\u001b[31m  { id:'vrs',          label:'Veh. Request', icon:'📋', url:'/vrs/',                   live:true  },\u001b[39m\n\u001b[31m  { id:'sales',        label:'Sales',        icon:'💼', url:'/sales/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'stores',       label:'Stores',       icon:'📦', url:'/stores/',                live:true  },\u001b[39m\n\u001b[31m  { id:'client',       label:'Client',       icon:'👤', url:'/client/',                live:true  },\u001b[39m\n\u001b[31m  { id:'team-pins',    label:'Team PINs',    icon:'🔑', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'admin',        label:'Admin',        icon:'🛠️', url:'/admin/',                  live:true  },\u001b[39m\n\u001b[31m  { id:'activity',     label:'Activity',     icon:'🎯', url:'/activity/',               live:true  },\u001b[39m\n\u001b[31m  { id:'hr',           label:'HR',           icon:'👥', url:'/hr/',                     live:true  },\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31mconst MGMT = ['harish', 'pramod'];·\u001b[39m\n\u001b[31m/* ── STATE ── */\u001b[39m\n\u001b[31mlet currentUser     = null;\u001b[39m\n\u001b[31mlet pinBuffer       = '';\u001b[39m\n\u001b[31mlet setPinTarget    = null;\u001b[39m\n\u001b[31mlet pendingShare    = false;\u001b[39m\n\u001b[31mlet _loginAttempts  = 0;\u001b[39m\n\u001b[31mlet _lockoutUntil   = 0;\u001b[39m\n\u001b[31mlet _loginInFlight  = false;·\u001b[39m\n\u001b[31m/* ── UTILS ── */\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => { s.classList.remove('active'); s.style.display = ''; });\u001b[39m\n\u001b[31m  document.getElementById('screen-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet toastTimer;\u001b[39m\n\u001b[31mfunction toast(msg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.classList.add('show');\u001b[39m\n\u001b[31m  clearTimeout(toastTimer);\u001b[39m\n\u001b[31m  toastTimer = setTimeout(() => el.classList.remove('show'), 2800);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fmtINR(n) {\u001b[39m\n\u001b[31m  if (n >= 100000) return '₹' + (n / 100000).toFixed(1) + 'L';\u001b[39m\n\u001b[31m  if (n >= 1000)   return '₹' + (n / 1000).toFixed(1) + 'K';\u001b[39m\n\u001b[31m  return '₹' + Math.round(n);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SESSION ── */\u001b[39m\n\u001b[31mconst SESSION_KEY  = 'lm360-session';\u001b[39m\n\u001b[31mconst EXP_SES_KEY  = '360exp-session';\u001b[39m\n\u001b[31mconst SESSION_TTL  = 12 * 3600 * 1000;·\u001b[39m\n\u001b[31mfunction saveSession(user) {\u001b[39m\n\u001b[31m  const payload = { empId: user.id, name: user.name, role: user.role, loginAt: new Date().toISOString(), source: 'hub' };\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m  localStorage.setItem(EXP_SES_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearSession() {\u001b[39m\n\u001b[31m  localStorage.removeItem(SESSION_KEY);\u001b[39m\n\u001b[31m  localStorage.removeItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const raw = localStorage.getItem(SESSION_KEY) || localStorage.getItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m    if (!raw) return null;\u001b[39m\n\u001b[31m    const s = JSON.parse(raw);\u001b[39m\n\u001b[31m    if (!s.empId || !s.name || !s.role) return null;\u001b[39m\n\u001b[31m    if (Date.now() - new Date(s.loginAt).getTime() > SESSION_TTL) { clearSession(); return null; }\u001b[39m\n\u001b[31m    return s;\u001b[39m\n\u001b[31m  } catch (_) { return null; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGIN FLOW ── */\u001b[39m\n\u001b[31masync function initLogin() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('login-name');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— Choose employee —</option>'; // clear before repopulating\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    emps.forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    [\u001b[39m\n\u001b[31m      { id:'amit',        name:'Amit'             },\u001b[39m\n\u001b[31m      { id:'ashok',       name:'Ashok'            },\u001b[39m\n\u001b[31m      { id:'gurpreet',    name:'Gurpreet'         },\u001b[39m\n\u001b[31m      { id:'harish',      name:'Harish Kumar Lal' },\u001b[39m\n\u001b[31m      { id:'mukesh',      name:'Mukesh'           },\u001b[39m\n\u001b[31m      { id:'narender',    name:'Narender'         },\u001b[39m\n\u001b[31m      { id:'pramod',      name:'Pramod Narang'    },\u001b[39m\n\u001b[31m      { id:'rakesh',      name:'Rakesh'           },\u001b[39m\n\u001b[31m      { id:'sachin',      name:'Sachin'           },\u001b[39m\n\u001b[31m      { id:'shambhu',     name:'Shambhu'          },\u001b[39m\n\u001b[31m      { id:'umashankar',  name:'Umashankar'       },\u001b[39m\n\u001b[31m    ].forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  document.querySelectorAll('.pin-key').forEach(btn =>\u001b[39m\n\u001b[31m    btn.addEventListener('click', () => handlePinKey(btn.dataset.k))\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handlePinKey(k) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (_loginInFlight) return;\u001b[39m\n\u001b[31m  // Lockout guard\u001b[39m\n\u001b[31m  if (_lockoutUntil && Date.now() < _lockoutUntil) {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    errEl.textContent = `Too many attempts — wait ${secs}s`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (_lockoutUntil) { _lockoutUntil = 0; _loginAttempts = 0; }\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (k === 'C') { pinBuffer = ''; }\u001b[39m\n\u001b[31m  else if (k === 'B') { pinBuffer = pinBuffer.slice(0, -1); }\u001b[39m\n\u001b[31m  else {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (!empId) { toast('Select your name first'); return; }\u001b[39m\n\u001b[31m    if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  if (pinBuffer.length === 4) {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (empId) attemptLogin(empId, pinBuffer);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updatePinDisplay() {\u001b[39m\n\u001b[31m  for (let i = 0; i < 4; i++)\u001b[39m\n\u001b[31m    document.getElementById('pd' + i).classList.toggle('filled', i < pinBuffer.length);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _shakePin() {\u001b[39m\n\u001b[31m  const pd = document.getElementById('pin-display');\u001b[39m\n\u001b[31m  pd.classList.add('shake');\u001b[39m\n\u001b[31m  setTimeout(() => pd.classList.remove('shake'), 400);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _startLockout() {\u001b[39m\n\u001b[31m  _lockoutUntil = Date.now() + 30000;\u001b[39m\n\u001b[31m  _loginAttempts = 0;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  const tick = () => {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    if (secs > 0) { errEl.textContent = `Too many attempts — wait ${secs}s`; setTimeout(tick, 500); }\u001b[39m\n\u001b[31m    else { _lockoutUntil = 0; errEl.textContent = ''; }\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  tick();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function attemptLogin(empId, pin) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  _loginInFlight = true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pin }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      _loginAttempts++;\u001b[39m\n\u001b[31m      _shakePin();\u001b[39m\n\u001b[31m      if (_loginAttempts >= 5) { _startLockout(); return; }\u001b[39m\n\u001b[31m      errEl.textContent = `Wrong PIN — ${5 - _loginAttempts} attempt${5 - _loginAttempts !== 1 ? 's' : ''} left`;\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    currentUser = { id: user.id, name: user.name, role: user.role };\u001b[39m\n\u001b[31m    saveSession(currentUser);\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — check connection';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    _loginInFlight = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── HOME SCREEN ── */\u001b[39m\n\u001b[31masync function buildHome() {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const body   = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  // Management users always use hardcoded ACCESS; others prefer server-side hub-access.json\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const tiles  = accessIds.map(id => TILES.find(t => t.id === id)).filter(Boolean);·\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"avatar\\\">${initials}</div>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1 style=\\\"font-size:16px\\\">${currentUser.name}</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">${currentUser.role}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${isMgmt ? '<button class=\\\"btn-icon\\\" id=\\\"btn-hub-ai\\\" title=\\\"AI Config\\\" style=\\\"font-size:18px\\\">🤖</button>' : ''}\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m  if (isMgmt) document.getElementById('btn-hub-ai').addEventListener('click', openHubAiConfig);·\u001b[39m\n\u001b[31m  if (isMgmt) {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"hub-main-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-dash\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">📊</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">Dashboard</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-tools\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">🧰</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">All Tools</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-header\\\">\u001b[39m\n\u001b[31m        <div class=\\\"section-title\\\" style=\\\"margin:0\\\">Quick Actions</div>\u001b[39m\n\u001b[31m        <button class=\\\"btn-icon\\\" id=\\\"btn-hub-qa-cfg\\\" title=\\\"Configure Quick Actions\\\" style=\\\"font-size:18px;padding:8px;min-width:44px;min-height:44px\\\">⚙️</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-grid\\\" id=\\\"hub-qa-grid\\\"></div>`;\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-dash').addEventListener('click',  () => enterHubDash(tiles));\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-tools').addEventListener('click', () => enterHubTools(tiles));\u001b[39m\n\u001b[31m    document.getElementById('btn-hub-qa-cfg').addEventListener('click', openHubQaConfig);\u001b[39m\n\u001b[31m    renderHubQA();\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"tile-grid field\\\" id=\\\"tile-grid\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"text-align:center;font-size:12px;color:var(--muted);padding-top:8px\\\">Tap a tile to open your tool</div>`;\u001b[39m\n\u001b[31m    renderTiles(tiles, 'field');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderTiles(tiles, mode) {\u001b[39m\n\u001b[31m  const grid = document.getElementById('tile-grid');\u001b[39m\n\u001b[31m  tiles.forEach(tile => {\u001b[39m\n\u001b[31m    const div = document.createElement('div');\u001b[39m\n\u001b[31m    div.className = 'tile ' + mode + (tile.live ? '' : ' coming');\u001b[39m\n\u001b[31m    div.innerHTML = `<div class=\\\"tile-icon\\\">${tile.icon}</div><div class=\\\"tile-label\\\">${tile.label}</div>` +\u001b[39m\n\u001b[31m      (tile.live ? '' : '<div class=\\\"soon-badge\\\">Soon</div>');\u001b[39m\n\u001b[31m    div.addEventListener('click', () => handleTile(tile));\u001b[39m\n\u001b[31m    grid.appendChild(div);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handleTile(tile) {\u001b[39m\n\u001b[31m  if (!tile.live) { toast('Coming soon'); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'team-pins') { enterTeamPins(); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'jobs')      { enterJobs();     return; }\u001b[39m\n\u001b[31m  window.location.href = tile.url;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT SUB-VIEWS ── */\u001b[39m\n\u001b[31mfunction _mgmtHeader(title) {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-subview-back\\\" style=\\\"font-size:20px;font-weight:900\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 style=\\\"font-size:16px\\\">${title}</h1></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-subview-back').addEventListener('click', () => buildHome());\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubDash(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('Dashboard');\u001b[39m\n\u001b[31m  const hdr = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const refreshBtn = document.createElement('button');\u001b[39m\n\u001b[31m  refreshBtn.className = 'btn-icon';\u001b[39m\n\u001b[31m  refreshBtn.title = 'Refresh dashboard';\u001b[39m\n\u001b[31m  refreshBtn.setAttribute('aria-label', 'Refresh dashboard');\u001b[39m\n\u001b[31m  refreshBtn.style.cssText = 'font-size:18px';\u001b[39m\n\u001b[31m  refreshBtn.textContent = '🔄';\u001b[39m\n\u001b[31m  refreshBtn.addEventListener('click', loadKPIs);\u001b[39m\n\u001b[31m  hdr.insertBefore(refreshBtn, hdr.querySelector('#btn-logout'));\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Actions Required</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-actions\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Operations</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row kpi-4col\\\" id=\\\"kpi-ops\\\">${skel}${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Production</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-prod\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Installation</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-inst\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">HR</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-hr\\\">${skel}${skel}${skel}</div>`;\u001b[39m\n\u001b[31m  loadKPIs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubTools(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('All Tools');\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  body.innerHTML = `<div class=\\\"tile-grid mgmt\\\" id=\\\"tile-grid\\\"></div>`;\u001b[39m\n\u001b[31m  renderTiles(tiles, 'mgmt');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT AI CONFIG ── */\u001b[39m\n\u001b[31mconst HUB_FIN_HDR = { 'Content-Type': 'application/json', 'Accept-Profile': 'finance' };·\u001b[39m\n\u001b[31masync function openHubAiConfig() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r    = await fetch(`${CFG.api}/upi_config`, { headers: HUB_FIN_HDR });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const get  = key => rows.find(x => x.key === key)?.value;\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-tesseract').value = get('tesseract_enabled') || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-enabled').value   = get('ai_enabled')        || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-model').value     = get('ai_model')           || 'haiku';\u001b[39m\n\u001b[31m  } catch(_) { toast('Could not load AI config'); return; }\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-ai').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveHubAiConfig() {\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-hub-ai-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  const pairs = [\u001b[39m\n\u001b[31m    { key:'tesseract_enabled', value: document.getElementById('hub-ai-tesseract').value },\u001b[39m\n\u001b[31m    { key:'ai_enabled',        value: document.getElementById('hub-ai-enabled').value },\u001b[39m\n\u001b[31m    { key:'ai_model',          value: document.getElementById('hub-ai-model').value },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const results = await Promise.all(pairs.map(p =>\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/upi_config?key=eq.${p.key}`, {\u001b[39m\n\u001b[31m        method: 'PATCH',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'finance', 'Prefer': 'return=minimal' },\u001b[39m\n\u001b[31m        body: JSON.stringify({ value: p.value }),\u001b[39m\n\u001b[31m      })\u001b[39m\n\u001b[31m    ));\u001b[39m\n\u001b[31m    if (results.some(r => !r.ok)) throw new Error('partial failure');\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show');\u001b[39m\n\u001b[31m    toast('✅ AI config saved');\u001b[39m\n\u001b[31m  } catch(_) { toast('Save failed'); }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Save'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function checkApiKeyStatus() {\u001b[39m\n\u001b[31m  const btn  = document.getElementById('btn-check-keys');\u001b[39m\n\u001b[31m  const wrap = document.getElementById('api-key-status-wrap');\u001b[39m\n\u001b[31m  btn.textContent = 'Checking…';\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  wrap.innerHTML = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/tour-ai/key-status');\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Proxy error ' + r.status);\u001b[39m\n\u001b[31m    const d = await r.json();\u001b[39m\n\u001b[31m    function mkBadge(info, label, link) {\u001b[39m\n\u001b[31m      const col  = info.ok ? 'var(--success)' : 'var(--danger)';\u001b[39m\n\u001b[31m      const icon = info.ok ? '✅' : '❌';\u001b[39m\n\u001b[31m      const note = info.ok ? (info.model || 'Valid') : (info.error || 'Error');\u001b[39m\n\u001b[31m      const hint = info.hint ? ' · key ' + info.hint : '';\u001b[39m\n\u001b[31m      return `<div style=\\\"background:var(--card);border:1px solid var(--border);border-left:3px solid ${col};border-radius:8px;padding:10px 12px;display:flex;align-items:center;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:13px;font-weight:600;color:var(--text)\\\">${icon} ${label}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${note}${hint}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <a href=\\\"${link}\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:11px;color:var(--info);text-decoration:none;white-space:nowrap\\\">Billing ↗</a>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    wrap.innerHTML =\u001b[39m\n\u001b[31m      mkBadge(d.anthropic, 'Anthropic API',    'https://console.anthropic.com/settings/billing') +\u001b[39m\n\u001b[31m      mkBadge(d.maps,      'Google Maps API',  'https://console.cloud.google.com/billing');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    wrap.innerHTML = `<div style=\\\"font-size:12px;color:var(--danger)\\\">Error: ${e.message}</div>`;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.textContent = 'Check Again';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT QUICK ACTIONS ── */\u001b[39m\n\u001b[31mconst HUB_QA_DEFS = [\u001b[39m\n\u001b[31m  { id:'offer',       icon:'📋', label:'New Offer',    accessId:'sales',        fn:() => { window.location.href='/sales/'; } },\u001b[39m\n\u001b[31m  { id:'paytm',       icon:'📱', label:'UPI Pay',       accessId:'finance',      fn:() => { window.location.href='/finance/?qa=upi'; } },\u001b[39m\n\u001b[31m  { id:'expense',     icon:'🧾', label:'Expenses',      accessId:'expense',      fn:() => { window.location.href='/expense/'; } },\u001b[39m\n\u001b[31m  { id:'finance',     icon:'💰', label:'Finance',       accessId:'finance',      fn:() => { window.location.href='/finance/'; } },\u001b[39m\n\u001b[31m  { id:'dispatch',    icon:'🚚', label:'Dispatch',      accessId:'dispatch',     fn:() => { window.location.href='/dispatch/'; } },\u001b[39m\n\u001b[31m  { id:'stores',      icon:'📦', label:'Stores',        accessId:'stores',       fn:() => { window.location.href='/stores/'; } },\u001b[39m\n\u001b[31m  { id:'recce',       icon:'📍', label:'Recce',         accessId:'recce',        fn:() => { window.location.href='/recce/'; } },\u001b[39m\n\u001b[31m  { id:'production',  icon:'⚙️', label:'Production',   accessId:'production',   fn:() => { window.location.href='/production/'; } },\u001b[39m\n\u001b[31m  { id:'tours',       icon:'🗺️', label:'Tour Plan',   accessId:'installation', fn:() => { window.location.href='/tour-planner/'; } },\u001b[39m\n\u001b[31m  { id:'installation',icon:'🔧', label:'Installation',  accessId:'installation', fn:() => { window.location.href='/installation/current/'; } },\u001b[39m\n\u001b[31m  { id:'activity',    icon:'🎯', label:'Activity',      accessId:'activity',     fn:() => { window.location.href='/activity/'; } },\u001b[39m\n\u001b[31m];\u001b[39m\n\u001b[31mconst HUB_QA_DEFAULT = ['offer', 'paytm'];\u001b[39m\n\u001b[31mconst HUB_QA_STORE   = '360lm-hub-qa';\u001b[39m\n\u001b[31mlet   _hubQaTempSel  = [];·\u001b[39m\n\u001b[31mfunction getHubQA() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(HUB_QA_STORE));\u001b[39m\n\u001b[31m    if (Array.isArray(s) && s.length) return s;\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m  return [...HUB_QA_DEFAULT];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHubQA() {\u001b[39m\n\u001b[31m  const ids  = getHubQA();\u001b[39m\n\u001b[31m  const grid = document.getElementById('hub-qa-grid');\u001b[39m\n\u001b[31m  if (!grid) return;\u001b[39m\n\u001b[31m  if (!ids.length) {\u001b[39m\n\u001b[31m    grid.innerHTML = `<div style=\\\"color:var(--muted);font-size:13px;grid-column:1/-1;\u001b[39m\n\u001b[31m      text-align:center;padding:16px\\\">Tap ⚙️ to add quick actions</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  grid.innerHTML = ids.map(id => {\u001b[39m\n\u001b[31m    const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!d) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-tile\\\" onclick=\\\"hubQaRun('${id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction hubQaRun(id) {\u001b[39m\n\u001b[31m  const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m  if (d) d.fn();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openHubQaConfig() {\u001b[39m\n\u001b[31m  _hubQaTempSel = [...getHubQA()];\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const visibleDefs = HUB_QA_DEFS.filter(d => accessIds.includes(d.accessId));\u001b[39m\n\u001b[31m  const list = document.getElementById('hub-qa-config-list');\u001b[39m\n\u001b[31m  list.innerHTML = visibleDefs.map(d => {\u001b[39m\n\u001b[31m    const sel = _hubQaTempSel.includes(d.id);\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-item${sel?' selected':''}\\\" id=\\\"hub-qa-item-${d.id}\\\"\u001b[39m\n\u001b[31m        onclick=\\\"toggleHubQaItem('${d.id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-toggle\\\"></div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleHubQaItem(id) {\u001b[39m\n\u001b[31m  const idx = _hubQaTempSel.indexOf(id);\u001b[39m\n\u001b[31m  if (idx >= 0) {\u001b[39m\n\u001b[31m    _hubQaTempSel.splice(idx, 1);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    if (_hubQaTempSel.length >= 6) {\u001b[39m\n\u001b[31m      document.getElementById('hub-qa-cfg-err').textContent = 'Maximum 6 quick actions';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _hubQaTempSel.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  const el = document.getElementById('hub-qa-item-' + id);\u001b[39m\n\u001b[31m  if (el) el.className = 'hub-qa-item' + (_hubQaTempSel.includes(id) ? ' selected' : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction saveHubQaConfig() {\u001b[39m\n\u001b[31m  localStorage.setItem(HUB_QA_STORE, JSON.stringify(_hubQaTempSel));\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.remove('show');\u001b[39m\n\u001b[31m  renderHubQA();\u001b[39m\n\u001b[31m  toast('✅ Quick actions updated');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── KPI FETCH ── */\u001b[39m\n\u001b[31mfunction _hdr(schema) {\u001b[39m\n\u001b[31m  return { 'Content-Type': 'application/json', 'Accept-Profile': schema };\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _safe(p) { return p.catch(() => null); }·\u001b[39m\n\u001b[31masync function loadKPIs() {\u001b[39m\n\u001b[31m  const mon = firstOfMonth();\u001b[39m\n\u001b[31m  const curMonth = new Date().toISOString().slice(0, 7); // YYYY-MM\u001b[39m\n\u001b[31m  const todayIST = (() => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; })();·\u001b[39m\n\u001b[31m  // Reset sections to loading skeletons before fetching (makes refresh visible)\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  [['kpi-actions',3],['kpi-ops',4],['kpi-prod',3],['kpi-inst',3],['kpi-hr',3]].forEach(([id,n]) => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    if (el) el.innerHTML = skel.repeat(n);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  const [\u001b[39m\n\u001b[31m    sheetsRes, claimsRes, salaryRes,\u001b[39m\n\u001b[31m    cashRes,   dispRes,   recceRes,  prodRes,\u001b[39m\n\u001b[31m    instJobsRes, instCtrsRes, storeItemsRes,\u001b[39m\n\u001b[31m    hrAttRes, hrEmpRes, hrConcernRes, hrSlipsRes\u001b[39m\n\u001b[31m  ] = await Promise.all([\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/sheets?status=in.(submitted,escalated)&select=sheet_id`, { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/approved_pending_reimbursement?select=sheet_id`,         { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_register?status=in.(pending,partial)&month=eq.${curMonth}&select=record_id`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/accounts?account_id=eq.petty-cash&select=current_balance`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/dispatches?status=eq.active&select=dispatch_id`,         { headers: _hdr('dispatch') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/submissions?submitted_at=gte.${mon}&select=sub_id`,      { headers: _hdr('recce') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/orders?status=not.in.(dispatched)&select=order_id,status`, { headers: _hdr('production') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/jobs?select=job_id,status`,                              { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/counters?select=counter_id,status`,                      { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/items?active=eq.true&select=id,current_stock,min_stock`, { headers: _hdr('stores') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance?date=eq.${todayIST}&status=eq.present&select=attendance_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/employee_list?select=id`,                               { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance_concerns?status=eq.open&select=concern_id`,  { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_slips?month=eq.${curMonth}&status=eq.finalized&select=slip_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m  ]);·\u001b[39m\n\u001b[31m  const sheets    = sheetsRes?.ok    ? await sheetsRes.json()    : [];\u001b[39m\n\u001b[31m  const claims    = claimsRes?.ok    ? await claimsRes.json()    : [];\u001b[39m\n\u001b[31m  const salary    = salaryRes?.ok    ? await salaryRes.json()    : [];\u001b[39m\n\u001b[31m  const cashArr   = cashRes?.ok      ? await cashRes.json()      : [];\u001b[39m\n\u001b[31m  const disps     = dispRes?.ok      ? await dispRes.json()      : [];\u001b[39m\n\u001b[31m  const recces    = recceRes?.ok     ? await recceRes.json()     : [];\u001b[39m\n\u001b[31m  const prods     = prodRes?.ok      ? await prodRes.json()      : [];\u001b[39m\n\u001b[31m  const instJobs  = instJobsRes?.ok  ? await instJobsRes.json()  : [];\u001b[39m\n\u001b[31m  const instCtrs  = instCtrsRes?.ok  ? await instCtrsRes.json()  : [];\u001b[39m\n\u001b[31m  const storeItems = storeItemsRes?.ok ? await storeItemsRes.json() : [];\u001b[39m\n\u001b[31m  const hrAtt     = hrAttRes?.ok     ? await hrAttRes.json()     : [];\u001b[39m\n\u001b[31m  const hrEmp     = hrEmpRes?.ok     ? await hrEmpRes.json()     : [];\u001b[39m\n\u001b[31m  const hrConcern = hrConcernRes?.ok ? await hrConcernRes.json() : [];\u001b[39m\n\u001b[31m  const hrSlips   = hrSlipsRes?.ok   ? await hrSlipsRes.json()   : [];·\u001b[39m\n\u001b[31m  const cash = Number(cashArr[0]?.current_balance || 0);·\u001b[39m\n\u001b[31m  const actionsEl = document.getElementById('kpi-actions');\u001b[39m\n\u001b[31m  const opsEl     = document.getElementById('kpi-ops');\u001b[39m\n\u001b[31m  const prodEl    = document.getElementById('kpi-prod');\u001b[39m\n\u001b[31m  const instEl    = document.getElementById('kpi-inst');\u001b[39m\n\u001b[31m  const hrEl      = document.getElementById('kpi-hr');\u001b[39m\n\u001b[31m  if (!actionsEl || !opsEl || !prodEl || !instEl) return;·\u001b[39m\n\u001b[31m  // ── Actions Required ────────────────────────────────────────────\u001b[39m\n\u001b[31m  actionsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${sheets.length ? 'alert' : 'ok'}\\\" data-href=\\\"/expense/?tab=approvals\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${sheets.length ? 'red' : 'green'}\\\">${sheets.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Expense Approvals</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${claims.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=claims\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${claims.length ? 'amber' : 'green'}\\\">${claims.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Claims to Reimburse</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${salary.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=salary\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${salary.length ? 'amber' : 'green'}\\\">${salary.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Salary Due This Month</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Operations ──────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const cashColor = cash < 2000 ? 'red' : cash < 5000 ? 'amber' : 'green';\u001b[39m\n\u001b[31m  const cashCard  = cash < 2000 ? 'alert' : cash < 5000 ? 'warn' : 'ok';\u001b[39m\n\u001b[31m  const lowStock = storeItems.filter(i => i.current_stock < i.min_stock).length;\u001b[39m\n\u001b[31m  opsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${cashCard}\\\" data-href=\\\"/finance/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${cashColor}\\\">${fmtINR(cash)}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">${cash < 0 ? '⚠️ ' : ''}Petty Cash</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/dispatch/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${disps.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Dispatches</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/recce/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${recces.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Recce This Month</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${lowStock > 0 ? 'alert' : 'ok'}\\\" data-href=\\\"/stores/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${lowStock > 0 ? 'red' : 'green'}\\\">${lowStock}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Low Stock Items</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Production ───────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const prodPending = prods.filter(o => o.status === 'pending').length;\u001b[39m\n\u001b[31m  const prodActive  = prods.filter(o => o.status === 'in_production' || o.status === 'quality_check').length;\u001b[39m\n\u001b[31m  const prodReady   = prods.filter(o => o.status === 'ready').length;\u001b[39m\n\u001b[31m  prodEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodPending ? 'warn' : 'ok'}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodPending ? 'amber' : 'green'}\\\">${prodPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Pending Orders</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${prodActive}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">In Production</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodReady ? 'ok' : ''}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodReady ? 'green' : ''}\\\">${prodReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Ready to Dispatch</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Installation ─────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const activeJobs  = instJobs.filter(j => j.status === 'active').length;\u001b[39m\n\u001b[31m  const ctrsPending = instCtrs.filter(c => c.status === 'recce_pending' || c.status === 'mockup_pending' || c.status === 'unclear').length;\u001b[39m\n\u001b[31m  const ctrsReady   = instCtrs.filter(c => c.status === 'ready').length;\u001b[39m\n\u001b[31m  instEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${activeJobs ? 'info' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${activeJobs ? 'blue' : 'green'}\\\">${activeJobs}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Jobs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsPending ? 'warn' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsPending ? 'amber' : 'green'}\\\">${ctrsPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Pending</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsReady ? 'ok' : ''}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsReady ? 'green' : ''}\\\">${ctrsReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Ready</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── HR ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31m  if (hrEl) {\u001b[39m\n\u001b[31m    const totalEmp   = hrEmp.length || 0;\u001b[39m\n\u001b[31m    const presentCnt = hrAtt.length;\u001b[39m\n\u001b[31m    const attPct     = totalEmp > 0 ? Math.round(presentCnt / totalEmp * 100) : 0;\u001b[39m\n\u001b[31m    const attColor   = attPct >= 80 ? 'green' : attPct >= 50 ? 'amber' : 'red';\u001b[39m\n\u001b[31m    const attCard    = attPct >= 80 ? 'ok' : attPct >= 50 ? 'warn' : 'alert';\u001b[39m\n\u001b[31m    const concernCnt = hrConcern.length;\u001b[39m\n\u001b[31m    const slipsDue   = hrSlips.length;\u001b[39m\n\u001b[31m    hrEl.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${attCard}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${attColor}\\\">${attPct}%</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Attendance Today</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${concernCnt ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${concernCnt ? 'amber' : 'green'}\\\">${concernCnt}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Open Concerns</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${slipsDue ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${slipsDue ? 'amber' : 'green'}\\\">${slipsDue}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Slips to Pay</div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Wire tap-to-navigate on all KPI cards\u001b[39m\n\u001b[31m  document.querySelectorAll('.kpi-card[data-href]').forEach(card => {\u001b[39m\n\u001b[31m    card.addEventListener('click', () => window.location.href = card.dataset.href);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction firstOfMonth() {\u001b[39m\n\u001b[31m  const d = new Date();\u001b[39m\n\u001b[31m  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGOUT ── */\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  pinBuffer   = '';\u001b[39m\n\u001b[31m  pendingShare = false;\u001b[39m\n\u001b[31m  _loginAttempts = 0; _lockoutUntil = 0; _loginInFlight = false;\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  document.getElementById('login-name').value = '';\u001b[39m\n\u001b[31m  document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m  showScreen('login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── TEAM PINs ── */\u001b[39m\n\u001b[31masync function enterTeamPins() {\u001b[39m\n\u001b[31m  showScreen('team');\u001b[39m\n\u001b[31m  await loadTeamPins();\u001b[39m\n\u001b[31m  // Show icon admin section for harish/pramod\u001b[39m\n\u001b[31m  const sec = document.getElementById('icon-admin-section');\u001b[39m\n\u001b[31m  if (sec) {\u001b[39m\n\u001b[31m    sec.style.display = MGMT.includes(currentUser.id) ? 'block' : 'none';\u001b[39m\n\u001b[31m    if (MGMT.includes(currentUser.id)) refreshSecretDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── ICON ADMIN ────────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst ICON_SECRET_KEY    = 'lm360_icon_secret';\u001b[39m\n\u001b[31mconst ICON_SECRET_INITIAL = 'nlr8Zsqw6XsD0rEXPQ_RfpYiOeNhH-9VglYKoIsElHE';·\u001b[39m\n\u001b[31mfunction getIconSecret() {\u001b[39m\n\u001b[31m  return localStorage.getItem(ICON_SECRET_KEY) || ICON_SECRET_INITIAL;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction setIconSecret(s) {\u001b[39m\n\u001b[31m  localStorage.setItem(ICON_SECRET_KEY, s);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet secretVisible = false;\u001b[39m\n\u001b[31mfunction refreshSecretDisplay() {\u001b[39m\n\u001b[31m  const el = document.getElementById('icon-secret-display');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  const s = getIconSecret();\u001b[39m\n\u001b[31m  el.textContent = secretVisible ? s : s.slice(0,4) + '••••••••••••••••' + s.slice(-4);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleSecretVisible() {\u001b[39m\n\u001b[31m  secretVisible = !secretVisible;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-secret-show');\u001b[39m\n\u001b[31m  if (btn) btn.textContent = secretVisible ? 'Hide' : 'Show';\u001b[39m\n\u001b[31m  refreshSecretDisplay();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function changeIconSecret() {\u001b[39m\n\u001b[31m  const newSecret = document.getElementById('icon-new-secret').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-secret-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (newSecret.length < 16) { errEl.textContent = 'Minimum 16 characters'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/change-secret', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret(), 'X-New-Secret': newSecret }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Failed');\u001b[39m\n\u001b[31m    setIconSecret(newSecret);\u001b[39m\n\u001b[31m    document.getElementById('icon-new-secret').value = '';\u001b[39m\n\u001b[31m    refreshSecretDisplay();\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = '✓ Secret updated';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mdocument.addEventListener('DOMContentLoaded', () => {\u001b[39m\n\u001b[31m  const fileInput = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  if (fileInput) {\u001b[39m\n\u001b[31m    fileInput.addEventListener('change', function() {\u001b[39m\n\u001b[31m      const wrap = document.getElementById('icon-preview-wrap');\u001b[39m\n\u001b[31m      const img  = document.getElementById('icon-preview-img');\u001b[39m\n\u001b[31m      if (this.files[0]) { img.src = URL.createObjectURL(this.files[0]); wrap.style.display = 'block'; }\u001b[39m\n\u001b[31m      else { wrap.style.display = 'none'; }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function uploadIcon() {\u001b[39m\n\u001b[31m  const pwa   = document.getElementById('icon-pwa-select').value;\u001b[39m\n\u001b[31m  const size  = document.getElementById('icon-size-select').value;\u001b[39m\n\u001b[31m  const input = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-upload-err');\u001b[39m\n\u001b[31m  errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (!input.files[0]) { errEl.textContent = 'Choose an image file first'; return; }\u001b[39m\n\u001b[31m  const formData = new FormData();\u001b[39m\n\u001b[31m  formData.append('pwa', pwa);\u001b[39m\n\u001b[31m  formData.append('size', size);\u001b[39m\n\u001b[31m  formData.append('icon', input.files[0]);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret() },\u001b[39m\n\u001b[31m      body: formData\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Upload failed');\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = `✓ Icon updated for ${pwa} (${size}px). Clear browser cache / reinstall to see new icon.`;\u001b[39m\n\u001b[31m    input.value = '';\u001b[39m\n\u001b[31m    document.getElementById('icon-preview-wrap').style.display = 'none';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadTeamPins() {\u001b[39m\n\u001b[31m  const list = document.getElementById('team-list');\u001b[39m\n\u001b[31m  list.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    list.innerHTML = '';\u001b[39m\n\u001b[31m    emps.forEach(emp => {\u001b[39m\n\u001b[31m      const row = document.createElement('div');\u001b[39m\n\u001b[31m      row.className = 'emp-row';\u001b[39m\n\u001b[31m      row.innerHTML = `\u001b[39m\n\u001b[31m        <div style=\\\"min-width:0;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:15px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.name}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.role}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-secondary btn-sm\\\" data-id=\\\"${emp.id}\\\" data-name=\\\"${emp.name}\\\">Set PIN</button>`;\u001b[39m\n\u001b[31m      row.querySelector('button').addEventListener('click', () => openSetPin(emp.id, emp.name));\u001b[39m\n\u001b[31m      list.appendChild(row);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    list.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openSetPin(targetId, targetName) {\u001b[39m\n\u001b[31m  setPinTarget = targetId;\u001b[39m\n\u001b[31m  document.getElementById('setpin-target-name').textContent = targetName;\u001b[39m\n\u001b[31m  ['setpin-new','setpin-confirm','setpin-auth'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-setpin').classList.add('show');\u001b[39m\n\u001b[31m  setTimeout(() => document.getElementById('setpin-new').focus(), 50);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function doSetPin() {\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('setpin-new').value.trim();\u001b[39m\n\u001b[31m  const conf    = document.getElementById('setpin-confirm').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('setpin-auth').value.trim();\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('setpin-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  if (!newPin || !conf || !authPin) { errEl.textContent = 'All fields are required'; return; }\u001b[39m\n\u001b[31m  if (newPin !== conf)              { errEl.textContent = 'New PINs do not match'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))     { errEl.textContent = 'PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-setpin-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_admin_id:  currentUser.id,\u001b[39m\n\u001b[31m        p_admin_pin: authPin,\u001b[39m\n\u001b[31m        p_target_id: setPinTarget,\u001b[39m\n\u001b[31m        p_new_pin:   newPin,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const err = await resp.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Failed to set PIN';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m    toast('✅ PIN updated for ' + document.getElementById('setpin-target-name').textContent);\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Set PIN';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ═══════════════════════════════════════════\u001b[39m\n\u001b[31m   JOBS — Campaign + Tour management\u001b[39m\n\u001b[31m   Access: harish, pramod (campaigns + tours)\u001b[39m\n\u001b[31m           rakesh (tours only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ */\u001b[39m\n\u001b[31mconst INST_RD = { 'Accept-Profile': 'installation' };\u001b[39m\n\u001b[31mconst INST_WR = { 'Content-Type':'application/json','Accept-Profile':'installation','Content-Profile':'installation' };·\u001b[39m\n\u001b[31mlet _currentCampaignId = null;\u001b[39m\n\u001b[31mlet _allEmployees      = [];\u001b[39m\n\u001b[31mlet _editTourId        = null;  // null = create mode, string = edit mode·\u001b[39m\n\u001b[31m// ── Helpers ──────────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _slugify(s) {\u001b[39m\n\u001b[31m  return s.toUpperCase().trim()\u001b[39m\n\u001b[31m    .replace(/[^A-Z0-9]+/g, '_')\u001b[39m\n\u001b[31m    .replace(/^_|_$/g, '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _buildCampaignId(year, brand, client, campaign, phase) {\u001b[39m\n\u001b[31m  const nn = String(phase || 1).padStart(2, '0');\u001b[39m\n\u001b[31m  return `${year}-${_slugify(brand)}-${_slugify(client)}-${_slugify(campaign)}-${nn}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _campStatusBadge(s) {\u001b[39m\n\u001b[31m  const map = { active:'ok', completed:'', on_hold:'warn' };\u001b[39m\n\u001b[31m  const col = { active:'green', completed:'', on_hold:'amber' };\u001b[39m\n\u001b[31m  return `<span class=\\\"kpi-val ${col[s]||''}\\\" style=\\\"font-size:12px;font-weight:700\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _tourStatusBadge(s) {\u001b[39m\n\u001b[31m  const colors = { active:'var(--success)', planned:'var(--info)', completed:'var(--muted)', cancelled:'var(--danger)' };\u001b[39m\n\u001b[31m  return `<span style=\\\"font-size:11px;font-weight:700;padding:2px 8px;border-radius:10px;\u001b[39m\n\u001b[31m    background:var(--card);color:${colors[s]||'var(--muted)'}\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _canCreateCampaign() { return ['harish','pramod'].includes(currentUser?.id); }\u001b[39m\n\u001b[31mfunction _canCreateTour()     { return ['harish','pramod','rakesh'].includes(currentUser?.id); }·\u001b[39m\n\u001b[31m// ── Jobs screen ───────────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterJobs() {\u001b[39m\n\u001b[31m  showScreen('jobs');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').style.display = _canCreateCampaign() ? '' : 'none';\u001b[39m\n\u001b[31m  await loadJobs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadJobs() {\u001b[39m\n\u001b[31m  const el = document.getElementById('jobs-body');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?order=created_at.desc`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?select=tour_id,campaign_id,status`, { headers: INST_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const campaigns = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours     = tResp.ok ? await tResp.json() : [];·\u001b[39m\n\u001b[31m    if (!campaigns.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty\\\"><div style=\\\"font-size:32px\\\">📋</div><div>No campaigns yet</div></div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    el.innerHTML = campaigns.map(c => {\u001b[39m\n\u001b[31m      const cTours  = tours.filter(t => t.campaign_id === c.campaign_id);\u001b[39m\n\u001b[31m      const active  = cTours.filter(t => t.status === 'active').length;\u001b[39m\n\u001b[31m      const total   = cTours.length;\u001b[39m\n\u001b[31m      const statusColors = { active:'var(--success)', on_hold:'var(--accent2)', completed:'var(--muted)' };\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card tappable\\\" style=\\\"padding:14px\\\" onclick=\\\"enterCampaign('${c.campaign_id}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:flex-start;gap:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:15px;font-weight:700\\\">${c.brand} — ${c.campaign_name}\u001b[39m\n\u001b[31m                ${c.phase > 1 ? `<span style=\\\"font-size:11px;color:var(--muted)\\\"> Phase ${c.phase}</span>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:2px\\\">${c.client} · ${c.year}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:4px\\\">\u001b[39m\n\u001b[31m                ${total} tour${total!==1?'s':''} · ${active} active\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;align-items:flex-end;gap:6px\\\">\u001b[39m\n\u001b[31m              <span style=\\\"font-size:11px;font-weight:700;color:${statusColors[c.status]||'var(--muted)'}\\\">\u001b[39m\n\u001b[31m                ${(c.status||'').replace('_',' ')}\u001b[39m\n\u001b[31m              </span>\u001b[39m\n\u001b[31m              <span style=\\\"font-size:18px;color:var(--muted)\\\">›</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-top:8px;word-break:break-all\\\">${c.campaign_id}</div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign detail ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterCampaign(campaignId) {\u001b[39m\n\u001b[31m  _currentCampaignId = campaignId;\u001b[39m\n\u001b[31m  showScreen('campaign');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').style.display = _canCreateTour() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-sub').textContent   = '';\u001b[39m\n\u001b[31m  await loadCampaign(campaignId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCampaign(campaignId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('campaign-body');\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp, empResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(campaignId)}&order=start_date`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const camps = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours = tResp.ok ? await tResp.json() : [];\u001b[39m\n\u001b[31m    _allEmployees = empResp.ok ? await empResp.json() : [];·\u001b[39m\n\u001b[31m    if (!camps.length) { body.innerHTML = '<div class=\\\"empty\\\">Campaign not found</div>'; return; }\u001b[39m\n\u001b[31m    const c = camps[0];·\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-title').textContent = `${c.brand} — ${c.campaign_name}`;\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-sub').textContent   = `${c.client} · ${c.year}${c.phase>1?' · Phase '+c.phase:''}`;·\u001b[39m\n\u001b[31m    const empMap = {};\u001b[39m\n\u001b[31m    _allEmployees.forEach(e => empMap[e.id] = e.name);·\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"padding:12px 14px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Campaign ID</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:10px;color:var(--muted);word-break:break-all;text-align:right;max-width:65%\\\">${c.campaign_id}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Client</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:13px;font-weight:600\\\">${c.client}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${_canCreateCampaign() ? `\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div class=\\\"section-title\\\" style=\\\"margin-bottom:6px\\\">Campaign Status</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m            ${['active','on_hold','completed'].map(s =>\u001b[39m\n\u001b[31m              `<button class=\\\"status-chip${c.status===s?' sel':''}\\\" onclick=\\\"updateCampaignStatus('${c.campaign_id}','${s}')\\\">\u001b[39m\n\u001b[31m                ${s.replace('_',' ')}\u001b[39m\n\u001b[31m              </button>`).join('')}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>` : ''}\u001b[39m\n\u001b[31m      </div>·\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:4px\\\">Tours (${tours.length})</div>\u001b[39m\n\u001b[31m      ${tours.length === 0 ? `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\">\u001b[39m\n\u001b[31m        <div>No tours yet</div>\u001b[39m\n\u001b[31m        ${_canCreateTour() ? '<div style=\\\"font-size:12px\\\">Tap ＋ to add the first tour</div>' : ''}\u001b[39m\n\u001b[31m      </div>` :\u001b[39m\n\u001b[31m      tours.map(t => {\u001b[39m\n\u001b[31m        const empNames = (t.assigned_employees || []).map(id => empMap[id] || id).join(', ') || 'No one assigned';\u001b[39m\n\u001b[31m        const dates    = t.start_date ? `${t.start_date} → ${t.end_date||'?'}` : 'Dates not set';\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m          <div class=\\\"card\\\" style=\\\"padding:14px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;align-items:flex-start;gap:8px\\\">\u001b[39m\n\u001b[31m              <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m                <div style=\\\"font-size:14px;font-weight:700\\\">${t.title || t.tour_id}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">📅 ${dates}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">👥 ${empNames}</div>\u001b[39m\n\u001b[31m                ${t.notes ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">📝 ${t.notes}</div>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              ${_tourStatusBadge(t.status)}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            ${_canCreateTour() ? `\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;gap:6px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m              ${['planned','active','completed'].map(s =>\u001b[39m\n\u001b[31m                `<button class=\\\"status-chip${t.status===s?' sel':''}\\\"\u001b[39m\n\u001b[31m                  onclick=\\\"updateTourStatus('${t.tour_id}','${s}')\\\">${s}</button>`).join('')}\u001b[39m\n\u001b[31m              <button class=\\\"status-chip\\\" onclick=\\\"openEditTour('${t.tour_id}')\\\"\u001b[39m\n\u001b[31m                style=\\\"margin-left:auto;background:var(--surface);border-color:#3a3a6e;color:#8888ff\\\">\u001b[39m\n\u001b[31m                ✏️ Edit\u001b[39m\n\u001b[31m              </button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m            <div style=\\\"font-size:10px;color:var(--muted);margin-top:6px\\\">${t.tour_id}</div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m      <div style=\\\"height:20px\\\"></div>`;\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateTourStatus(tourId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Tour marked ${status}`);\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateCampaignStatus(campaignId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Campaign marked ${status.replace('_',' ')}`);\u001b[39m\n\u001b[31m    loadCampaign(campaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Campaign modal ────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewCampaign() {\u001b[39m\n\u001b[31m  if (!_canCreateCampaign()) { toast('Only Harish or Pramod can create campaigns'); return; }\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('nc-year').value    = new Date().getFullYear();\u001b[39m\n\u001b[31m  document.getElementById('nc-phase').value   = '1';\u001b[39m\n\u001b[31m  document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  document.getElementById('nc-err').textContent     = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-new-campaign').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateCampaignPreview() {\u001b[39m\n\u001b[31m  const year  = document.getElementById('nc-year').value;\u001b[39m\n\u001b[31m  const brand = document.getElementById('nc-brand').value;\u001b[39m\n\u001b[31m  const clt   = document.getElementById('nc-client').value;\u001b[39m\n\u001b[31m  const camp  = document.getElementById('nc-campaign').value;\u001b[39m\n\u001b[31m  const phase = document.getElementById('nc-phase').value;\u001b[39m\n\u001b[31m  if (brand && clt && camp) {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = _buildCampaignId(year, brand, clt, camp, phase);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewCampaign() {\u001b[39m\n\u001b[31m  const year   = Number(document.getElementById('nc-year').value);\u001b[39m\n\u001b[31m  const brand  = document.getElementById('nc-brand').value.trim();\u001b[39m\n\u001b[31m  const client = document.getElementById('nc-client').value.trim();\u001b[39m\n\u001b[31m  const cname  = document.getElementById('nc-campaign').value.trim();\u001b[39m\n\u001b[31m  const phase  = Number(document.getElementById('nc-phase').value) || 1;\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('nc-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const brandEl = document.getElementById('nc-brand');\u001b[39m\n\u001b[31m  const clientEl = document.getElementById('nc-client');\u001b[39m\n\u001b[31m  const cnameEl = document.getElementById('nc-campaign');\u001b[39m\n\u001b[31m  [brandEl, clientEl, cnameEl].forEach(el => el.style.borderColor = '');\u001b[39m\n\u001b[31m  let _missingField = false;\u001b[39m\n\u001b[31m  if (!brand)  { brandEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!client) { clientEl.style.borderColor = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!cname)  { cnameEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (_missingField) { errEl.textContent = 'Brand, Client and Campaign Name are required'; return; }\u001b[39m\n\u001b[31m  const campaignId = _buildCampaignId(year, brand, client, cname, phase);·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nc-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/campaigns`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ campaign_id:campaignId, year, brand, client, campaign_name:cname, phase, created_by:currentUser.id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || 'Failed to create';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show');\u001b[39m\n\u001b[31m    toast(`✅ Campaign created`);\u001b[39m\n\u001b[31m    loadJobs();\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Create'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Tour modal ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _buildEmpCheckboxes(assigned = []) {\u001b[39m\n\u001b[31m  return _allEmployees.map(e =>\u001b[39m\n\u001b[31m    `<label style=\\\"display:flex;align-items:center;gap:10px;padding:8px 10px;background:var(--card);\u001b[39m\n\u001b[31m      border:1px solid var(--border);border-radius:8px;cursor:pointer\\\">\u001b[39m\n\u001b[31m      <input type=\\\"checkbox\\\" value=\\\"${e.id}\\\" ${assigned.includes(e.id)?'checked':''} style=\\\"width:16px;height:16px;accent-color:var(--accent)\\\">\u001b[39m\n\u001b[31m      <span style=\\\"font-size:14px;font-weight:600\\\">${e.name}</span>\u001b[39m\n\u001b[31m      <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${e.role}</span>\u001b[39m\n\u001b[31m    </label>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openNewTour() {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = null;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '🗺️ New Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Create Tour';\u001b[39m\n\u001b[31m  document.getElementById('nt-title').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-start').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-end').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-notes').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(_currentCampaignId)}&select=tour_id`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const existing = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    const nn = String(existing.length + 1).padStart(2, '0');\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T${nn}`;\u001b[39m\n\u001b[31m  } catch(_) {\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T01`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (_allEmployees.length)\u001b[39m\n\u001b[31m    document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes();·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEditTour(tourId) {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = tourId;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '✏️ Edit Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Save Changes';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const tours = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    if (!tours.length) { toast('Tour not found'); return; }\u001b[39m\n\u001b[31m    const t = tours[0];\u001b[39m\n\u001b[31m    document.getElementById('nt-title').value = t.title || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-start').value = t.start_date || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-end').value   = t.end_date   || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-notes').value = t.notes      || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = tourId;\u001b[39m\n\u001b[31m    if (_allEmployees.length)\u001b[39m\n\u001b[31m      document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes(t.assigned_employees || []);\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to load tour'); return; }·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewTour() {\u001b[39m\n\u001b[31m  const title = document.getElementById('nt-title').value.trim();\u001b[39m\n\u001b[31m  const start = document.getElementById('nt-start').value || null;\u001b[39m\n\u001b[31m  const end   = document.getElementById('nt-end').value   || null;\u001b[39m\n\u001b[31m  const notes = document.getElementById('nt-notes').value.trim() || null;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('nt-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const checked = [...document.querySelectorAll('#nt-employees input:checked')].map(cb => cb.value);\u001b[39m\n\u001b[31m  if (!checked.length) { errEl.textContent = 'Assign at least one employee'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nt-save');\u001b[39m\n\u001b[31m  const wasEdit = !!_editTourId;\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = wasEdit ? 'Saving…' : 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editTourId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(_editTourId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      const tourId = document.getElementById('nt-id-preview').textContent;\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          tour_id: tourId, campaign_id: _currentCampaignId,\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes, status: 'planned',\u001b[39m\n\u001b[31m          created_by: currentUser.id,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || (_editTourId ? 'Failed to save' : 'Failed to create');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show');\u001b[39m\n\u001b[31m    toast(_editTourId ? '✅ Tour updated' : '✅ Tour created');\u001b[39m\n\u001b[31m    _editTourId = null;\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = wasEdit ? 'Save Changes' : 'Create Tour';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── EVENT WIRING ── */\u001b[39m\n\u001b[31mfunction wireEvents() {\u001b[39m\n\u001b[31m  // Jobs screens\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-back').addEventListener('click',     () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-refresh').addEventListener('click',  loadJobs);\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').addEventListener('click',  openNewCampaign);\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-back').addEventListener('click', () => showScreen('jobs'));\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-refresh').addEventListener('click', () => loadCampaign(_currentCampaignId));\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').addEventListener('click',      openNewTour);·\u001b[39m\n\u001b[31m  // New Campaign modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-save').addEventListener('click', saveNewCampaign);\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign','nc-year','nc-phase'].forEach(id =>\u001b[39m\n\u001b[31m    document.getElementById(id).addEventListener('input', updateCampaignPreview));·\u001b[39m\n\u001b[31m  // New Tour modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').addEventListener('click', saveNewTour);·\u001b[39m\n\u001b[31m  document.getElementById('btn-team-back').addEventListener('click', () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-team-refresh').addEventListener('click', loadTeamPins);\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-cancel').addEventListener('click', () => {\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-save').addEventListener('click', doSetPin);·\u001b[39m\n\u001b[31m  // Set PIN — progressive field reveal\u001b[39m\n\u001b[31m  document.getElementById('setpin-new').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const val = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const showConf = val.length === 4;\u001b[39m\n\u001b[31m    document.getElementById('setpin-confirm-group').style.display = showConf ? '' : 'none';\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m    if (!showConf) { document.getElementById('setpin-confirm').value = ''; document.getElementById('setpin-auth').value = ''; }\u001b[39m\n\u001b[31m    if (showConf) setTimeout(() => document.getElementById('setpin-confirm').focus(), 20);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const newVal = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const conf   = document.getElementById('setpin-confirm').value;\u001b[39m\n\u001b[31m    const showAuth = conf.length === 4 && conf === newVal;\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = showAuth ? '' : 'none';\u001b[39m\n\u001b[31m    if (!showAuth) document.getElementById('setpin-auth').value = '';\u001b[39m\n\u001b[31m    if (showAuth) setTimeout(() => document.getElementById('setpin-auth').focus(), 20);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // Hub AI config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-save').addEventListener('click',   saveHubAiConfig);\u001b[39m\n\u001b[31m  document.getElementById('btn-check-keys').addEventListener('click',    checkApiKeyStatus);·\u001b[39m\n\u001b[31m  // Hub QA config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-qa').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-save').addEventListener('click', saveHubQaConfig);·\u001b[39m\n\u001b[31m  // Share picker\u001b[39m\n\u001b[31m  document.getElementById('share-to-expense').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/expense/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-recce').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/recce/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-installation').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/installation/current/?share=1';\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-04: employee dropdown change resets PIN buffer and lockout\u001b[39m\n\u001b[31m  document.getElementById('login-name').addEventListener('change', () => {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-07: ESC key closes any open modal\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (e.key === 'Escape') {\u001b[39m\n\u001b[31m      document.querySelectorAll('.modal-overlay.show').forEach(m => m.classList.remove('show'));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-11: keyboard digit/backspace input on login screen\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (!document.getElementById('screen-login').classList.contains('active')) return;\u001b[39m\n\u001b[31m    if (e.key >= '0' && e.key <= '9') { handlePinKey(e.key); }\u001b[39m\n\u001b[31m    else if (e.key === 'Backspace') { e.preventDefault(); handlePinKey('B'); }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-02/B-08: re-check session TTL when user returns to the tab\u001b[39m\n\u001b[31m  document.addEventListener('visibilitychange', () => {\u001b[39m\n\u001b[31m    if (document.visibilityState === 'visible' && currentUser) {\u001b[39m\n\u001b[31m      if (!loadSession()) doLogout();\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── BOOT ── */\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  wireEvents();·\u001b[39m\n\u001b[31m  if (location.search.includes('share=1')) {\u001b[39m\n\u001b[31m    pendingShare = true;\u001b[39m\n\u001b[31m    history.replaceState({}, '', '/hub/');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Always populate the login dropdown (needed if user logs out later)\u001b[39m\n\u001b[31m  initLogin(); // fire-and-forget — does not block home screen path·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess) {\u001b[39m\n\u001b[31m    currentUser = { id: sess.empId, name: sess.name, role: sess.role };\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  setTimeout(() => showScreen('login'), 1500);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SERVICE WORKER: install + update ── */\u001b[39m\n\u001b[31mif ('serviceWorker' in navigator) {\u001b[39m\n\u001b[31m  navigator.serviceWorker.register('/hub/sw.js').then(reg => {·\u001b[39m\n\u001b[31m    // Install banner: show when browser signals app is installable\u001b[39m\n\u001b[31m    let deferredInstall = null;\u001b[39m\n\u001b[31m    window.addEventListener('beforeinstallprompt', e => {\u001b[39m\n\u001b[31m      e.preventDefault();\u001b[39m\n\u001b[31m      deferredInstall = e;\u001b[39m\n\u001b[31m      if (!window.matchMedia('(display-mode: standalone)').matches) {\u001b[39m\n\u001b[31m        const delay = document.getElementById('update-banner').classList.contains('show') ? 3500 : 0;\u001b[39m\n\u001b[31m        setTimeout(() => document.getElementById('install-banner').classList.add('show'), delay);\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    window.addEventListener('appinstalled', () => {\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install').addEventListener('click', async () => {\u001b[39m\n\u001b[31m      if (!deferredInstall) return;\u001b[39m\n\u001b[31m      deferredInstall.prompt();\u001b[39m\n\u001b[31m      await deferredInstall.userChoice;\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m    // Update banner: show when a new SW version is waiting to activate\u001b[39m\n\u001b[31m    function showUpdateBanner() {\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.add('show');\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (reg.waiting) showUpdateBanner();·\u001b[39m\n\u001b[31m    reg.addEventListener('updatefound', () => {\u001b[39m\n\u001b[31m      const newSW = reg.installing;\u001b[39m\n\u001b[31m      newSW.addEventListener('statechange', () => {\u001b[39m\n\u001b[31m        if (newSW.state === 'installed' && navigator.serviceWorker.controller) {\u001b[39m\n\u001b[31m          showUpdateBanner();\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    document.getElementById('btn-update').addEventListener('click', () => {\u001b[39m\n\u001b[31m      if (reg.waiting) {\u001b[39m\n\u001b[31m        reg.waiting.postMessage({ type: 'SKIP_WAITING' });\u001b[39m\n\u001b[31m        navigator.serviceWorker.addEventListener('controllerchange', () => {\u001b[39m\n\u001b[31m          window.location.reload();\u001b[39m\n\u001b[31m        }, { once: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-update-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m  }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mboot();\u001b[39m\n\u001b[31m</script>··\u001b[39m\n\u001b[31m</body></html>\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"recce-v10\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html><html lang=\\\"en\\\"><head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#FF6B35\\\">\u001b[39m\n\u001b[31m<meta name=\\\"mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-status-bar-style\\\" content=\\\"black-translucent\\\">\u001b[39m\n\u001b[31m<title>360LM</title>\u001b[39m\n\u001b[31m<meta name=\\\"description\\\" content=\\\"360LM Operations Hub — employee tools\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/hub/manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"icon\\\" href=\\\"/hub/icons/icon-192.png\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--surface:#161616;--card:#1e1e1e;--border:#2a2a2a;\u001b[39m\n\u001b[31m  --accent:#FF6B35;--accent2:#FFB800;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --danger:#e74c3c;--success:#2ecc71;--info:#3498db;\u001b[39m\n\u001b[31m  --radius:12px;--font:'Segoe UI',system-ui,sans-serif;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:var(--font);min-height:100vh;overflow-x:hidden}\u001b[39m\n\u001b[31m.screen{display:none;min-height:100vh;flex-direction:column}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── HEADER ── */\u001b[39m\n\u001b[31m.header{background:var(--surface);border-bottom:1px solid var(--border);padding:14px 16px;\u001b[39m\n\u001b[31m  display:flex;align-items:center;gap:12px;position:sticky;top:0;z-index:10;flex-shrink:0}\u001b[39m\n\u001b[31m.header h1{font-size:18px;font-weight:700;color:var(--accent);flex:1}\u001b[39m\n\u001b[31m.header .sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.btn-icon{background:none;border:none;color:var(--muted);font-size:22px;cursor:pointer;\u001b[39m\n\u001b[31m  padding:4px;border-radius:8px;line-height:1;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m.btn-icon:active{background:var(--border)}·\u001b[39m\n\u001b[31m/* ── BODY ── */\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:16px}·\u001b[39m\n\u001b[31m/* ── SPLASH ── */\u001b[39m\n\u001b[31m#screen-splash{align-items:center;justify-content:center;gap:8px}\u001b[39m\n\u001b[31m.splash-logo{font-size:72px;font-weight:900;color:var(--accent);letter-spacing:-2px;line-height:1}\u001b[39m\n\u001b[31m.splash-sub{font-size:15px;color:var(--muted);letter-spacing:2px;text-transform:uppercase}\u001b[39m\n\u001b[31m.splash-dot{width:8px;height:8px;border-radius:50%;background:var(--accent);\u001b[39m\n\u001b[31m  animation:pulse 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes pulse{0%,100%{opacity:.3;transform:scale(.8)}50%{opacity:1;transform:scale(1)}}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  min-height:100dvh;padding:24px;gap:20px;overflow-y:auto}\u001b[39m\n\u001b[31m.login-logo{font-size:44px;font-weight:900;color:var(--accent);letter-spacing:-1px}\u001b[39m\n\u001b[31m.login-sub{font-size:13px;color:var(--muted);text-transform:uppercase;letter-spacing:2px}\u001b[39m\n\u001b[31m.form-group{width:100%;max-width:360px;display:flex;flex-direction:column;gap:8px}\u001b[39m\n\u001b[31m.form-group label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px}\u001b[39m\n\u001b[31mselect{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31mselect:focus{border-color:var(--accent)}·\u001b[39m\n\u001b[31m/* ── PIN PAD ── */\u001b[39m\n\u001b[31m.pin-display{display:flex;gap:14px;justify-content:center;margin:4px 0}\u001b[39m\n\u001b[31m.pin-dot{width:16px;height:16px;border-radius:50%;border:2px solid var(--muted);background:transparent;transition:.15s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.pin-pad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;max-width:260px;margin:0 auto;width:100%}\u001b[39m\n\u001b[31m.pin-key{background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  font-size:22px;font-weight:600;padding:16px;border-radius:var(--radius);cursor:pointer;\u001b[39m\n\u001b[31m  text-align:center;line-height:1;user-select:none}\u001b[39m\n\u001b[31m.pin-key:active{background:var(--border)}\u001b[39m\n\u001b[31m.pin-key.clr{color:var(--danger);font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.pin-key.back{color:var(--accent2)}\u001b[39m\n\u001b[31m.login-err{font-size:13px;color:var(--danger);text-align:center;min-height:18px}\u001b[39m\n\u001b[31m@keyframes shake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-6px)}40%,80%{transform:translateX(6px)}}\u001b[39m\n\u001b[31m.pin-display.shake{animation:shake .4s ease}·\u001b[39m\n\u001b[31m/* ── KPI CARDS ── */\u001b[39m\n\u001b[31m.kpi-row{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\u001b[39m\n\u001b[31m.kpi-4col{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m#home-body{gap:10px}\u001b[39m\n\u001b[31m.kpi-card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:10px 8px;text-align:center;border-left:3px solid var(--accent);cursor:default}\u001b[39m\n\u001b[31m.kpi-card.alert{border-left-color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-card.warn{border-left-color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-card.ok{border-left-color:var(--success)}\u001b[39m\n\u001b[31m.kpi-card.info{border-left-color:var(--info)}\u001b[39m\n\u001b[31m.kpi-val{font-size:18px;font-weight:900;color:var(--accent);line-height:1.2}\u001b[39m\n\u001b[31m.kpi-val.red{color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-val.amber{color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-val.green{color:var(--success)}\u001b[39m\n\u001b[31m.kpi-val.blue{color:var(--info)}\u001b[39m\n\u001b[31m.kpi-lbl{font-size:10px;color:var(--muted);margin-top:4px;text-transform:uppercase;letter-spacing:.4px;line-height:1.3}\u001b[39m\n\u001b[31m.kpi-skel{background:var(--border);border-radius:4px;height:24px;margin-bottom:4px;\u001b[39m\n\u001b[31m  animation:shimmer 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes shimmer{0%,100%{opacity:.5}50%{opacity:1}}·\u001b[39m\n\u001b[31m/* ── SECTION TITLE ── */\u001b[39m\n\u001b[31m.section-title{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.8px}·\u001b[39m\n\u001b[31m/* ── MGMT HOME: MAIN TILES ── */\u001b[39m\n\u001b[31m.hub-main-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px}\u001b[39m\n\u001b[31m.hub-main-tile{background:var(--card);border:2px solid var(--border);border-radius:18px;\u001b[39m\n\u001b[31m  padding:36px 12px 28px;text-align:center;cursor:pointer;transition:background .15s,border-color .15s}\u001b[39m\n\u001b[31m.hub-main-tile:active{background:#1a2a1a;border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-main-icon{font-size:40px;line-height:1;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-main-label{font-size:15px;font-weight:800;color:var(--text)}\u001b[39m\n\u001b[31m/* ── MGMT HOME: QUICK ACTIONS ── */\u001b[39m\n\u001b[31m.hub-qa-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-qa-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(88px,1fr));gap:10px}\u001b[39m\n\u001b[31m.hub-qa-tile{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px 8px;text-align:center;cursor:pointer;display:flex;flex-direction:column;\u001b[39m\n\u001b[31m  align-items:center;gap:6px;transition:background .15s}\u001b[39m\n\u001b[31m.hub-qa-tile:active{background:var(--border)}\u001b[39m\n\u001b[31m.hub-qa-icon{font-size:26px;line-height:1}\u001b[39m\n\u001b[31m.hub-qa-label{font-size:11px;font-weight:700;color:var(--text);line-height:1.3}\u001b[39m\n\u001b[31m.hub-qa-item{display:flex;align-items:center;gap:12px;padding:10px 0;\u001b[39m\n\u001b[31m  border-bottom:1px solid var(--border);cursor:pointer}\u001b[39m\n\u001b[31m.hub-qa-item:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.hub-qa-item-icon{font-size:22px;width:32px;text-align:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item-label{flex:1;font-size:14px;font-weight:600}\u001b[39m\n\u001b[31m.hub-qa-item-toggle{width:20px;height:20px;border-radius:4px;border:2px solid var(--border);\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle::after{content:'✓';color:#fff;font-weight:700;font-size:12px}·\u001b[39m\n\u001b[31m/* ── TILE GRID ── */\u001b[39m\n\u001b[31m.tile-grid{display:grid;gap:12px}\u001b[39m\n\u001b[31m.tile-grid.mgmt{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m.tile-grid.field{grid-template-columns:repeat(2,1fr)}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:18px 8px;display:flex;flex-direction:column;align-items:center;gap:8px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s;position:relative}\u001b[39m\n\u001b[31m.tile:active{background:var(--border);transform:scale(.97)}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px;line-height:1}\u001b[39m\n\u001b[31m.tile-label{font-size:11px;font-weight:700;color:var(--text);text-align:center;text-transform:uppercase;letter-spacing:.4px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-icon{font-size:24px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-label{font-size:10px}\u001b[39m\n\u001b[31m.tile.coming{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.tile.coming:active{transform:none;background:var(--card)}\u001b[39m\n\u001b[31m.soon-badge{position:absolute;top:6px;right:6px;background:var(--accent2);color:#000;\u001b[39m\n\u001b[31m  font-size:8px;font-weight:800;padding:2px 5px;border-radius:20px;text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── USER BADGE ── */\u001b[39m\n\u001b[31m.user-badge{display:flex;align-items:center;gap:12px}\u001b[39m\n\u001b[31m.avatar{width:44px;height:44px;background:var(--accent);border-radius:50%;\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:800;\u001b[39m\n\u001b[31m  color:#fff;flex-shrink:0;text-transform:uppercase}\u001b[39m\n\u001b[31m.user-name{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.user-role{font-size:12px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── TEAM PINS ── */\u001b[39m\n\u001b[31m.emp-row{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px}\u001b[39m\n\u001b[31m.btn{display:flex;align-items:center;justify-content:center;gap:6px;border:none;\u001b[39m\n\u001b[31m  border-radius:var(--radius);font-size:15px;font-weight:700;padding:12px 18px;\u001b[39m\n\u001b[31m  cursor:pointer;width:100%;transition:.15s;font-family:var(--font)}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--card);border:1px solid var(--border);color:var(--text)}\u001b[39m\n\u001b[31m.btn-sm{padding:7px 12px;font-size:12px;font-weight:700;width:auto;flex-shrink:0}\u001b[39m\n\u001b[31m.info-note{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;font-size:13px;color:var(--muted);line-height:1.5}·\u001b[39m\n\u001b[31m/* ── MODAL ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:200;\u001b[39m\n\u001b[31m  align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;\u001b[39m\n\u001b[31m  padding:24px;width:100%;max-width:480px;display:flex;flex-direction:column;gap:14px;\u001b[39m\n\u001b[31m  max-height:90dvh;overflow-y:auto;-webkit-overflow-scrolling:touch}\u001b[39m\n\u001b[31m.modal-title{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.modal-box input[type=password]{\u001b[39m\n\u001b[31m  width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31m.modal-box input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.action-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px}·\u001b[39m\n\u001b[31m/* ── SHARE PICKER ── */\u001b[39m\n\u001b[31m#screen-share .body{align-items:center;justify-content:center;gap:24px}\u001b[39m\n\u001b[31m.share-prompt{font-size:15px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.share-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;width:100%;max-width:360px}\u001b[39m\n\u001b[31m.share-tile{background:var(--card);border:2px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:24px 8px;display:flex;flex-direction:column;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s}\u001b[39m\n\u001b[31m.share-tile:active{background:var(--border);transform:scale(.97);border-color:var(--accent)}\u001b[39m\n\u001b[31m.share-tile-icon{font-size:36px;line-height:1}\u001b[39m\n\u001b[31m.share-tile-label{font-size:12px;font-weight:700;color:var(--text);text-align:center;\u001b[39m\n\u001b[31m  text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── INSTALL BANNER ── */\u001b[39m\n\u001b[31m#install-banner{display:none;position:fixed;bottom:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:linear-gradient(135deg,#1a1a1a,#0d2010);\u001b[39m\n\u001b[31m  border-top:1px solid var(--accent);padding:14px 16px;\u001b[39m\n\u001b[31m  align-items:center;gap:12px;box-shadow:0 -4px 24px rgba(0,0,0,.7)}\u001b[39m\n\u001b[31m#install-banner.show{display:flex}\u001b[39m\n\u001b[31m#install-banner .ib-icon{font-size:30px;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-text{flex:1}\u001b[39m\n\u001b[31m#install-banner .ib-title{font-size:14px;font-weight:700;color:var(--text)}\u001b[39m\n\u001b[31m#install-banner .ib-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#install-banner .ib-btn{background:var(--accent);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-close{background:none;border:none;color:var(--muted);font-size:20px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── UPDATE BANNER ── */\u001b[39m\n\u001b[31m#update-banner{display:none;position:fixed;top:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:#1a2a1a;border-bottom:2px solid var(--success);\u001b[39m\n\u001b[31m  padding:12px 16px;align-items:center;gap:12px}\u001b[39m\n\u001b[31m#update-banner.show{display:flex}\u001b[39m\n\u001b[31m#update-banner .ub-text{flex:1;font-size:14px;font-weight:600;color:var(--text)}\u001b[39m\n\u001b[31m#update-banner .ub-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#update-banner .ub-btn{background:var(--success);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:8px 14px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#update-banner .ub-close{background:none;border:none;color:var(--muted);font-size:18px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#222;color:#fff;padding:10px 20px;border-radius:20px;font-size:14px;font-weight:600;\u001b[39m\n\u001b[31m  opacity:0;transition:.3s;pointer-events:none;white-space:nowrap;z-index:500}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  padding:40px;color:var(--muted);font-size:14px;gap:12px}\u001b[39m\n\u001b[31m.spinner{width:36px;height:36px;border:3px solid var(--border);border-top-color:var(--accent);\u001b[39m\n\u001b[31m  border-radius:50%;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* ── CARD (jobs / campaign screens) ── */\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius)}\u001b[39m\n\u001b[31m.card.tappable:active{border-color:var(--accent);opacity:.85}·\u001b[39m\n\u001b[31m/* ── STATUS CHIPS (tour / campaign) ── */\u001b[39m\n\u001b[31m.status-chip{font-size:11px;padding:4px 11px;border-radius:8px;cursor:pointer;\u001b[39m\n\u001b[31m  background:var(--card);color:var(--text);border:1px solid var(--border);font-weight:600;\u001b[39m\n\u001b[31m  font-family:var(--font);transition:.15s}\u001b[39m\n\u001b[31m.status-chip.sel{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.status-chip:active{opacity:.8}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SPLASH\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-splash\\\" class=\\\"screen active\\\">\u001b[39m\n\u001b[31m  <div class=\\\"splash-logo\\\">360LM</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-sub\\\">Operations Hub</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-dot\\\" style=\\\"margin-top:16px\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     LOGIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-login\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-wrap\\\">\u001b[39m\n\u001b[31m    <div class=\\\"login-logo\\\">360LM</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">Operations Hub</div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Select Your Name</label>\u001b[39m\n\u001b[31m      <select id=\\\"login-name\\\"><option value=\\\"\\\">— Choose employee —</option><option value=\\\"amit\\\">Amit</option><option value=\\\"ashok\\\">Ashok</option><option value=\\\"gurpreet\\\">Gurpreet</option><option value=\\\"harish\\\">Harish Kumar Lal</option><option value=\\\"mukesh\\\">Mukesh</option><option value=\\\"narender\\\">Narender</option><option value=\\\"pramod\\\">Pramod Narang</option><option value=\\\"rakesh\\\">Rakesh</option><option value=\\\"sachin\\\">Sachin</option><option value=\\\"shambhu\\\">Shambhu</option><option value=\\\"umashankar\\\">Umashankar</option></select>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m      <label style=\\\"text-align:center\\\">Enter PIN</label>\u001b[39m\n\u001b[31m      <div class=\\\"pin-display\\\" id=\\\"pin-display\\\">\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd0\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd1\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd2\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd3\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"login-err\\\" id=\\\"login-err\\\"></div>\u001b[39m\n\u001b[31m      <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-1\\\" data-k=\\\"1\\\" aria-label=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-2\\\" data-k=\\\"2\\\" aria-label=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-3\\\" data-k=\\\"3\\\" aria-label=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-4\\\" data-k=\\\"4\\\" aria-label=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-5\\\" data-k=\\\"5\\\" aria-label=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-6\\\" data-k=\\\"6\\\" aria-label=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-7\\\" data-k=\\\"7\\\" aria-label=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-8\\\" data-k=\\\"8\\\" aria-label=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-9\\\" data-k=\\\"9\\\" aria-label=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key clr\\\" id=\\\"pk-C\\\" data-k=\\\"C\\\" aria-label=\\\"Clear\\\">CLR</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-0\\\" data-k=\\\"0\\\" aria-label=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key back\\\" id=\\\"pk-B\\\" data-k=\\\"B\\\" aria-label=\\\"Backspace\\\">⌫</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     HOME  (built dynamically)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\" id=\\\"home-header\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"home-body\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SHARE PICKER\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-share\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>Share Image To</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">Choose where to send this photo</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"share-prompt\\\">Which tool should receive this image?</div>\u001b[39m\n\u001b[31m    <div class=\\\"share-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-expense\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🧾</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Expenses</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-recce\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">📍</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Recce</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-installation\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🔧</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Installation</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     TEAM PINs  (admin only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-team\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-back\\\" style=\\\"font-size:20px\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Team PINs</h1><div class=\\\"sub\\\">Set employee login PINs</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-refresh\\\" title=\\\"Refresh\\\">🔄</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"info-note\\\">PINs are stored encrypted — current PIN cannot be viewed. You can set a new PIN for any employee below.</div>\u001b[39m\n\u001b[31m    <div id=\\\"team-list\\\"></div>·\u001b[39m\n\u001b[31m    <!-- Icon Admin — Harish & Pramod only -->\u001b[39m\n\u001b[31m    <div id=\\\"icon-admin-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:8px\\\">🎨 PWA Icons (Admin)</div>\u001b[39m\n\u001b[31m      <div class=\\\"info-note\\\" style=\\\"margin-bottom:10px\\\">Upload a new icon for any PWA. Upload both 192×192 and 512×512 sizes. PNG/JPG/WEBP accepted — auto-resized on server.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;flex-direction:column;gap:10px\\\">·\u001b[39m\n\u001b[31m        <!-- Secret management -->\u001b[39m\n\u001b[31m        <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:12px;padding:14px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px\\\">🔑 Admin Secret</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:center;gap:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"font-family:monospace;font-size:13px;color:var(--accent);background:var(--bg);\u001b[39m\n\u001b[31m              border-radius:6px;padding:8px 12px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\" id=\\\"icon-secret-display\\\">—</div>\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"toggleSecretVisible()\\\" id=\\\"btn-secret-show\\\">Show</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:10px\\\">Change secret (min 16 chars):</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m            <input id=\\\"icon-new-secret\\\" type=\\\"text\\\" placeholder=\\\"Enter new secret…\\\" style=\\\"flex:1;background:var(--bg);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m              padding:10px 12px;border-radius:8px;font-size:13px;font-family:monospace;outline:none\\\">\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"changeIconSecret()\\\" style=\\\"white-space:nowrap\\\">Update</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div id=\\\"icon-secret-err\\\" style=\\\"font-size:11px;color:var(--danger);min-height:14px;margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>·\u001b[39m\n\u001b[31m        <!-- Upload form -->\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Select PWA</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-pwa-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"hub\\\">Hub</option>\u001b[39m\n\u001b[31m            <option value=\\\"expense\\\">Expenses</option>\u001b[39m\n\u001b[31m            <option value=\\\"finance\\\">Finance</option>\u001b[39m\n\u001b[31m            <option value=\\\"installation\\\">Installation</option>\u001b[39m\n\u001b[31m            <option value=\\\"recce\\\">Recce</option>\u001b[39m\n\u001b[31m            <option value=\\\"dispatch\\\">Dispatch</option>\u001b[39m\n\u001b[31m            <option value=\\\"production\\\">Production</option>\u001b[39m\n\u001b[31m            <option value=\\\"vehicle\\\">Vehicle Log</option>\u001b[39m\n\u001b[31m            <option value=\\\"vrs\\\">Vehicle Request</option>\u001b[39m\n\u001b[31m            <option value=\\\"sales\\\">Sales</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Icon Size</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-size-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"192\\\">192 × 192 (standard)</option>\u001b[39m\n\u001b[31m            <option value=\\\"512\\\">512 × 512 (large)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Choose Image File</label>\u001b[39m\n\u001b[31m          <input type=\\\"file\\\" id=\\\"icon-file-input\\\" accept=\\\"image/png,image/jpeg,image/webp\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:14px;width:100%\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-preview-wrap\\\" style=\\\"display:none;text-align:center\\\">\u001b[39m\n\u001b[31m          <img id=\\\"icon-preview-img\\\" style=\\\"width:96px;height:96px;border-radius:16px;border:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-upload-err\\\" style=\\\"font-size:12px;color:var(--danger);min-height:16px\\\"></div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" onclick=\\\"uploadIcon()\\\">Upload Icon</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     MODAL: Admin set PIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-setpin\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🔑 Set PIN — <span id=\\\"setpin-target-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>New PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Enter new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-confirm-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Confirm New PIN</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-confirm\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Repeat new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-auth-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Your own current PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"setpin-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-setpin-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-setpin-save\\\">Set PIN</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     JOBS — Campaign list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-jobs\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Jobs</h1><div class=\\\"sub\\\">Campaigns &amp; Tours</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh jobs\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-campaign\\\" title=\\\"New Campaign\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"jobs-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     CAMPAIGN DETAIL — Tour list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-campaign\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 id=\\\"campaign-hdr-title\\\">Campaign</h1><div class=\\\"sub\\\" id=\\\"campaign-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh campaign\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-tour\\\" title=\\\"New Tour\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"campaign-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Campaign -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-campaign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">📋 New Campaign</div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Year</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-year\\\" min=\\\"2024\\\" max=\\\"2030\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Phase #</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-phase\\\" value=\\\"1\\\" min=\\\"1\\\" max=\\\"99\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Brand <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-brand\\\" placeholder=\\\"e.g. Lenovo\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Client <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-client\\\" placeholder=\\\"e.g. Lenovo India\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Campaign Name <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-campaign\\\" placeholder=\\\"e.g. Chandigarh BTL\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Generated Campaign ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nc-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nc-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nc-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nc-save\\\">Create</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Tour -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-tour\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px;max-height:88vh;overflow-y:auto\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🗺️ New Tour</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Tour Title</div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nt-title\\\" placeholder=\\\"e.g. Chandigarh Week 1\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Start Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-start\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">End Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-end\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:6px\\\">Assign Employees <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-employees\\\" style=\\\"display:flex;flex-direction:column;gap:6px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"color:var(--muted);font-size:13px\\\">Loading…</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Notes</div>\u001b[39m\n\u001b[31m      <textarea id=\\\"nt-notes\\\" rows=\\\"2\\\" placeholder=\\\"Optional instructions or remarks\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px;font-family:inherit;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Tour ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-id-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nt-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nt-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nt-save\\\">Create Tour</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- UPDATE BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"update-banner\\\">\u001b[39m\n\u001b[31m  <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ub-text\\\">App update available</div>\u001b[39m\n\u001b[31m    <div class=\\\"ub-sub\\\">Tap to refresh and get the latest version</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ub-btn\\\" id=\\\"btn-update\\\">Update now</button>\u001b[39m\n\u001b[31m  <button class=\\\"ub-close\\\" id=\\\"btn-update-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- INSTALL BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"install-banner\\\">\u001b[39m\n\u001b[31m  <div class=\\\"ib-icon\\\">📲</div>\u001b[39m\n\u001b[31m  <div class=\\\"ib-text\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ib-title\\\">Install 360LM Hub</div>\u001b[39m\n\u001b[31m    <div class=\\\"ib-sub\\\">Add to home screen for the best experience</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ib-btn\\\" id=\\\"btn-install\\\">Install</button>\u001b[39m\n\u001b[31m  <button class=\\\"ib-close\\\" id=\\\"btn-install-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: AI Config (mgmt only) -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-ai\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🤖 AI Config — Screenshot Extraction</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Tesseract OCR</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-tesseract\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Extraction Fallback</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-enabled\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Model</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-model\\\">\u001b[39m\n\u001b[31m        <option value=\\\"haiku\\\">Haiku (fast, cost-effective)</option>\u001b[39m\n\u001b[31m        <option value=\\\"sonnet\\\">Sonnet (higher accuracy)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-ai-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-ai-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"border-top:1px solid var(--border);padding-top:12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">API Key Health</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px;margin-bottom:10px\\\">Live check — result is always the same for all users</div>\u001b[39m\n\u001b[31m      <div id=\\\"api-key-status-wrap\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-check-keys\\\" style=\\\"margin-top:10px;width:100%\\\">Check API Keys</button>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:14px;border-top:1px solid var(--border);padding-top:12px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">View Usage &amp; Billing</div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Anthropic</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/usage\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Google Maps</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/google/maps-apis/api-list\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: Hub Quick-Action Config -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-qa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">⚡ Quick Actions</div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-config-list\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-cfg-err\\\" style=\\\"color:var(--danger);font-size:13px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-qa-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-qa-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SCRIPT\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31m/* ── CONFIG ── */\u001b[39m\n\u001b[31mconst CFG = {\u001b[39m\n\u001b[31m  api:        '/db',\u001b[39m\n\u001b[31m  apiHeaders: { 'Content-Type': 'application/json', 'Accept-Profile': 'expense' },\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31m/* ── SERVER ACCESS MAP (fetched from hub-access.json, overrides ACCESS for non-mgmt) ── */\u001b[39m\n\u001b[31mlet _serverAccess = {};\u001b[39m\n\u001b[31masync function loadHubAccess() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/hub/hub-access.json?t=' + Date.now());\u001b[39m\n\u001b[31m    if (r.ok) _serverAccess = await r.json();\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── ROLE ACCESS MAP (hardcoded fallback) ── */\u001b[39m\n\u001b[31mconst ACCESS = {\u001b[39m\n\u001b[31m  harish:     ['expense','installation','finance','recce','dispatch','production','jobs','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  pramod:     ['expense','installation','finance','recce','dispatch','production','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  rakesh:     ['installation','expense','production','jobs','vehicle','vrs','sales','hr'],\u001b[39m\n\u001b[31m  ashok:      ['expense','production','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  shambhu:    ['finance','expense','vehicle','vrs','stores','activity','hr'],\u001b[39m\n\u001b[31m  gurpreet:   ['expense','dispatch','vehicle','vrs','stores','hr'],\u001b[39m\n\u001b[31m  sachin:     ['installation','expense','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  mukesh:     ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  umashankar: ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  narender:   ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  amit:       ['vehicle','vrs','hr'],\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst TILES = [\u001b[39m\n\u001b[31m  { id:'expense',      label:'Expenses',     icon:'🧾', url:'/expense/',               live:true  },\u001b[39m\n\u001b[31m  { id:'installation', label:'Installation', icon:'🔧', url:'/installation/current/',  live:true  },\u001b[39m\n\u001b[31m  { id:'finance',      label:'Finance',      icon:'💰', url:'/finance/',               live:true  },\u001b[39m\n\u001b[31m  { id:'recce',        label:'Recce',        icon:'📍', url:'/recce/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'dispatch',     label:'Dispatch',     icon:'🚚', url:'/dispatch/',              live:true  },\u001b[39m\n\u001b[31m  { id:'production',   label:'Production',   icon:'⚙️', url:'/production/',            live:true  },\u001b[39m\n\u001b[31m  { id:'jobs',         label:'Jobs',         icon:'📋', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'vehicle',      label:'Vehicle Log',  icon:'🚐', url:'/vehicle/',               live:true  },\u001b[39m\n\u001b[31m  { id:'vrs',          label:'Veh. Request', icon:'📋', url:'/vrs/',                   live:true  },\u001b[39m\n\u001b[31m  { id:'sales',        label:'Sales',        icon:'💼', url:'/sales/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'stores',       label:'Stores',       icon:'📦', url:'/stores/',                live:true  },\u001b[39m\n\u001b[31m  { id:'client',       label:'Client',       icon:'👤', url:'/client/',                live:true  },\u001b[39m\n\u001b[31m  { id:'team-pins',    label:'Team PINs',    icon:'🔑', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'admin',        label:'Admin',        icon:'🛠️', url:'/admin/',                  live:true  },\u001b[39m\n\u001b[31m  { id:'activity',     label:'Activity',     icon:'🎯', url:'/activity/',               live:true  },\u001b[39m\n\u001b[31m  { id:'hr',           label:'HR',           icon:'👥', url:'/hr/',                     live:true  },\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31mconst MGMT = ['harish', 'pramod'];·\u001b[39m\n\u001b[31m/* ── STATE ── */\u001b[39m\n\u001b[31mlet currentUser     = null;\u001b[39m\n\u001b[31mlet pinBuffer       = '';\u001b[39m\n\u001b[31mlet setPinTarget    = null;\u001b[39m\n\u001b[31mlet pendingShare    = false;\u001b[39m\n\u001b[31mlet _loginAttempts  = 0;\u001b[39m\n\u001b[31mlet _lockoutUntil   = 0;\u001b[39m\n\u001b[31mlet _loginInFlight  = false;·\u001b[39m\n\u001b[31m/* ── UTILS ── */\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => { s.classList.remove('active'); s.style.display = ''; });\u001b[39m\n\u001b[31m  document.getElementById('screen-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet toastTimer;\u001b[39m\n\u001b[31mfunction toast(msg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.classList.add('show');\u001b[39m\n\u001b[31m  clearTimeout(toastTimer);\u001b[39m\n\u001b[31m  toastTimer = setTimeout(() => el.classList.remove('show'), 2800);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fmtINR(n) {\u001b[39m\n\u001b[31m  if (n >= 100000) return '₹' + (n / 100000).toFixed(1) + 'L';\u001b[39m\n\u001b[31m  if (n >= 1000)   return '₹' + (n / 1000).toFixed(1) + 'K';\u001b[39m\n\u001b[31m  return '₹' + Math.round(n);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SESSION ── */\u001b[39m\n\u001b[31mconst SESSION_KEY  = 'lm360-session';\u001b[39m\n\u001b[31mconst EXP_SES_KEY  = '360exp-session';\u001b[39m\n\u001b[31mconst SESSION_TTL  = 12 * 3600 * 1000;·\u001b[39m\n\u001b[31mfunction saveSession(user) {\u001b[39m\n\u001b[31m  const payload = { empId: user.id, name: user.name, role: user.role, loginAt: new Date().toISOString(), source: 'hub' };\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m  localStorage.setItem(EXP_SES_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearSession() {\u001b[39m\n\u001b[31m  localStorage.removeItem(SESSION_KEY);\u001b[39m\n\u001b[31m  localStorage.removeItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const raw = localStorage.getItem(SESSION_KEY) || localStorage.getItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m    if (!raw) return null;\u001b[39m\n\u001b[31m    const s = JSON.parse(raw);\u001b[39m\n\u001b[31m    if (!s.empId || !s.name || !s.role) return null;\u001b[39m\n\u001b[31m    if (Date.now() - new Date(s.loginAt).getTime() > SESSION_TTL) { clearSession(); return null; }\u001b[39m\n\u001b[31m    return s;\u001b[39m\n\u001b[31m  } catch (_) { return null; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGIN FLOW ── */\u001b[39m\n\u001b[31masync function initLogin() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('login-name');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— Choose employee —</option>'; // clear before repopulating\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    emps.forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    [\u001b[39m\n\u001b[31m      { id:'amit',        name:'Amit'             },\u001b[39m\n\u001b[31m      { id:'ashok',       name:'Ashok'            },\u001b[39m\n\u001b[31m      { id:'gurpreet',    name:'Gurpreet'         },\u001b[39m\n\u001b[31m      { id:'harish',      name:'Harish Kumar Lal' },\u001b[39m\n\u001b[31m      { id:'mukesh',      name:'Mukesh'           },\u001b[39m\n\u001b[31m      { id:'narender',    name:'Narender'         },\u001b[39m\n\u001b[31m      { id:'pramod',      name:'Pramod Narang'    },\u001b[39m\n\u001b[31m      { id:'rakesh',      name:'Rakesh'           },\u001b[39m\n\u001b[31m      { id:'sachin',      name:'Sachin'           },\u001b[39m\n\u001b[31m      { id:'shambhu',     name:'Shambhu'          },\u001b[39m\n\u001b[31m      { id:'umashankar',  name:'Umashankar'       },\u001b[39m\n\u001b[31m    ].forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  document.querySelectorAll('.pin-key').forEach(btn =>\u001b[39m\n\u001b[31m    btn.addEventListener('click', () => handlePinKey(btn.dataset.k))\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handlePinKey(k) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (_loginInFlight) return;\u001b[39m\n\u001b[31m  // Lockout guard\u001b[39m\n\u001b[31m  if (_lockoutUntil && Date.now() < _lockoutUntil) {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    errEl.textContent = `Too many attempts — wait ${secs}s`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (_lockoutUntil) { _lockoutUntil = 0; _loginAttempts = 0; }\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (k === 'C') { pinBuffer = ''; }\u001b[39m\n\u001b[31m  else if (k === 'B') { pinBuffer = pinBuffer.slice(0, -1); }\u001b[39m\n\u001b[31m  else {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (!empId) { toast('Select your name first'); return; }\u001b[39m\n\u001b[31m    if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  if (pinBuffer.length === 4) {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (empId) attemptLogin(empId, pinBuffer);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updatePinDisplay() {\u001b[39m\n\u001b[31m  for (let i = 0; i < 4; i++)\u001b[39m\n\u001b[31m    document.getElementById('pd' + i).classList.toggle('filled', i < pinBuffer.length);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _shakePin() {\u001b[39m\n\u001b[31m  const pd = document.getElementById('pin-display');\u001b[39m\n\u001b[31m  pd.classList.add('shake');\u001b[39m\n\u001b[31m  setTimeout(() => pd.classList.remove('shake'), 400);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _startLockout() {\u001b[39m\n\u001b[31m  _lockoutUntil = Date.now() + 30000;\u001b[39m\n\u001b[31m  _loginAttempts = 0;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  const tick = () => {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    if (secs > 0) { errEl.textContent = `Too many attempts — wait ${secs}s`; setTimeout(tick, 500); }\u001b[39m\n\u001b[31m    else { _lockoutUntil = 0; errEl.textContent = ''; }\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  tick();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function attemptLogin(empId, pin) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  _loginInFlight = true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pin }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      _loginAttempts++;\u001b[39m\n\u001b[31m      _shakePin();\u001b[39m\n\u001b[31m      if (_loginAttempts >= 5) { _startLockout(); return; }\u001b[39m\n\u001b[31m      errEl.textContent = `Wrong PIN — ${5 - _loginAttempts} attempt${5 - _loginAttempts !== 1 ? 's' : ''} left`;\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    currentUser = { id: user.id, name: user.name, role: user.role };\u001b[39m\n\u001b[31m    saveSession(currentUser);\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — check connection';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    _loginInFlight = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── HOME SCREEN ── */\u001b[39m\n\u001b[31masync function buildHome() {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const body   = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  // Management users always use hardcoded ACCESS; others prefer server-side hub-access.json\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const tiles  = accessIds.map(id => TILES.find(t => t.id === id)).filter(Boolean);·\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"avatar\\\">${initials}</div>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1 style=\\\"font-size:16px\\\">${currentUser.name}</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">${currentUser.role}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${isMgmt ? '<button class=\\\"btn-icon\\\" id=\\\"btn-hub-ai\\\" title=\\\"AI Config\\\" style=\\\"font-size:18px\\\">🤖</button>' : ''}\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m  if (isMgmt) document.getElementById('btn-hub-ai').addEventListener('click', openHubAiConfig);·\u001b[39m\n\u001b[31m  if (isMgmt) {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"hub-main-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-dash\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">📊</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">Dashboard</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-tools\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">🧰</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">All Tools</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-header\\\">\u001b[39m\n\u001b[31m        <div class=\\\"section-title\\\" style=\\\"margin:0\\\">Quick Actions</div>\u001b[39m\n\u001b[31m        <button class=\\\"btn-icon\\\" id=\\\"btn-hub-qa-cfg\\\" title=\\\"Configure Quick Actions\\\" style=\\\"font-size:18px;padding:8px;min-width:44px;min-height:44px\\\">⚙️</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-grid\\\" id=\\\"hub-qa-grid\\\"></div>`;\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-dash').addEventListener('click',  () => enterHubDash(tiles));\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-tools').addEventListener('click', () => enterHubTools(tiles));\u001b[39m\n\u001b[31m    document.getElementById('btn-hub-qa-cfg').addEventListener('click', openHubQaConfig);\u001b[39m\n\u001b[31m    renderHubQA();\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"tile-grid field\\\" id=\\\"tile-grid\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"text-align:center;font-size:12px;color:var(--muted);padding-top:8px\\\">Tap a tile to open your tool</div>`;\u001b[39m\n\u001b[31m    renderTiles(tiles, 'field');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderTiles(tiles, mode) {\u001b[39m\n\u001b[31m  const grid = document.getElementById('tile-grid');\u001b[39m\n\u001b[31m  tiles.forEach(tile => {\u001b[39m\n\u001b[31m    const div = document.createElement('div');\u001b[39m\n\u001b[31m    div.className = 'tile ' + mode + (tile.live ? '' : ' coming');\u001b[39m\n\u001b[31m    div.innerHTML = `<div class=\\\"tile-icon\\\">${tile.icon}</div><div class=\\\"tile-label\\\">${tile.label}</div>` +\u001b[39m\n\u001b[31m      (tile.live ? '' : '<div class=\\\"soon-badge\\\">Soon</div>');\u001b[39m\n\u001b[31m    div.addEventListener('click', () => handleTile(tile));\u001b[39m\n\u001b[31m    grid.appendChild(div);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handleTile(tile) {\u001b[39m\n\u001b[31m  if (!tile.live) { toast('Coming soon'); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'team-pins') { enterTeamPins(); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'jobs')      { enterJobs();     return; }\u001b[39m\n\u001b[31m  window.location.href = tile.url;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT SUB-VIEWS ── */\u001b[39m\n\u001b[31mfunction _mgmtHeader(title) {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-subview-back\\\" style=\\\"font-size:20px;font-weight:900\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 style=\\\"font-size:16px\\\">${title}</h1></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-subview-back').addEventListener('click', () => buildHome());\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubDash(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('Dashboard');\u001b[39m\n\u001b[31m  const hdr = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const refreshBtn = document.createElement('button');\u001b[39m\n\u001b[31m  refreshBtn.className = 'btn-icon';\u001b[39m\n\u001b[31m  refreshBtn.title = 'Refresh dashboard';\u001b[39m\n\u001b[31m  refreshBtn.setAttribute('aria-label', 'Refresh dashboard');\u001b[39m\n\u001b[31m  refreshBtn.style.cssText = 'font-size:18px';\u001b[39m\n\u001b[31m  refreshBtn.textContent = '🔄';\u001b[39m\n\u001b[31m  refreshBtn.addEventListener('click', loadKPIs);\u001b[39m\n\u001b[31m  hdr.insertBefore(refreshBtn, hdr.querySelector('#btn-logout'));\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Actions Required</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-actions\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Operations</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row kpi-4col\\\" id=\\\"kpi-ops\\\">${skel}${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Production</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-prod\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Installation</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-inst\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">HR</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-hr\\\">${skel}${skel}${skel}</div>`;\u001b[39m\n\u001b[31m  loadKPIs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubTools(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('All Tools');\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  body.innerHTML = `<div class=\\\"tile-grid mgmt\\\" id=\\\"tile-grid\\\"></div>`;\u001b[39m\n\u001b[31m  renderTiles(tiles, 'mgmt');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT AI CONFIG ── */\u001b[39m\n\u001b[31mconst HUB_FIN_HDR = { 'Content-Type': 'application/json', 'Accept-Profile': 'finance' };·\u001b[39m\n\u001b[31masync function openHubAiConfig() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r    = await fetch(`${CFG.api}/upi_config`, { headers: HUB_FIN_HDR });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const get  = key => rows.find(x => x.key === key)?.value;\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-tesseract').value = get('tesseract_enabled') || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-enabled').value   = get('ai_enabled')        || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-model').value     = get('ai_model')           || 'haiku';\u001b[39m\n\u001b[31m  } catch(_) { toast('Could not load AI config'); return; }\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-ai').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveHubAiConfig() {\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-hub-ai-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  const pairs = [\u001b[39m\n\u001b[31m    { key:'tesseract_enabled', value: document.getElementById('hub-ai-tesseract').value },\u001b[39m\n\u001b[31m    { key:'ai_enabled',        value: document.getElementById('hub-ai-enabled').value },\u001b[39m\n\u001b[31m    { key:'ai_model',          value: document.getElementById('hub-ai-model').value },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const results = await Promise.all(pairs.map(p =>\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/upi_config?key=eq.${p.key}`, {\u001b[39m\n\u001b[31m        method: 'PATCH',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'finance', 'Prefer': 'return=minimal' },\u001b[39m\n\u001b[31m        body: JSON.stringify({ value: p.value }),\u001b[39m\n\u001b[31m      })\u001b[39m\n\u001b[31m    ));\u001b[39m\n\u001b[31m    if (results.some(r => !r.ok)) throw new Error('partial failure');\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show');\u001b[39m\n\u001b[31m    toast('✅ AI config saved');\u001b[39m\n\u001b[31m  } catch(_) { toast('Save failed'); }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Save'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function checkApiKeyStatus() {\u001b[39m\n\u001b[31m  const btn  = document.getElementById('btn-check-keys');\u001b[39m\n\u001b[31m  const wrap = document.getElementById('api-key-status-wrap');\u001b[39m\n\u001b[31m  btn.textContent = 'Checking…';\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  wrap.innerHTML = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/tour-ai/key-status');\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Proxy error ' + r.status);\u001b[39m\n\u001b[31m    const d = await r.json();\u001b[39m\n\u001b[31m    function mkBadge(info, label, link) {\u001b[39m\n\u001b[31m      const col  = info.ok ? 'var(--success)' : 'var(--danger)';\u001b[39m\n\u001b[31m      const icon = info.ok ? '✅' : '❌';\u001b[39m\n\u001b[31m      const note = info.ok ? (info.model || 'Valid') : (info.error || 'Error');\u001b[39m\n\u001b[31m      const hint = info.hint ? ' · key ' + info.hint : '';\u001b[39m\n\u001b[31m      return `<div style=\\\"background:var(--card);border:1px solid var(--border);border-left:3px solid ${col};border-radius:8px;padding:10px 12px;display:flex;align-items:center;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:13px;font-weight:600;color:var(--text)\\\">${icon} ${label}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${note}${hint}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <a href=\\\"${link}\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:11px;color:var(--info);text-decoration:none;white-space:nowrap\\\">Billing ↗</a>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    wrap.innerHTML =\u001b[39m\n\u001b[31m      mkBadge(d.anthropic, 'Anthropic API',    'https://console.anthropic.com/settings/billing') +\u001b[39m\n\u001b[31m      mkBadge(d.maps,      'Google Maps API',  'https://console.cloud.google.com/billing');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    wrap.innerHTML = `<div style=\\\"font-size:12px;color:var(--danger)\\\">Error: ${e.message}</div>`;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.textContent = 'Check Again';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT QUICK ACTIONS ── */\u001b[39m\n\u001b[31mconst HUB_QA_DEFS = [\u001b[39m\n\u001b[31m  { id:'offer',       icon:'📋', label:'New Offer',    accessId:'sales',        fn:() => { window.location.href='/sales/'; } },\u001b[39m\n\u001b[31m  { id:'paytm',       icon:'📱', label:'UPI Pay',       accessId:'finance',      fn:() => { window.location.href='/finance/?qa=upi'; } },\u001b[39m\n\u001b[31m  { id:'expense',     icon:'🧾', label:'Expenses',      accessId:'expense',      fn:() => { window.location.href='/expense/'; } },\u001b[39m\n\u001b[31m  { id:'finance',     icon:'💰', label:'Finance',       accessId:'finance',      fn:() => { window.location.href='/finance/'; } },\u001b[39m\n\u001b[31m  { id:'dispatch',    icon:'🚚', label:'Dispatch',      accessId:'dispatch',     fn:() => { window.location.href='/dispatch/'; } },\u001b[39m\n\u001b[31m  { id:'stores',      icon:'📦', label:'Stores',        accessId:'stores',       fn:() => { window.location.href='/stores/'; } },\u001b[39m\n\u001b[31m  { id:'recce',       icon:'📍', label:'Recce',         accessId:'recce',        fn:() => { window.location.href='/recce/'; } },\u001b[39m\n\u001b[31m  { id:'production',  icon:'⚙️', label:'Production',   accessId:'production',   fn:() => { window.location.href='/production/'; } },\u001b[39m\n\u001b[31m  { id:'tours',       icon:'🗺️', label:'Tour Plan',   accessId:'installation', fn:() => { window.location.href='/tour-planner/'; } },\u001b[39m\n\u001b[31m  { id:'installation',icon:'🔧', label:'Installation',  accessId:'installation', fn:() => { window.location.href='/installation/current/'; } },\u001b[39m\n\u001b[31m  { id:'activity',    icon:'🎯', label:'Activity',      accessId:'activity',     fn:() => { window.location.href='/activity/'; } },\u001b[39m\n\u001b[31m];\u001b[39m\n\u001b[31mconst HUB_QA_DEFAULT = ['offer', 'paytm'];\u001b[39m\n\u001b[31mconst HUB_QA_STORE   = '360lm-hub-qa';\u001b[39m\n\u001b[31mlet   _hubQaTempSel  = [];·\u001b[39m\n\u001b[31mfunction getHubQA() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(HUB_QA_STORE));\u001b[39m\n\u001b[31m    if (Array.isArray(s) && s.length) return s;\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m  return [...HUB_QA_DEFAULT];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHubQA() {\u001b[39m\n\u001b[31m  const ids  = getHubQA();\u001b[39m\n\u001b[31m  const grid = document.getElementById('hub-qa-grid');\u001b[39m\n\u001b[31m  if (!grid) return;\u001b[39m\n\u001b[31m  if (!ids.length) {\u001b[39m\n\u001b[31m    grid.innerHTML = `<div style=\\\"color:var(--muted);font-size:13px;grid-column:1/-1;\u001b[39m\n\u001b[31m      text-align:center;padding:16px\\\">Tap ⚙️ to add quick actions</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  grid.innerHTML = ids.map(id => {\u001b[39m\n\u001b[31m    const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!d) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-tile\\\" onclick=\\\"hubQaRun('${id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction hubQaRun(id) {\u001b[39m\n\u001b[31m  const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m  if (d) d.fn();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openHubQaConfig() {\u001b[39m\n\u001b[31m  _hubQaTempSel = [...getHubQA()];\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const visibleDefs = HUB_QA_DEFS.filter(d => accessIds.includes(d.accessId));\u001b[39m\n\u001b[31m  const list = document.getElementById('hub-qa-config-list');\u001b[39m\n\u001b[31m  list.innerHTML = visibleDefs.map(d => {\u001b[39m\n\u001b[31m    const sel = _hubQaTempSel.includes(d.id);\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-item${sel?' selected':''}\\\" id=\\\"hub-qa-item-${d.id}\\\"\u001b[39m\n\u001b[31m        onclick=\\\"toggleHubQaItem('${d.id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-toggle\\\"></div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleHubQaItem(id) {\u001b[39m\n\u001b[31m  const idx = _hubQaTempSel.indexOf(id);\u001b[39m\n\u001b[31m  if (idx >= 0) {\u001b[39m\n\u001b[31m    _hubQaTempSel.splice(idx, 1);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    if (_hubQaTempSel.length >= 6) {\u001b[39m\n\u001b[31m      document.getElementById('hub-qa-cfg-err').textContent = 'Maximum 6 quick actions';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _hubQaTempSel.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  const el = document.getElementById('hub-qa-item-' + id);\u001b[39m\n\u001b[31m  if (el) el.className = 'hub-qa-item' + (_hubQaTempSel.includes(id) ? ' selected' : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction saveHubQaConfig() {\u001b[39m\n\u001b[31m  localStorage.setItem(HUB_QA_STORE, JSON.stringify(_hubQaTempSel));\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.remove('show');\u001b[39m\n\u001b[31m  renderHubQA();\u001b[39m\n\u001b[31m  toast('✅ Quick actions updated');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── KPI FETCH ── */\u001b[39m\n\u001b[31mfunction _hdr(schema) {\u001b[39m\n\u001b[31m  return { 'Content-Type': 'application/json', 'Accept-Profile': schema };\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _safe(p) { return p.catch(() => null); }·\u001b[39m\n\u001b[31masync function loadKPIs() {\u001b[39m\n\u001b[31m  const mon = firstOfMonth();\u001b[39m\n\u001b[31m  const curMonth = new Date().toISOString().slice(0, 7); // YYYY-MM\u001b[39m\n\u001b[31m  const todayIST = (() => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; })();·\u001b[39m\n\u001b[31m  // Reset sections to loading skeletons before fetching (makes refresh visible)\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  [['kpi-actions',3],['kpi-ops',4],['kpi-prod',3],['kpi-inst',3],['kpi-hr',3]].forEach(([id,n]) => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    if (el) el.innerHTML = skel.repeat(n);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  const [\u001b[39m\n\u001b[31m    sheetsRes, claimsRes, salaryRes,\u001b[39m\n\u001b[31m    cashRes,   dispRes,   recceRes,  prodRes,\u001b[39m\n\u001b[31m    instJobsRes, instCtrsRes, storeItemsRes,\u001b[39m\n\u001b[31m    hrAttRes, hrEmpRes, hrConcernRes, hrSlipsRes\u001b[39m\n\u001b[31m  ] = await Promise.all([\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/sheets?status=in.(submitted,escalated)&select=sheet_id`, { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/approved_pending_reimbursement?select=sheet_id`,         { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_register?status=in.(pending,partial)&month=eq.${curMonth}&select=record_id`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/accounts?account_id=eq.petty-cash&select=current_balance`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/dispatches?status=eq.active&select=dispatch_id`,         { headers: _hdr('dispatch') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/submissions?submitted_at=gte.${mon}&select=sub_id`,      { headers: _hdr('recce') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/orders?status=not.in.(dispatched)&select=order_id,status`, { headers: _hdr('production') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/jobs?select=job_id,status`,                              { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/counters?select=counter_id,status`,                      { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/items?active=eq.true&select=id,current_stock,min_stock`, { headers: _hdr('stores') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance?date=eq.${todayIST}&status=eq.present&select=attendance_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/employee_list?select=id`,                               { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance_concerns?status=eq.open&select=concern_id`,  { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_slips?month=eq.${curMonth}&status=eq.finalized&select=slip_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m  ]);·\u001b[39m\n\u001b[31m  const sheets    = sheetsRes?.ok    ? await sheetsRes.json()    : [];\u001b[39m\n\u001b[31m  const claims    = claimsRes?.ok    ? await claimsRes.json()    : [];\u001b[39m\n\u001b[31m  const salary    = salaryRes?.ok    ? await salaryRes.json()    : [];\u001b[39m\n\u001b[31m  const cashArr   = cashRes?.ok      ? await cashRes.json()      : [];\u001b[39m\n\u001b[31m  const disps     = dispRes?.ok      ? await dispRes.json()      : [];\u001b[39m\n\u001b[31m  const recces    = recceRes?.ok     ? await recceRes.json()     : [];\u001b[39m\n\u001b[31m  const prods     = prodRes?.ok      ? await prodRes.json()      : [];\u001b[39m\n\u001b[31m  const instJobs  = instJobsRes?.ok  ? await instJobsRes.json()  : [];\u001b[39m\n\u001b[31m  const instCtrs  = instCtrsRes?.ok  ? await instCtrsRes.json()  : [];\u001b[39m\n\u001b[31m  const storeItems = storeItemsRes?.ok ? await storeItemsRes.json() : [];\u001b[39m\n\u001b[31m  const hrAtt     = hrAttRes?.ok     ? await hrAttRes.json()     : [];\u001b[39m\n\u001b[31m  const hrEmp     = hrEmpRes?.ok     ? await hrEmpRes.json()     : [];\u001b[39m\n\u001b[31m  const hrConcern = hrConcernRes?.ok ? await hrConcernRes.json() : [];\u001b[39m\n\u001b[31m  const hrSlips   = hrSlipsRes?.ok   ? await hrSlipsRes.json()   : [];·\u001b[39m\n\u001b[31m  const cash = Number(cashArr[0]?.current_balance || 0);·\u001b[39m\n\u001b[31m  const actionsEl = document.getElementById('kpi-actions');\u001b[39m\n\u001b[31m  const opsEl     = document.getElementById('kpi-ops');\u001b[39m\n\u001b[31m  const prodEl    = document.getElementById('kpi-prod');\u001b[39m\n\u001b[31m  const instEl    = document.getElementById('kpi-inst');\u001b[39m\n\u001b[31m  const hrEl      = document.getElementById('kpi-hr');\u001b[39m\n\u001b[31m  if (!actionsEl || !opsEl || !prodEl || !instEl) return;·\u001b[39m\n\u001b[31m  // ── Actions Required ────────────────────────────────────────────\u001b[39m\n\u001b[31m  actionsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${sheets.length ? 'alert' : 'ok'}\\\" data-href=\\\"/expense/?tab=approvals\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${sheets.length ? 'red' : 'green'}\\\">${sheets.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Expense Approvals</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${claims.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=claims\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${claims.length ? 'amber' : 'green'}\\\">${claims.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Claims to Reimburse</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${salary.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=salary\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${salary.length ? 'amber' : 'green'}\\\">${salary.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Salary Due This Month</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Operations ──────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const cashColor = cash < 2000 ? 'red' : cash < 5000 ? 'amber' : 'green';\u001b[39m\n\u001b[31m  const cashCard  = cash < 2000 ? 'alert' : cash < 5000 ? 'warn' : 'ok';\u001b[39m\n\u001b[31m  const lowStock = storeItems.filter(i => i.current_stock < i.min_stock).length;\u001b[39m\n\u001b[31m  opsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${cashCard}\\\" data-href=\\\"/finance/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${cashColor}\\\">${fmtINR(cash)}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">${cash < 0 ? '⚠️ ' : ''}Petty Cash</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/dispatch/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${disps.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Dispatches</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/recce/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${recces.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Recce This Month</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${lowStock > 0 ? 'alert' : 'ok'}\\\" data-href=\\\"/stores/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${lowStock > 0 ? 'red' : 'green'}\\\">${lowStock}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Low Stock Items</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Production ───────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const prodPending = prods.filter(o => o.status === 'pending').length;\u001b[39m\n\u001b[31m  const prodActive  = prods.filter(o => o.status === 'in_production' || o.status === 'quality_check').length;\u001b[39m\n\u001b[31m  const prodReady   = prods.filter(o => o.status === 'ready').length;\u001b[39m\n\u001b[31m  prodEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodPending ? 'warn' : 'ok'}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodPending ? 'amber' : 'green'}\\\">${prodPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Pending Orders</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${prodActive}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">In Production</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodReady ? 'ok' : ''}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodReady ? 'green' : ''}\\\">${prodReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Ready to Dispatch</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Installation ─────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const activeJobs  = instJobs.filter(j => j.status === 'active').length;\u001b[39m\n\u001b[31m  const ctrsPending = instCtrs.filter(c => c.status === 'recce_pending' || c.status === 'mockup_pending' || c.status === 'unclear').length;\u001b[39m\n\u001b[31m  const ctrsReady   = instCtrs.filter(c => c.status === 'ready').length;\u001b[39m\n\u001b[31m  instEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${activeJobs ? 'info' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${activeJobs ? 'blue' : 'green'}\\\">${activeJobs}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Jobs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsPending ? 'warn' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsPending ? 'amber' : 'green'}\\\">${ctrsPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Pending</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsReady ? 'ok' : ''}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsReady ? 'green' : ''}\\\">${ctrsReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Ready</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── HR ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31m  if (hrEl) {\u001b[39m\n\u001b[31m    const totalEmp   = hrEmp.length || 0;\u001b[39m\n\u001b[31m    const presentCnt = hrAtt.length;\u001b[39m\n\u001b[31m    const attPct     = totalEmp > 0 ? Math.round(presentCnt / totalEmp * 100) : 0;\u001b[39m\n\u001b[31m    const attColor   = attPct >= 80 ? 'green' : attPct >= 50 ? 'amber' : 'red';\u001b[39m\n\u001b[31m    const attCard    = attPct >= 80 ? 'ok' : attPct >= 50 ? 'warn' : 'alert';\u001b[39m\n\u001b[31m    const concernCnt = hrConcern.length;\u001b[39m\n\u001b[31m    const slipsDue   = hrSlips.length;\u001b[39m\n\u001b[31m    hrEl.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${attCard}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${attColor}\\\">${attPct}%</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Attendance Today</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${concernCnt ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${concernCnt ? 'amber' : 'green'}\\\">${concernCnt}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Open Concerns</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${slipsDue ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${slipsDue ? 'amber' : 'green'}\\\">${slipsDue}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Slips to Pay</div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Wire tap-to-navigate on all KPI cards\u001b[39m\n\u001b[31m  document.querySelectorAll('.kpi-card[data-href]').forEach(card => {\u001b[39m\n\u001b[31m    card.addEventListener('click', () => window.location.href = card.dataset.href);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction firstOfMonth() {\u001b[39m\n\u001b[31m  const d = new Date();\u001b[39m\n\u001b[31m  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGOUT ── */\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  pinBuffer   = '';\u001b[39m\n\u001b[31m  pendingShare = false;\u001b[39m\n\u001b[31m  _loginAttempts = 0; _lockoutUntil = 0; _loginInFlight = false;\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  document.getElementById('login-name').value = '';\u001b[39m\n\u001b[31m  document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m  showScreen('login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── TEAM PINs ── */\u001b[39m\n\u001b[31masync function enterTeamPins() {\u001b[39m\n\u001b[31m  showScreen('team');\u001b[39m\n\u001b[31m  await loadTeamPins();\u001b[39m\n\u001b[31m  // Show icon admin section for harish/pramod\u001b[39m\n\u001b[31m  const sec = document.getElementById('icon-admin-section');\u001b[39m\n\u001b[31m  if (sec) {\u001b[39m\n\u001b[31m    sec.style.display = MGMT.includes(currentUser.id) ? 'block' : 'none';\u001b[39m\n\u001b[31m    if (MGMT.includes(currentUser.id)) refreshSecretDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── ICON ADMIN ────────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst ICON_SECRET_KEY    = 'lm360_icon_secret';\u001b[39m\n\u001b[31mconst ICON_SECRET_INITIAL = 'nlr8Zsqw6XsD0rEXPQ_RfpYiOeNhH-9VglYKoIsElHE';·\u001b[39m\n\u001b[31mfunction getIconSecret() {\u001b[39m\n\u001b[31m  return localStorage.getItem(ICON_SECRET_KEY) || ICON_SECRET_INITIAL;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction setIconSecret(s) {\u001b[39m\n\u001b[31m  localStorage.setItem(ICON_SECRET_KEY, s);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet secretVisible = false;\u001b[39m\n\u001b[31mfunction refreshSecretDisplay() {\u001b[39m\n\u001b[31m  const el = document.getElementById('icon-secret-display');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  const s = getIconSecret();\u001b[39m\n\u001b[31m  el.textContent = secretVisible ? s : s.slice(0,4) + '••••••••••••••••' + s.slice(-4);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleSecretVisible() {\u001b[39m\n\u001b[31m  secretVisible = !secretVisible;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-secret-show');\u001b[39m\n\u001b[31m  if (btn) btn.textContent = secretVisible ? 'Hide' : 'Show';\u001b[39m\n\u001b[31m  refreshSecretDisplay();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function changeIconSecret() {\u001b[39m\n\u001b[31m  const newSecret = document.getElementById('icon-new-secret').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-secret-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (newSecret.length < 16) { errEl.textContent = 'Minimum 16 characters'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/change-secret', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret(), 'X-New-Secret': newSecret }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Failed');\u001b[39m\n\u001b[31m    setIconSecret(newSecret);\u001b[39m\n\u001b[31m    document.getElementById('icon-new-secret').value = '';\u001b[39m\n\u001b[31m    refreshSecretDisplay();\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = '✓ Secret updated';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mdocument.addEventListener('DOMContentLoaded', () => {\u001b[39m\n\u001b[31m  const fileInput = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  if (fileInput) {\u001b[39m\n\u001b[31m    fileInput.addEventListener('change', function() {\u001b[39m\n\u001b[31m      const wrap = document.getElementById('icon-preview-wrap');\u001b[39m\n\u001b[31m      const img  = document.getElementById('icon-preview-img');\u001b[39m\n\u001b[31m      if (this.files[0]) { img.src = URL.createObjectURL(this.files[0]); wrap.style.display = 'block'; }\u001b[39m\n\u001b[31m      else { wrap.style.display = 'none'; }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function uploadIcon() {\u001b[39m\n\u001b[31m  const pwa   = document.getElementById('icon-pwa-select').value;\u001b[39m\n\u001b[31m  const size  = document.getElementById('icon-size-select').value;\u001b[39m\n\u001b[31m  const input = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-upload-err');\u001b[39m\n\u001b[31m  errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (!input.files[0]) { errEl.textContent = 'Choose an image file first'; return; }\u001b[39m\n\u001b[31m  const formData = new FormData();\u001b[39m\n\u001b[31m  formData.append('pwa', pwa);\u001b[39m\n\u001b[31m  formData.append('size', size);\u001b[39m\n\u001b[31m  formData.append('icon', input.files[0]);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret() },\u001b[39m\n\u001b[31m      body: formData\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Upload failed');\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = `✓ Icon updated for ${pwa} (${size}px). Clear browser cache / reinstall to see new icon.`;\u001b[39m\n\u001b[31m    input.value = '';\u001b[39m\n\u001b[31m    document.getElementById('icon-preview-wrap').style.display = 'none';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadTeamPins() {\u001b[39m\n\u001b[31m  const list = document.getElementById('team-list');\u001b[39m\n\u001b[31m  list.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    list.innerHTML = '';\u001b[39m\n\u001b[31m    emps.forEach(emp => {\u001b[39m\n\u001b[31m      const row = document.createElement('div');\u001b[39m\n\u001b[31m      row.className = 'emp-row';\u001b[39m\n\u001b[31m      row.innerHTML = `\u001b[39m\n\u001b[31m        <div style=\\\"min-width:0;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:15px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.name}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.role}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-secondary btn-sm\\\" data-id=\\\"${emp.id}\\\" data-name=\\\"${emp.name}\\\">Set PIN</button>`;\u001b[39m\n\u001b[31m      row.querySelector('button').addEventListener('click', () => openSetPin(emp.id, emp.name));\u001b[39m\n\u001b[31m      list.appendChild(row);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    list.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openSetPin(targetId, targetName) {\u001b[39m\n\u001b[31m  setPinTarget = targetId;\u001b[39m\n\u001b[31m  document.getElementById('setpin-target-name').textContent = targetName;\u001b[39m\n\u001b[31m  ['setpin-new','setpin-confirm','setpin-auth'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-setpin').classList.add('show');\u001b[39m\n\u001b[31m  setTimeout(() => document.getElementById('setpin-new').focus(), 50);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function doSetPin() {\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('setpin-new').value.trim();\u001b[39m\n\u001b[31m  const conf    = document.getElementById('setpin-confirm').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('setpin-auth').value.trim();\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('setpin-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  if (!newPin || !conf || !authPin) { errEl.textContent = 'All fields are required'; return; }\u001b[39m\n\u001b[31m  if (newPin !== conf)              { errEl.textContent = 'New PINs do not match'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))     { errEl.textContent = 'PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-setpin-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_admin_id:  currentUser.id,\u001b[39m\n\u001b[31m        p_admin_pin: authPin,\u001b[39m\n\u001b[31m        p_target_id: setPinTarget,\u001b[39m\n\u001b[31m        p_new_pin:   newPin,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const err = await resp.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Failed to set PIN';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m    toast('✅ PIN updated for ' + document.getElementById('setpin-target-name').textContent);\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Set PIN';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ═══════════════════════════════════════════\u001b[39m\n\u001b[31m   JOBS — Campaign + Tour management\u001b[39m\n\u001b[31m   Access: harish, pramod (campaigns + tours)\u001b[39m\n\u001b[31m           rakesh (tours only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ */\u001b[39m\n\u001b[31mconst INST_RD = { 'Accept-Profile': 'installation' };\u001b[39m\n\u001b[31mconst INST_WR = { 'Content-Type':'application/json','Accept-Profile':'installation','Content-Profile':'installation' };·\u001b[39m\n\u001b[31mlet _currentCampaignId = null;\u001b[39m\n\u001b[31mlet _allEmployees      = [];\u001b[39m\n\u001b[31mlet _editTourId        = null;  // null = create mode, string = edit mode·\u001b[39m\n\u001b[31m// ── Helpers ──────────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _slugify(s) {\u001b[39m\n\u001b[31m  return s.toUpperCase().trim()\u001b[39m\n\u001b[31m    .replace(/[^A-Z0-9]+/g, '_')\u001b[39m\n\u001b[31m    .replace(/^_|_$/g, '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _buildCampaignId(year, brand, client, campaign, phase) {\u001b[39m\n\u001b[31m  const nn = String(phase || 1).padStart(2, '0');\u001b[39m\n\u001b[31m  return `${year}-${_slugify(brand)}-${_slugify(client)}-${_slugify(campaign)}-${nn}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _campStatusBadge(s) {\u001b[39m\n\u001b[31m  const map = { active:'ok', completed:'', on_hold:'warn' };\u001b[39m\n\u001b[31m  const col = { active:'green', completed:'', on_hold:'amber' };\u001b[39m\n\u001b[31m  return `<span class=\\\"kpi-val ${col[s]||''}\\\" style=\\\"font-size:12px;font-weight:700\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _tourStatusBadge(s) {\u001b[39m\n\u001b[31m  const colors = { active:'var(--success)', planned:'var(--info)', completed:'var(--muted)', cancelled:'var(--danger)' };\u001b[39m\n\u001b[31m  return `<span style=\\\"font-size:11px;font-weight:700;padding:2px 8px;border-radius:10px;\u001b[39m\n\u001b[31m    background:var(--card);color:${colors[s]||'var(--muted)'}\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _canCreateCampaign() { return ['harish','pramod'].includes(currentUser?.id); }\u001b[39m\n\u001b[31mfunction _canCreateTour()     { return ['harish','pramod','rakesh'].includes(currentUser?.id); }·\u001b[39m\n\u001b[31m// ── Jobs screen ───────────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterJobs() {\u001b[39m\n\u001b[31m  showScreen('jobs');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').style.display = _canCreateCampaign() ? '' : 'none';\u001b[39m\n\u001b[31m  await loadJobs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadJobs() {\u001b[39m\n\u001b[31m  const el = document.getElementById('jobs-body');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?order=created_at.desc`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?select=tour_id,campaign_id,status`, { headers: INST_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const campaigns = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours     = tResp.ok ? await tResp.json() : [];·\u001b[39m\n\u001b[31m    if (!campaigns.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty\\\"><div style=\\\"font-size:32px\\\">📋</div><div>No campaigns yet</div></div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    el.innerHTML = campaigns.map(c => {\u001b[39m\n\u001b[31m      const cTours  = tours.filter(t => t.campaign_id === c.campaign_id);\u001b[39m\n\u001b[31m      const active  = cTours.filter(t => t.status === 'active').length;\u001b[39m\n\u001b[31m      const total   = cTours.length;\u001b[39m\n\u001b[31m      const statusColors = { active:'var(--success)', on_hold:'var(--accent2)', completed:'var(--muted)' };\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card tappable\\\" style=\\\"padding:14px\\\" onclick=\\\"enterCampaign('${c.campaign_id}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:flex-start;gap:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:15px;font-weight:700\\\">${c.brand} — ${c.campaign_name}\u001b[39m\n\u001b[31m                ${c.phase > 1 ? `<span style=\\\"font-size:11px;color:var(--muted)\\\"> Phase ${c.phase}</span>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:2px\\\">${c.client} · ${c.year}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:4px\\\">\u001b[39m\n\u001b[31m                ${total} tour${total!==1?'s':''} · ${active} active\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;align-items:flex-end;gap:6px\\\">\u001b[39m\n\u001b[31m              <span style=\\\"font-size:11px;font-weight:700;color:${statusColors[c.status]||'var(--muted)'}\\\">\u001b[39m\n\u001b[31m                ${(c.status||'').replace('_',' ')}\u001b[39m\n\u001b[31m              </span>\u001b[39m\n\u001b[31m              <span style=\\\"font-size:18px;color:var(--muted)\\\">›</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-top:8px;word-break:break-all\\\">${c.campaign_id}</div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign detail ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterCampaign(campaignId) {\u001b[39m\n\u001b[31m  _currentCampaignId = campaignId;\u001b[39m\n\u001b[31m  showScreen('campaign');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').style.display = _canCreateTour() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-sub').textContent   = '';\u001b[39m\n\u001b[31m  await loadCampaign(campaignId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCampaign(campaignId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('campaign-body');\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp, empResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(campaignId)}&order=start_date`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const camps = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours = tResp.ok ? await tResp.json() : [];\u001b[39m\n\u001b[31m    _allEmployees = empResp.ok ? await empResp.json() : [];·\u001b[39m\n\u001b[31m    if (!camps.length) { body.innerHTML = '<div class=\\\"empty\\\">Campaign not found</div>'; return; }\u001b[39m\n\u001b[31m    const c = camps[0];·\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-title').textContent = `${c.brand} — ${c.campaign_name}`;\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-sub').textContent   = `${c.client} · ${c.year}${c.phase>1?' · Phase '+c.phase:''}`;·\u001b[39m\n\u001b[31m    const empMap = {};\u001b[39m\n\u001b[31m    _allEmployees.forEach(e => empMap[e.id] = e.name);·\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"padding:12px 14px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Campaign ID</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:10px;color:var(--muted);word-break:break-all;text-align:right;max-width:65%\\\">${c.campaign_id}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Client</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:13px;font-weight:600\\\">${c.client}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${_canCreateCampaign() ? `\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div class=\\\"section-title\\\" style=\\\"margin-bottom:6px\\\">Campaign Status</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m            ${['active','on_hold','completed'].map(s =>\u001b[39m\n\u001b[31m              `<button class=\\\"status-chip${c.status===s?' sel':''}\\\" onclick=\\\"updateCampaignStatus('${c.campaign_id}','${s}')\\\">\u001b[39m\n\u001b[31m                ${s.replace('_',' ')}\u001b[39m\n\u001b[31m              </button>`).join('')}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>` : ''}\u001b[39m\n\u001b[31m      </div>·\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:4px\\\">Tours (${tours.length})</div>\u001b[39m\n\u001b[31m      ${tours.length === 0 ? `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\">\u001b[39m\n\u001b[31m        <div>No tours yet</div>\u001b[39m\n\u001b[31m        ${_canCreateTour() ? '<div style=\\\"font-size:12px\\\">Tap ＋ to add the first tour</div>' : ''}\u001b[39m\n\u001b[31m      </div>` :\u001b[39m\n\u001b[31m      tours.map(t => {\u001b[39m\n\u001b[31m        const empNames = (t.assigned_employees || []).map(id => empMap[id] || id).join(', ') || 'No one assigned';\u001b[39m\n\u001b[31m        const dates    = t.start_date ? `${t.start_date} → ${t.end_date||'?'}` : 'Dates not set';\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m          <div class=\\\"card\\\" style=\\\"padding:14px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;align-items:flex-start;gap:8px\\\">\u001b[39m\n\u001b[31m              <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m                <div style=\\\"font-size:14px;font-weight:700\\\">${t.title || t.tour_id}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">📅 ${dates}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">👥 ${empNames}</div>\u001b[39m\n\u001b[31m                ${t.notes ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">📝 ${t.notes}</div>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              ${_tourStatusBadge(t.status)}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            ${_canCreateTour() ? `\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;gap:6px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m              ${['planned','active','completed'].map(s =>\u001b[39m\n\u001b[31m                `<button class=\\\"status-chip${t.status===s?' sel':''}\\\"\u001b[39m\n\u001b[31m                  onclick=\\\"updateTourStatus('${t.tour_id}','${s}')\\\">${s}</button>`).join('')}\u001b[39m\n\u001b[31m              <button class=\\\"status-chip\\\" onclick=\\\"openEditTour('${t.tour_id}')\\\"\u001b[39m\n\u001b[31m                style=\\\"margin-left:auto;background:var(--surface);border-color:#3a3a6e;color:#8888ff\\\">\u001b[39m\n\u001b[31m                ✏️ Edit\u001b[39m\n\u001b[31m              </button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m            <div style=\\\"font-size:10px;color:var(--muted);margin-top:6px\\\">${t.tour_id}</div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m      <div style=\\\"height:20px\\\"></div>`;\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateTourStatus(tourId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Tour marked ${status}`);\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateCampaignStatus(campaignId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Campaign marked ${status.replace('_',' ')}`);\u001b[39m\n\u001b[31m    loadCampaign(campaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Campaign modal ────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewCampaign() {\u001b[39m\n\u001b[31m  if (!_canCreateCampaign()) { toast('Only Harish or Pramod can create campaigns'); return; }\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('nc-year').value    = new Date().getFullYear();\u001b[39m\n\u001b[31m  document.getElementById('nc-phase').value   = '1';\u001b[39m\n\u001b[31m  document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  document.getElementById('nc-err').textContent     = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-new-campaign').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateCampaignPreview() {\u001b[39m\n\u001b[31m  const year  = document.getElementById('nc-year').value;\u001b[39m\n\u001b[31m  const brand = document.getElementById('nc-brand').value;\u001b[39m\n\u001b[31m  const clt   = document.getElementById('nc-client').value;\u001b[39m\n\u001b[31m  const camp  = document.getElementById('nc-campaign').value;\u001b[39m\n\u001b[31m  const phase = document.getElementById('nc-phase').value;\u001b[39m\n\u001b[31m  if (brand && clt && camp) {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = _buildCampaignId(year, brand, clt, camp, phase);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewCampaign() {\u001b[39m\n\u001b[31m  const year   = Number(document.getElementById('nc-year').value);\u001b[39m\n\u001b[31m  const brand  = document.getElementById('nc-brand').value.trim();\u001b[39m\n\u001b[31m  const client = document.getElementById('nc-client').value.trim();\u001b[39m\n\u001b[31m  const cname  = document.getElementById('nc-campaign').value.trim();\u001b[39m\n\u001b[31m  const phase  = Number(document.getElementById('nc-phase').value) || 1;\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('nc-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const brandEl = document.getElementById('nc-brand');\u001b[39m\n\u001b[31m  const clientEl = document.getElementById('nc-client');\u001b[39m\n\u001b[31m  const cnameEl = document.getElementById('nc-campaign');\u001b[39m\n\u001b[31m  [brandEl, clientEl, cnameEl].forEach(el => el.style.borderColor = '');\u001b[39m\n\u001b[31m  let _missingField = false;\u001b[39m\n\u001b[31m  if (!brand)  { brandEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!client) { clientEl.style.borderColor = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!cname)  { cnameEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (_missingField) { errEl.textContent = 'Brand, Client and Campaign Name are required'; return; }\u001b[39m\n\u001b[31m  const campaignId = _buildCampaignId(year, brand, client, cname, phase);·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nc-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/campaigns`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ campaign_id:campaignId, year, brand, client, campaign_name:cname, phase, created_by:currentUser.id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || 'Failed to create';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show');\u001b[39m\n\u001b[31m    toast(`✅ Campaign created`);\u001b[39m\n\u001b[31m    loadJobs();\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Create'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Tour modal ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _buildEmpCheckboxes(assigned = []) {\u001b[39m\n\u001b[31m  return _allEmployees.map(e =>\u001b[39m\n\u001b[31m    `<label style=\\\"display:flex;align-items:center;gap:10px;padding:8px 10px;background:var(--card);\u001b[39m\n\u001b[31m      border:1px solid var(--border);border-radius:8px;cursor:pointer\\\">\u001b[39m\n\u001b[31m      <input type=\\\"checkbox\\\" value=\\\"${e.id}\\\" ${assigned.includes(e.id)?'checked':''} style=\\\"width:16px;height:16px;accent-color:var(--accent)\\\">\u001b[39m\n\u001b[31m      <span style=\\\"font-size:14px;font-weight:600\\\">${e.name}</span>\u001b[39m\n\u001b[31m      <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${e.role}</span>\u001b[39m\n\u001b[31m    </label>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openNewTour() {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = null;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '🗺️ New Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Create Tour';\u001b[39m\n\u001b[31m  document.getElementById('nt-title').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-start').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-end').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-notes').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(_currentCampaignId)}&select=tour_id`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const existing = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    const nn = String(existing.length + 1).padStart(2, '0');\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T${nn}`;\u001b[39m\n\u001b[31m  } catch(_) {\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T01`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (_allEmployees.length)\u001b[39m\n\u001b[31m    document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes();·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEditTour(tourId) {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = tourId;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '✏️ Edit Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Save Changes';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const tours = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    if (!tours.length) { toast('Tour not found'); return; }\u001b[39m\n\u001b[31m    const t = tours[0];\u001b[39m\n\u001b[31m    document.getElementById('nt-title').value = t.title || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-start').value = t.start_date || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-end').value   = t.end_date   || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-notes').value = t.notes      || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = tourId;\u001b[39m\n\u001b[31m    if (_allEmployees.length)\u001b[39m\n\u001b[31m      document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes(t.assigned_employees || []);\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to load tour'); return; }·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewTour() {\u001b[39m\n\u001b[31m  const title = document.getElementById('nt-title').value.trim();\u001b[39m\n\u001b[31m  const start = document.getElementById('nt-start').value || null;\u001b[39m\n\u001b[31m  const end   = document.getElementById('nt-end').value   || null;\u001b[39m\n\u001b[31m  const notes = document.getElementById('nt-notes').value.trim() || null;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('nt-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const checked = [...document.querySelectorAll('#nt-employees input:checked')].map(cb => cb.value);\u001b[39m\n\u001b[31m  if (!checked.length) { errEl.textContent = 'Assign at least one employee'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nt-save');\u001b[39m\n\u001b[31m  const wasEdit = !!_editTourId;\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = wasEdit ? 'Saving…' : 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editTourId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(_editTourId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      const tourId = document.getElementById('nt-id-preview').textContent;\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          tour_id: tourId, campaign_id: _currentCampaignId,\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes, status: 'planned',\u001b[39m\n\u001b[31m          created_by: currentUser.id,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || (_editTourId ? 'Failed to save' : 'Failed to create');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show');\u001b[39m\n\u001b[31m    toast(_editTourId ? '✅ Tour updated' : '✅ Tour created');\u001b[39m\n\u001b[31m    _editTourId = null;\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = wasEdit ? 'Save Changes' : 'Create Tour';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── EVENT WIRING ── */\u001b[39m\n\u001b[31mfunction wireEvents() {\u001b[39m\n\u001b[31m  // Jobs screens\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-back').addEventListener('click',     () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-refresh').addEventListener('click',  loadJobs);\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').addEventListener('click',  openNewCampaign);\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-back').addEventListener('click', () => showScreen('jobs'));\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-refresh').addEventListener('click', () => loadCampaign(_currentCampaignId));\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').addEventListener('click',      openNewTour);·\u001b[39m\n\u001b[31m  // New Campaign modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-save').addEventListener('click', saveNewCampaign);\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign','nc-year','nc-phase'].forEach(id =>\u001b[39m\n\u001b[31m    document.getElementById(id).addEventListener('input', updateCampaignPreview));·\u001b[39m\n\u001b[31m  // New Tour modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').addEventListener('click', saveNewTour);·\u001b[39m\n\u001b[31m  document.getElementById('btn-team-back').addEventListener('click', () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-team-refresh').addEventListener('click', loadTeamPins);\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-cancel').addEventListener('click', () => {\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-save').addEventListener('click', doSetPin);·\u001b[39m\n\u001b[31m  // Set PIN — progressive field reveal\u001b[39m\n\u001b[31m  document.getElementById('setpin-new').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const val = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const showConf = val.length === 4;\u001b[39m\n\u001b[31m    document.getElementById('setpin-confirm-group').style.display = showConf ? '' : 'none';\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m    if (!showConf) { document.getElementById('setpin-confirm').value = ''; document.getElementById('setpin-auth').value = ''; }\u001b[39m\n\u001b[31m    if (showConf) setTimeout(() => document.getElementById('setpin-confirm').focus(), 20);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const newVal = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const conf   = document.getElementById('setpin-confirm').value;\u001b[39m\n\u001b[31m    const showAuth = conf.length === 4 && conf === newVal;\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = showAuth ? '' : 'none';\u001b[39m\n\u001b[31m    if (!showAuth) document.getElementById('setpin-auth').value = '';\u001b[39m\n\u001b[31m    if (showAuth) setTimeout(() => document.getElementById('setpin-auth').focus(), 20);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // Hub AI config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-save').addEventListener('click',   saveHubAiConfig);\u001b[39m\n\u001b[31m  document.getElementById('btn-check-keys').addEventListener('click',    checkApiKeyStatus);·\u001b[39m\n\u001b[31m  // Hub QA config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-qa').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-save').addEventListener('click', saveHubQaConfig);·\u001b[39m\n\u001b[31m  // Share picker\u001b[39m\n\u001b[31m  document.getElementById('share-to-expense').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/expense/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-recce').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/recce/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-installation').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/installation/current/?share=1';\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-04: employee dropdown change resets PIN buffer and lockout\u001b[39m\n\u001b[31m  document.getElementById('login-name').addEventListener('change', () => {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-07: ESC key closes any open modal\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (e.key === 'Escape') {\u001b[39m\n\u001b[31m      document.querySelectorAll('.modal-overlay.show').forEach(m => m.classList.remove('show'));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-11: keyboard digit/backspace input on login screen\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (!document.getElementById('screen-login').classList.contains('active')) return;\u001b[39m\n\u001b[31m    if (e.key >= '0' && e.key <= '9') { handlePinKey(e.key); }\u001b[39m\n\u001b[31m    else if (e.key === 'Backspace') { e.preventDefault(); handlePinKey('B'); }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-02/B-08: re-check session TTL when user returns to the tab\u001b[39m\n\u001b[31m  document.addEventListener('visibilitychange', () => {\u001b[39m\n\u001b[31m    if (document.visibilityState === 'visible' && currentUser) {\u001b[39m\n\u001b[31m      if (!loadSession()) doLogout();\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── BOOT ── */\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  wireEvents();·\u001b[39m\n\u001b[31m  if (location.search.includes('share=1')) {\u001b[39m\n\u001b[31m    pendingShare = true;\u001b[39m\n\u001b[31m    history.replaceState({}, '', '/hub/');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Always populate the login dropdown (needed if user logs out later)\u001b[39m\n\u001b[31m  initLogin(); // fire-and-forget — does not block home screen path·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess) {\u001b[39m\n\u001b[31m    currentUser = { id: sess.empId, name: sess.name, role: sess.role };\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  setTimeout(() => showScreen('login'), 1500);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SERVICE WORKER: install + update ── */\u001b[39m\n\u001b[31mif ('serviceWorker' in navigator) {\u001b[39m\n\u001b[31m  navigator.serviceWorker.register('/hub/sw.js').then(reg => {·\u001b[39m\n\u001b[31m    // Install banner: show when browser signals app is installable\u001b[39m\n\u001b[31m    let deferredInstall = null;\u001b[39m\n\u001b[31m    window.addEventListener('beforeinstallprompt', e => {\u001b[39m\n\u001b[31m      e.preventDefault();\u001b[39m\n\u001b[31m      deferredInstall = e;\u001b[39m\n\u001b[31m      if (!window.matchMedia('(display-mode: standalone)').matches) {\u001b[39m\n\u001b[31m        const delay = document.getElementById('update-banner').classList.contains('show') ? 3500 : 0;\u001b[39m\n\u001b[31m        setTimeout(() => document.getElementById('install-banner').classList.add('show'), delay);\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    window.addEventListener('appinstalled', () => {\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install').addEventListener('click', async () => {\u001b[39m\n\u001b[31m      if (!deferredInstall) return;\u001b[39m\n\u001b[31m      deferredInstall.prompt();\u001b[39m\n\u001b[31m      await deferredInstall.userChoice;\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m    // Update banner: show when a new SW version is waiting to activate\u001b[39m\n\u001b[31m    function showUpdateBanner() {\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.add('show');\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (reg.waiting) showUpdateBanner();·\u001b[39m\n\u001b[31m    reg.addEventListener('updatefound', () => {\u001b[39m\n\u001b[31m      const newSW = reg.installing;\u001b[39m\n\u001b[31m      newSW.addEventListener('statechange', () => {\u001b[39m\n\u001b[31m        if (newSW.state === 'installed' && navigator.serviceWorker.controller) {\u001b[39m\n\u001b[31m          showUpdateBanner();\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    document.getElementById('btn-update').addEventListener('click', () => {\u001b[39m\n\u001b[31m      if (reg.waiting) {\u001b[39m\n\u001b[31m        reg.waiting.postMessage({ type: 'SKIP_WAITING' });\u001b[39m\n\u001b[31m        navigator.serviceWorker.addEventListener('controllerchange', () => {\u001b[39m\n\u001b[31m          window.location.reload();\u001b[39m\n\u001b[31m        }, { once: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-update-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m  }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mboot();\u001b[39m\n\u001b[31m</script>··\u001b[39m\n\u001b[31m</body></html>\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:331:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 331
                        },
                        "snippet": "  329 |     await page.goto(BASE_URL);\n  330 |     const src = await page.content();\n> 331 |     expect(src).toContain('recce-v10');\n      |                 ^\n  332 |     expect(src).not.toContain('recce-v9');\n  333 |   });\n  334 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 331
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"recce-v10\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html><html lang=\\\"en\\\"><head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#FF6B35\\\">\u001b[39m\n\u001b[31m<meta name=\\\"mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-status-bar-style\\\" content=\\\"black-translucent\\\">\u001b[39m\n\u001b[31m<title>360LM</title>\u001b[39m\n\u001b[31m<meta name=\\\"description\\\" content=\\\"360LM Operations Hub — employee tools\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/hub/manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"icon\\\" href=\\\"/hub/icons/icon-192.png\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--surface:#161616;--card:#1e1e1e;--border:#2a2a2a;\u001b[39m\n\u001b[31m  --accent:#FF6B35;--accent2:#FFB800;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --danger:#e74c3c;--success:#2ecc71;--info:#3498db;\u001b[39m\n\u001b[31m  --radius:12px;--font:'Segoe UI',system-ui,sans-serif;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:var(--font);min-height:100vh;overflow-x:hidden}\u001b[39m\n\u001b[31m.screen{display:none;min-height:100vh;flex-direction:column}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── HEADER ── */\u001b[39m\n\u001b[31m.header{background:var(--surface);border-bottom:1px solid var(--border);padding:14px 16px;\u001b[39m\n\u001b[31m  display:flex;align-items:center;gap:12px;position:sticky;top:0;z-index:10;flex-shrink:0}\u001b[39m\n\u001b[31m.header h1{font-size:18px;font-weight:700;color:var(--accent);flex:1}\u001b[39m\n\u001b[31m.header .sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.btn-icon{background:none;border:none;color:var(--muted);font-size:22px;cursor:pointer;\u001b[39m\n\u001b[31m  padding:4px;border-radius:8px;line-height:1;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m.btn-icon:active{background:var(--border)}·\u001b[39m\n\u001b[31m/* ── BODY ── */\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:16px}·\u001b[39m\n\u001b[31m/* ── SPLASH ── */\u001b[39m\n\u001b[31m#screen-splash{align-items:center;justify-content:center;gap:8px}\u001b[39m\n\u001b[31m.splash-logo{font-size:72px;font-weight:900;color:var(--accent);letter-spacing:-2px;line-height:1}\u001b[39m\n\u001b[31m.splash-sub{font-size:15px;color:var(--muted);letter-spacing:2px;text-transform:uppercase}\u001b[39m\n\u001b[31m.splash-dot{width:8px;height:8px;border-radius:50%;background:var(--accent);\u001b[39m\n\u001b[31m  animation:pulse 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes pulse{0%,100%{opacity:.3;transform:scale(.8)}50%{opacity:1;transform:scale(1)}}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  min-height:100dvh;padding:24px;gap:20px;overflow-y:auto}\u001b[39m\n\u001b[31m.login-logo{font-size:44px;font-weight:900;color:var(--accent);letter-spacing:-1px}\u001b[39m\n\u001b[31m.login-sub{font-size:13px;color:var(--muted);text-transform:uppercase;letter-spacing:2px}\u001b[39m\n\u001b[31m.form-group{width:100%;max-width:360px;display:flex;flex-direction:column;gap:8px}\u001b[39m\n\u001b[31m.form-group label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px}\u001b[39m\n\u001b[31mselect{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31mselect:focus{border-color:var(--accent)}·\u001b[39m\n\u001b[31m/* ── PIN PAD ── */\u001b[39m\n\u001b[31m.pin-display{display:flex;gap:14px;justify-content:center;margin:4px 0}\u001b[39m\n\u001b[31m.pin-dot{width:16px;height:16px;border-radius:50%;border:2px solid var(--muted);background:transparent;transition:.15s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.pin-pad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;max-width:260px;margin:0 auto;width:100%}\u001b[39m\n\u001b[31m.pin-key{background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  font-size:22px;font-weight:600;padding:16px;border-radius:var(--radius);cursor:pointer;\u001b[39m\n\u001b[31m  text-align:center;line-height:1;user-select:none}\u001b[39m\n\u001b[31m.pin-key:active{background:var(--border)}\u001b[39m\n\u001b[31m.pin-key.clr{color:var(--danger);font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.pin-key.back{color:var(--accent2)}\u001b[39m\n\u001b[31m.login-err{font-size:13px;color:var(--danger);text-align:center;min-height:18px}\u001b[39m\n\u001b[31m@keyframes shake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-6px)}40%,80%{transform:translateX(6px)}}\u001b[39m\n\u001b[31m.pin-display.shake{animation:shake .4s ease}·\u001b[39m\n\u001b[31m/* ── KPI CARDS ── */\u001b[39m\n\u001b[31m.kpi-row{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\u001b[39m\n\u001b[31m.kpi-4col{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m#home-body{gap:10px}\u001b[39m\n\u001b[31m.kpi-card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:10px 8px;text-align:center;border-left:3px solid var(--accent);cursor:default}\u001b[39m\n\u001b[31m.kpi-card.alert{border-left-color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-card.warn{border-left-color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-card.ok{border-left-color:var(--success)}\u001b[39m\n\u001b[31m.kpi-card.info{border-left-color:var(--info)}\u001b[39m\n\u001b[31m.kpi-val{font-size:18px;font-weight:900;color:var(--accent);line-height:1.2}\u001b[39m\n\u001b[31m.kpi-val.red{color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-val.amber{color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-val.green{color:var(--success)}\u001b[39m\n\u001b[31m.kpi-val.blue{color:var(--info)}\u001b[39m\n\u001b[31m.kpi-lbl{font-size:10px;color:var(--muted);margin-top:4px;text-transform:uppercase;letter-spacing:.4px;line-height:1.3}\u001b[39m\n\u001b[31m.kpi-skel{background:var(--border);border-radius:4px;height:24px;margin-bottom:4px;\u001b[39m\n\u001b[31m  animation:shimmer 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes shimmer{0%,100%{opacity:.5}50%{opacity:1}}·\u001b[39m\n\u001b[31m/* ── SECTION TITLE ── */\u001b[39m\n\u001b[31m.section-title{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.8px}·\u001b[39m\n\u001b[31m/* ── MGMT HOME: MAIN TILES ── */\u001b[39m\n\u001b[31m.hub-main-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px}\u001b[39m\n\u001b[31m.hub-main-tile{background:var(--card);border:2px solid var(--border);border-radius:18px;\u001b[39m\n\u001b[31m  padding:36px 12px 28px;text-align:center;cursor:pointer;transition:background .15s,border-color .15s}\u001b[39m\n\u001b[31m.hub-main-tile:active{background:#1a2a1a;border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-main-icon{font-size:40px;line-height:1;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-main-label{font-size:15px;font-weight:800;color:var(--text)}\u001b[39m\n\u001b[31m/* ── MGMT HOME: QUICK ACTIONS ── */\u001b[39m\n\u001b[31m.hub-qa-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-qa-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(88px,1fr));gap:10px}\u001b[39m\n\u001b[31m.hub-qa-tile{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px 8px;text-align:center;cursor:pointer;display:flex;flex-direction:column;\u001b[39m\n\u001b[31m  align-items:center;gap:6px;transition:background .15s}\u001b[39m\n\u001b[31m.hub-qa-tile:active{background:var(--border)}\u001b[39m\n\u001b[31m.hub-qa-icon{font-size:26px;line-height:1}\u001b[39m\n\u001b[31m.hub-qa-label{font-size:11px;font-weight:700;color:var(--text);line-height:1.3}\u001b[39m\n\u001b[31m.hub-qa-item{display:flex;align-items:center;gap:12px;padding:10px 0;\u001b[39m\n\u001b[31m  border-bottom:1px solid var(--border);cursor:pointer}\u001b[39m\n\u001b[31m.hub-qa-item:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.hub-qa-item-icon{font-size:22px;width:32px;text-align:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item-label{flex:1;font-size:14px;font-weight:600}\u001b[39m\n\u001b[31m.hub-qa-item-toggle{width:20px;height:20px;border-radius:4px;border:2px solid var(--border);\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle::after{content:'✓';color:#fff;font-weight:700;font-size:12px}·\u001b[39m\n\u001b[31m/* ── TILE GRID ── */\u001b[39m\n\u001b[31m.tile-grid{display:grid;gap:12px}\u001b[39m\n\u001b[31m.tile-grid.mgmt{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m.tile-grid.field{grid-template-columns:repeat(2,1fr)}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:18px 8px;display:flex;flex-direction:column;align-items:center;gap:8px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s;position:relative}\u001b[39m\n\u001b[31m.tile:active{background:var(--border);transform:scale(.97)}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px;line-height:1}\u001b[39m\n\u001b[31m.tile-label{font-size:11px;font-weight:700;color:var(--text);text-align:center;text-transform:uppercase;letter-spacing:.4px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-icon{font-size:24px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-label{font-size:10px}\u001b[39m\n\u001b[31m.tile.coming{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.tile.coming:active{transform:none;background:var(--card)}\u001b[39m\n\u001b[31m.soon-badge{position:absolute;top:6px;right:6px;background:var(--accent2);color:#000;\u001b[39m\n\u001b[31m  font-size:8px;font-weight:800;padding:2px 5px;border-radius:20px;text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── USER BADGE ── */\u001b[39m\n\u001b[31m.user-badge{display:flex;align-items:center;gap:12px}\u001b[39m\n\u001b[31m.avatar{width:44px;height:44px;background:var(--accent);border-radius:50%;\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:800;\u001b[39m\n\u001b[31m  color:#fff;flex-shrink:0;text-transform:uppercase}\u001b[39m\n\u001b[31m.user-name{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.user-role{font-size:12px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── TEAM PINS ── */\u001b[39m\n\u001b[31m.emp-row{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px}\u001b[39m\n\u001b[31m.btn{display:flex;align-items:center;justify-content:center;gap:6px;border:none;\u001b[39m\n\u001b[31m  border-radius:var(--radius);font-size:15px;font-weight:700;padding:12px 18px;\u001b[39m\n\u001b[31m  cursor:pointer;width:100%;transition:.15s;font-family:var(--font)}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--card);border:1px solid var(--border);color:var(--text)}\u001b[39m\n\u001b[31m.btn-sm{padding:7px 12px;font-size:12px;font-weight:700;width:auto;flex-shrink:0}\u001b[39m\n\u001b[31m.info-note{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;font-size:13px;color:var(--muted);line-height:1.5}·\u001b[39m\n\u001b[31m/* ── MODAL ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:200;\u001b[39m\n\u001b[31m  align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;\u001b[39m\n\u001b[31m  padding:24px;width:100%;max-width:480px;display:flex;flex-direction:column;gap:14px;\u001b[39m\n\u001b[31m  max-height:90dvh;overflow-y:auto;-webkit-overflow-scrolling:touch}\u001b[39m\n\u001b[31m.modal-title{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.modal-box input[type=password]{\u001b[39m\n\u001b[31m  width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31m.modal-box input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.action-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px}·\u001b[39m\n\u001b[31m/* ── SHARE PICKER ── */\u001b[39m\n\u001b[31m#screen-share .body{align-items:center;justify-content:center;gap:24px}\u001b[39m\n\u001b[31m.share-prompt{font-size:15px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.share-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;width:100%;max-width:360px}\u001b[39m\n\u001b[31m.share-tile{background:var(--card);border:2px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:24px 8px;display:flex;flex-direction:column;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s}\u001b[39m\n\u001b[31m.share-tile:active{background:var(--border);transform:scale(.97);border-color:var(--accent)}\u001b[39m\n\u001b[31m.share-tile-icon{font-size:36px;line-height:1}\u001b[39m\n\u001b[31m.share-tile-label{font-size:12px;font-weight:700;color:var(--text);text-align:center;\u001b[39m\n\u001b[31m  text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── INSTALL BANNER ── */\u001b[39m\n\u001b[31m#install-banner{display:none;position:fixed;bottom:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:linear-gradient(135deg,#1a1a1a,#0d2010);\u001b[39m\n\u001b[31m  border-top:1px solid var(--accent);padding:14px 16px;\u001b[39m\n\u001b[31m  align-items:center;gap:12px;box-shadow:0 -4px 24px rgba(0,0,0,.7)}\u001b[39m\n\u001b[31m#install-banner.show{display:flex}\u001b[39m\n\u001b[31m#install-banner .ib-icon{font-size:30px;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-text{flex:1}\u001b[39m\n\u001b[31m#install-banner .ib-title{font-size:14px;font-weight:700;color:var(--text)}\u001b[39m\n\u001b[31m#install-banner .ib-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#install-banner .ib-btn{background:var(--accent);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-close{background:none;border:none;color:var(--muted);font-size:20px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── UPDATE BANNER ── */\u001b[39m\n\u001b[31m#update-banner{display:none;position:fixed;top:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:#1a2a1a;border-bottom:2px solid var(--success);\u001b[39m\n\u001b[31m  padding:12px 16px;align-items:center;gap:12px}\u001b[39m\n\u001b[31m#update-banner.show{display:flex}\u001b[39m\n\u001b[31m#update-banner .ub-text{flex:1;font-size:14px;font-weight:600;color:var(--text)}\u001b[39m\n\u001b[31m#update-banner .ub-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#update-banner .ub-btn{background:var(--success);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:8px 14px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#update-banner .ub-close{background:none;border:none;color:var(--muted);font-size:18px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#222;color:#fff;padding:10px 20px;border-radius:20px;font-size:14px;font-weight:600;\u001b[39m\n\u001b[31m  opacity:0;transition:.3s;pointer-events:none;white-space:nowrap;z-index:500}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  padding:40px;color:var(--muted);font-size:14px;gap:12px}\u001b[39m\n\u001b[31m.spinner{width:36px;height:36px;border:3px solid var(--border);border-top-color:var(--accent);\u001b[39m\n\u001b[31m  border-radius:50%;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* ── CARD (jobs / campaign screens) ── */\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius)}\u001b[39m\n\u001b[31m.card.tappable:active{border-color:var(--accent);opacity:.85}·\u001b[39m\n\u001b[31m/* ── STATUS CHIPS (tour / campaign) ── */\u001b[39m\n\u001b[31m.status-chip{font-size:11px;padding:4px 11px;border-radius:8px;cursor:pointer;\u001b[39m\n\u001b[31m  background:var(--card);color:var(--text);border:1px solid var(--border);font-weight:600;\u001b[39m\n\u001b[31m  font-family:var(--font);transition:.15s}\u001b[39m\n\u001b[31m.status-chip.sel{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.status-chip:active{opacity:.8}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SPLASH\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-splash\\\" class=\\\"screen active\\\">\u001b[39m\n\u001b[31m  <div class=\\\"splash-logo\\\">360LM</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-sub\\\">Operations Hub</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-dot\\\" style=\\\"margin-top:16px\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     LOGIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-login\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-wrap\\\">\u001b[39m\n\u001b[31m    <div class=\\\"login-logo\\\">360LM</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">Operations Hub</div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Select Your Name</label>\u001b[39m\n\u001b[31m      <select id=\\\"login-name\\\"><option value=\\\"\\\">— Choose employee —</option><option value=\\\"amit\\\">Amit</option><option value=\\\"ashok\\\">Ashok</option><option value=\\\"gurpreet\\\">Gurpreet</option><option value=\\\"harish\\\">Harish Kumar Lal</option><option value=\\\"mukesh\\\">Mukesh</option><option value=\\\"narender\\\">Narender</option><option value=\\\"pramod\\\">Pramod Narang</option><option value=\\\"rakesh\\\">Rakesh</option><option value=\\\"sachin\\\">Sachin</option><option value=\\\"shambhu\\\">Shambhu</option><option value=\\\"umashankar\\\">Umashankar</option></select>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m      <label style=\\\"text-align:center\\\">Enter PIN</label>\u001b[39m\n\u001b[31m      <div class=\\\"pin-display\\\" id=\\\"pin-display\\\">\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd0\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd1\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd2\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd3\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"login-err\\\" id=\\\"login-err\\\"></div>\u001b[39m\n\u001b[31m      <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-1\\\" data-k=\\\"1\\\" aria-label=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-2\\\" data-k=\\\"2\\\" aria-label=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-3\\\" data-k=\\\"3\\\" aria-label=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-4\\\" data-k=\\\"4\\\" aria-label=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-5\\\" data-k=\\\"5\\\" aria-label=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-6\\\" data-k=\\\"6\\\" aria-label=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-7\\\" data-k=\\\"7\\\" aria-label=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-8\\\" data-k=\\\"8\\\" aria-label=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-9\\\" data-k=\\\"9\\\" aria-label=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key clr\\\" id=\\\"pk-C\\\" data-k=\\\"C\\\" aria-label=\\\"Clear\\\">CLR</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-0\\\" data-k=\\\"0\\\" aria-label=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key back\\\" id=\\\"pk-B\\\" data-k=\\\"B\\\" aria-label=\\\"Backspace\\\">⌫</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     HOME  (built dynamically)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\" id=\\\"home-header\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"home-body\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SHARE PICKER\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-share\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>Share Image To</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">Choose where to send this photo</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"share-prompt\\\">Which tool should receive this image?</div>\u001b[39m\n\u001b[31m    <div class=\\\"share-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-expense\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🧾</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Expenses</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-recce\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">📍</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Recce</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-installation\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🔧</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Installation</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     TEAM PINs  (admin only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-team\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-back\\\" style=\\\"font-size:20px\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Team PINs</h1><div class=\\\"sub\\\">Set employee login PINs</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-refresh\\\" title=\\\"Refresh\\\">🔄</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"info-note\\\">PINs are stored encrypted — current PIN cannot be viewed. You can set a new PIN for any employee below.</div>\u001b[39m\n\u001b[31m    <div id=\\\"team-list\\\"></div>·\u001b[39m\n\u001b[31m    <!-- Icon Admin — Harish & Pramod only -->\u001b[39m\n\u001b[31m    <div id=\\\"icon-admin-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:8px\\\">🎨 PWA Icons (Admin)</div>\u001b[39m\n\u001b[31m      <div class=\\\"info-note\\\" style=\\\"margin-bottom:10px\\\">Upload a new icon for any PWA. Upload both 192×192 and 512×512 sizes. PNG/JPG/WEBP accepted — auto-resized on server.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;flex-direction:column;gap:10px\\\">·\u001b[39m\n\u001b[31m        <!-- Secret management -->\u001b[39m\n\u001b[31m        <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:12px;padding:14px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px\\\">🔑 Admin Secret</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:center;gap:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"font-family:monospace;font-size:13px;color:var(--accent);background:var(--bg);\u001b[39m\n\u001b[31m              border-radius:6px;padding:8px 12px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\" id=\\\"icon-secret-display\\\">—</div>\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"toggleSecretVisible()\\\" id=\\\"btn-secret-show\\\">Show</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:10px\\\">Change secret (min 16 chars):</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m            <input id=\\\"icon-new-secret\\\" type=\\\"text\\\" placeholder=\\\"Enter new secret…\\\" style=\\\"flex:1;background:var(--bg);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m              padding:10px 12px;border-radius:8px;font-size:13px;font-family:monospace;outline:none\\\">\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"changeIconSecret()\\\" style=\\\"white-space:nowrap\\\">Update</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div id=\\\"icon-secret-err\\\" style=\\\"font-size:11px;color:var(--danger);min-height:14px;margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>·\u001b[39m\n\u001b[31m        <!-- Upload form -->\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Select PWA</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-pwa-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"hub\\\">Hub</option>\u001b[39m\n\u001b[31m            <option value=\\\"expense\\\">Expenses</option>\u001b[39m\n\u001b[31m            <option value=\\\"finance\\\">Finance</option>\u001b[39m\n\u001b[31m            <option value=\\\"installation\\\">Installation</option>\u001b[39m\n\u001b[31m            <option value=\\\"recce\\\">Recce</option>\u001b[39m\n\u001b[31m            <option value=\\\"dispatch\\\">Dispatch</option>\u001b[39m\n\u001b[31m            <option value=\\\"production\\\">Production</option>\u001b[39m\n\u001b[31m            <option value=\\\"vehicle\\\">Vehicle Log</option>\u001b[39m\n\u001b[31m            <option value=\\\"vrs\\\">Vehicle Request</option>\u001b[39m\n\u001b[31m            <option value=\\\"sales\\\">Sales</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Icon Size</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-size-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"192\\\">192 × 192 (standard)</option>\u001b[39m\n\u001b[31m            <option value=\\\"512\\\">512 × 512 (large)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Choose Image File</label>\u001b[39m\n\u001b[31m          <input type=\\\"file\\\" id=\\\"icon-file-input\\\" accept=\\\"image/png,image/jpeg,image/webp\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:14px;width:100%\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-preview-wrap\\\" style=\\\"display:none;text-align:center\\\">\u001b[39m\n\u001b[31m          <img id=\\\"icon-preview-img\\\" style=\\\"width:96px;height:96px;border-radius:16px;border:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-upload-err\\\" style=\\\"font-size:12px;color:var(--danger);min-height:16px\\\"></div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" onclick=\\\"uploadIcon()\\\">Upload Icon</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     MODAL: Admin set PIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-setpin\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🔑 Set PIN — <span id=\\\"setpin-target-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>New PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Enter new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-confirm-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Confirm New PIN</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-confirm\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Repeat new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-auth-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Your own current PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"setpin-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-setpin-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-setpin-save\\\">Set PIN</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     JOBS — Campaign list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-jobs\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Jobs</h1><div class=\\\"sub\\\">Campaigns &amp; Tours</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh jobs\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-campaign\\\" title=\\\"New Campaign\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"jobs-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     CAMPAIGN DETAIL — Tour list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-campaign\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 id=\\\"campaign-hdr-title\\\">Campaign</h1><div class=\\\"sub\\\" id=\\\"campaign-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh campaign\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-tour\\\" title=\\\"New Tour\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"campaign-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Campaign -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-campaign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">📋 New Campaign</div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Year</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-year\\\" min=\\\"2024\\\" max=\\\"2030\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Phase #</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-phase\\\" value=\\\"1\\\" min=\\\"1\\\" max=\\\"99\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Brand <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-brand\\\" placeholder=\\\"e.g. Lenovo\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Client <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-client\\\" placeholder=\\\"e.g. Lenovo India\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Campaign Name <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-campaign\\\" placeholder=\\\"e.g. Chandigarh BTL\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Generated Campaign ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nc-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nc-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nc-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nc-save\\\">Create</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Tour -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-tour\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px;max-height:88vh;overflow-y:auto\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🗺️ New Tour</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Tour Title</div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nt-title\\\" placeholder=\\\"e.g. Chandigarh Week 1\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Start Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-start\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">End Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-end\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:6px\\\">Assign Employees <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-employees\\\" style=\\\"display:flex;flex-direction:column;gap:6px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"color:var(--muted);font-size:13px\\\">Loading…</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Notes</div>\u001b[39m\n\u001b[31m      <textarea id=\\\"nt-notes\\\" rows=\\\"2\\\" placeholder=\\\"Optional instructions or remarks\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px;font-family:inherit;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Tour ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-id-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nt-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nt-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nt-save\\\">Create Tour</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- UPDATE BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"update-banner\\\">\u001b[39m\n\u001b[31m  <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ub-text\\\">App update available</div>\u001b[39m\n\u001b[31m    <div class=\\\"ub-sub\\\">Tap to refresh and get the latest version</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ub-btn\\\" id=\\\"btn-update\\\">Update now</button>\u001b[39m\n\u001b[31m  <button class=\\\"ub-close\\\" id=\\\"btn-update-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- INSTALL BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"install-banner\\\">\u001b[39m\n\u001b[31m  <div class=\\\"ib-icon\\\">📲</div>\u001b[39m\n\u001b[31m  <div class=\\\"ib-text\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ib-title\\\">Install 360LM Hub</div>\u001b[39m\n\u001b[31m    <div class=\\\"ib-sub\\\">Add to home screen for the best experience</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ib-btn\\\" id=\\\"btn-install\\\">Install</button>\u001b[39m\n\u001b[31m  <button class=\\\"ib-close\\\" id=\\\"btn-install-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: AI Config (mgmt only) -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-ai\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🤖 AI Config — Screenshot Extraction</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Tesseract OCR</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-tesseract\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Extraction Fallback</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-enabled\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Model</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-model\\\">\u001b[39m\n\u001b[31m        <option value=\\\"haiku\\\">Haiku (fast, cost-effective)</option>\u001b[39m\n\u001b[31m        <option value=\\\"sonnet\\\">Sonnet (higher accuracy)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-ai-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-ai-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"border-top:1px solid var(--border);padding-top:12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">API Key Health</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px;margin-bottom:10px\\\">Live check — result is always the same for all users</div>\u001b[39m\n\u001b[31m      <div id=\\\"api-key-status-wrap\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-check-keys\\\" style=\\\"margin-top:10px;width:100%\\\">Check API Keys</button>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:14px;border-top:1px solid var(--border);padding-top:12px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">View Usage &amp; Billing</div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Anthropic</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/usage\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Google Maps</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/google/maps-apis/api-list\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: Hub Quick-Action Config -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-qa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">⚡ Quick Actions</div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-config-list\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-cfg-err\\\" style=\\\"color:var(--danger);font-size:13px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-qa-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-qa-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SCRIPT\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31m/* ── CONFIG ── */\u001b[39m\n\u001b[31mconst CFG = {\u001b[39m\n\u001b[31m  api:        '/db',\u001b[39m\n\u001b[31m  apiHeaders: { 'Content-Type': 'application/json', 'Accept-Profile': 'expense' },\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31m/* ── SERVER ACCESS MAP (fetched from hub-access.json, overrides ACCESS for non-mgmt) ── */\u001b[39m\n\u001b[31mlet _serverAccess = {};\u001b[39m\n\u001b[31masync function loadHubAccess() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/hub/hub-access.json?t=' + Date.now());\u001b[39m\n\u001b[31m    if (r.ok) _serverAccess = await r.json();\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── ROLE ACCESS MAP (hardcoded fallback) ── */\u001b[39m\n\u001b[31mconst ACCESS = {\u001b[39m\n\u001b[31m  harish:     ['expense','installation','finance','recce','dispatch','production','jobs','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  pramod:     ['expense','installation','finance','recce','dispatch','production','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  rakesh:     ['installation','expense','production','jobs','vehicle','vrs','sales','hr'],\u001b[39m\n\u001b[31m  ashok:      ['expense','production','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  shambhu:    ['finance','expense','vehicle','vrs','stores','activity','hr'],\u001b[39m\n\u001b[31m  gurpreet:   ['expense','dispatch','vehicle','vrs','stores','hr'],\u001b[39m\n\u001b[31m  sachin:     ['installation','expense','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  mukesh:     ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  umashankar: ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  narender:   ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  amit:       ['vehicle','vrs','hr'],\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst TILES = [\u001b[39m\n\u001b[31m  { id:'expense',      label:'Expenses',     icon:'🧾', url:'/expense/',               live:true  },\u001b[39m\n\u001b[31m  { id:'installation', label:'Installation', icon:'🔧', url:'/installation/current/',  live:true  },\u001b[39m\n\u001b[31m  { id:'finance',      label:'Finance',      icon:'💰', url:'/finance/',               live:true  },\u001b[39m\n\u001b[31m  { id:'recce',        label:'Recce',        icon:'📍', url:'/recce/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'dispatch',     label:'Dispatch',     icon:'🚚', url:'/dispatch/',              live:true  },\u001b[39m\n\u001b[31m  { id:'production',   label:'Production',   icon:'⚙️', url:'/production/',            live:true  },\u001b[39m\n\u001b[31m  { id:'jobs',         label:'Jobs',         icon:'📋', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'vehicle',      label:'Vehicle Log',  icon:'🚐', url:'/vehicle/',               live:true  },\u001b[39m\n\u001b[31m  { id:'vrs',          label:'Veh. Request', icon:'📋', url:'/vrs/',                   live:true  },\u001b[39m\n\u001b[31m  { id:'sales',        label:'Sales',        icon:'💼', url:'/sales/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'stores',       label:'Stores',       icon:'📦', url:'/stores/',                live:true  },\u001b[39m\n\u001b[31m  { id:'client',       label:'Client',       icon:'👤', url:'/client/',                live:true  },\u001b[39m\n\u001b[31m  { id:'team-pins',    label:'Team PINs',    icon:'🔑', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'admin',        label:'Admin',        icon:'🛠️', url:'/admin/',                  live:true  },\u001b[39m\n\u001b[31m  { id:'activity',     label:'Activity',     icon:'🎯', url:'/activity/',               live:true  },\u001b[39m\n\u001b[31m  { id:'hr',           label:'HR',           icon:'👥', url:'/hr/',                     live:true  },\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31mconst MGMT = ['harish', 'pramod'];·\u001b[39m\n\u001b[31m/* ── STATE ── */\u001b[39m\n\u001b[31mlet currentUser     = null;\u001b[39m\n\u001b[31mlet pinBuffer       = '';\u001b[39m\n\u001b[31mlet setPinTarget    = null;\u001b[39m\n\u001b[31mlet pendingShare    = false;\u001b[39m\n\u001b[31mlet _loginAttempts  = 0;\u001b[39m\n\u001b[31mlet _lockoutUntil   = 0;\u001b[39m\n\u001b[31mlet _loginInFlight  = false;·\u001b[39m\n\u001b[31m/* ── UTILS ── */\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => { s.classList.remove('active'); s.style.display = ''; });\u001b[39m\n\u001b[31m  document.getElementById('screen-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet toastTimer;\u001b[39m\n\u001b[31mfunction toast(msg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.classList.add('show');\u001b[39m\n\u001b[31m  clearTimeout(toastTimer);\u001b[39m\n\u001b[31m  toastTimer = setTimeout(() => el.classList.remove('show'), 2800);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fmtINR(n) {\u001b[39m\n\u001b[31m  if (n >= 100000) return '₹' + (n / 100000).toFixed(1) + 'L';\u001b[39m\n\u001b[31m  if (n >= 1000)   return '₹' + (n / 1000).toFixed(1) + 'K';\u001b[39m\n\u001b[31m  return '₹' + Math.round(n);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SESSION ── */\u001b[39m\n\u001b[31mconst SESSION_KEY  = 'lm360-session';\u001b[39m\n\u001b[31mconst EXP_SES_KEY  = '360exp-session';\u001b[39m\n\u001b[31mconst SESSION_TTL  = 12 * 3600 * 1000;·\u001b[39m\n\u001b[31mfunction saveSession(user) {\u001b[39m\n\u001b[31m  const payload = { empId: user.id, name: user.name, role: user.role, loginAt: new Date().toISOString(), source: 'hub' };\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m  localStorage.setItem(EXP_SES_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearSession() {\u001b[39m\n\u001b[31m  localStorage.removeItem(SESSION_KEY);\u001b[39m\n\u001b[31m  localStorage.removeItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const raw = localStorage.getItem(SESSION_KEY) || localStorage.getItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m    if (!raw) return null;\u001b[39m\n\u001b[31m    const s = JSON.parse(raw);\u001b[39m\n\u001b[31m    if (!s.empId || !s.name || !s.role) return null;\u001b[39m\n\u001b[31m    if (Date.now() - new Date(s.loginAt).getTime() > SESSION_TTL) { clearSession(); return null; }\u001b[39m\n\u001b[31m    return s;\u001b[39m\n\u001b[31m  } catch (_) { return null; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGIN FLOW ── */\u001b[39m\n\u001b[31masync function initLogin() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('login-name');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— Choose employee —</option>'; // clear before repopulating\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    emps.forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    [\u001b[39m\n\u001b[31m      { id:'amit',        name:'Amit'             },\u001b[39m\n\u001b[31m      { id:'ashok',       name:'Ashok'            },\u001b[39m\n\u001b[31m      { id:'gurpreet',    name:'Gurpreet'         },\u001b[39m\n\u001b[31m      { id:'harish',      name:'Harish Kumar Lal' },\u001b[39m\n\u001b[31m      { id:'mukesh',      name:'Mukesh'           },\u001b[39m\n\u001b[31m      { id:'narender',    name:'Narender'         },\u001b[39m\n\u001b[31m      { id:'pramod',      name:'Pramod Narang'    },\u001b[39m\n\u001b[31m      { id:'rakesh',      name:'Rakesh'           },\u001b[39m\n\u001b[31m      { id:'sachin',      name:'Sachin'           },\u001b[39m\n\u001b[31m      { id:'shambhu',     name:'Shambhu'          },\u001b[39m\n\u001b[31m      { id:'umashankar',  name:'Umashankar'       },\u001b[39m\n\u001b[31m    ].forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  document.querySelectorAll('.pin-key').forEach(btn =>\u001b[39m\n\u001b[31m    btn.addEventListener('click', () => handlePinKey(btn.dataset.k))\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handlePinKey(k) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (_loginInFlight) return;\u001b[39m\n\u001b[31m  // Lockout guard\u001b[39m\n\u001b[31m  if (_lockoutUntil && Date.now() < _lockoutUntil) {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    errEl.textContent = `Too many attempts — wait ${secs}s`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (_lockoutUntil) { _lockoutUntil = 0; _loginAttempts = 0; }\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (k === 'C') { pinBuffer = ''; }\u001b[39m\n\u001b[31m  else if (k === 'B') { pinBuffer = pinBuffer.slice(0, -1); }\u001b[39m\n\u001b[31m  else {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (!empId) { toast('Select your name first'); return; }\u001b[39m\n\u001b[31m    if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  if (pinBuffer.length === 4) {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (empId) attemptLogin(empId, pinBuffer);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updatePinDisplay() {\u001b[39m\n\u001b[31m  for (let i = 0; i < 4; i++)\u001b[39m\n\u001b[31m    document.getElementById('pd' + i).classList.toggle('filled', i < pinBuffer.length);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _shakePin() {\u001b[39m\n\u001b[31m  const pd = document.getElementById('pin-display');\u001b[39m\n\u001b[31m  pd.classList.add('shake');\u001b[39m\n\u001b[31m  setTimeout(() => pd.classList.remove('shake'), 400);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _startLockout() {\u001b[39m\n\u001b[31m  _lockoutUntil = Date.now() + 30000;\u001b[39m\n\u001b[31m  _loginAttempts = 0;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  const tick = () => {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    if (secs > 0) { errEl.textContent = `Too many attempts — wait ${secs}s`; setTimeout(tick, 500); }\u001b[39m\n\u001b[31m    else { _lockoutUntil = 0; errEl.textContent = ''; }\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  tick();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function attemptLogin(empId, pin) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  _loginInFlight = true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pin }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      _loginAttempts++;\u001b[39m\n\u001b[31m      _shakePin();\u001b[39m\n\u001b[31m      if (_loginAttempts >= 5) { _startLockout(); return; }\u001b[39m\n\u001b[31m      errEl.textContent = `Wrong PIN — ${5 - _loginAttempts} attempt${5 - _loginAttempts !== 1 ? 's' : ''} left`;\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    currentUser = { id: user.id, name: user.name, role: user.role };\u001b[39m\n\u001b[31m    saveSession(currentUser);\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — check connection';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    _loginInFlight = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── HOME SCREEN ── */\u001b[39m\n\u001b[31masync function buildHome() {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const body   = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  // Management users always use hardcoded ACCESS; others prefer server-side hub-access.json\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const tiles  = accessIds.map(id => TILES.find(t => t.id === id)).filter(Boolean);·\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"avatar\\\">${initials}</div>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1 style=\\\"font-size:16px\\\">${currentUser.name}</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">${currentUser.role}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${isMgmt ? '<button class=\\\"btn-icon\\\" id=\\\"btn-hub-ai\\\" title=\\\"AI Config\\\" style=\\\"font-size:18px\\\">🤖</button>' : ''}\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m  if (isMgmt) document.getElementById('btn-hub-ai').addEventListener('click', openHubAiConfig);·\u001b[39m\n\u001b[31m  if (isMgmt) {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"hub-main-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-dash\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">📊</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">Dashboard</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-tools\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">🧰</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">All Tools</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-header\\\">\u001b[39m\n\u001b[31m        <div class=\\\"section-title\\\" style=\\\"margin:0\\\">Quick Actions</div>\u001b[39m\n\u001b[31m        <button class=\\\"btn-icon\\\" id=\\\"btn-hub-qa-cfg\\\" title=\\\"Configure Quick Actions\\\" style=\\\"font-size:18px;padding:8px;min-width:44px;min-height:44px\\\">⚙️</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-grid\\\" id=\\\"hub-qa-grid\\\"></div>`;\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-dash').addEventListener('click',  () => enterHubDash(tiles));\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-tools').addEventListener('click', () => enterHubTools(tiles));\u001b[39m\n\u001b[31m    document.getElementById('btn-hub-qa-cfg').addEventListener('click', openHubQaConfig);\u001b[39m\n\u001b[31m    renderHubQA();\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"tile-grid field\\\" id=\\\"tile-grid\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"text-align:center;font-size:12px;color:var(--muted);padding-top:8px\\\">Tap a tile to open your tool</div>`;\u001b[39m\n\u001b[31m    renderTiles(tiles, 'field');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderTiles(tiles, mode) {\u001b[39m\n\u001b[31m  const grid = document.getElementById('tile-grid');\u001b[39m\n\u001b[31m  tiles.forEach(tile => {\u001b[39m\n\u001b[31m    const div = document.createElement('div');\u001b[39m\n\u001b[31m    div.className = 'tile ' + mode + (tile.live ? '' : ' coming');\u001b[39m\n\u001b[31m    div.innerHTML = `<div class=\\\"tile-icon\\\">${tile.icon}</div><div class=\\\"tile-label\\\">${tile.label}</div>` +\u001b[39m\n\u001b[31m      (tile.live ? '' : '<div class=\\\"soon-badge\\\">Soon</div>');\u001b[39m\n\u001b[31m    div.addEventListener('click', () => handleTile(tile));\u001b[39m\n\u001b[31m    grid.appendChild(div);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handleTile(tile) {\u001b[39m\n\u001b[31m  if (!tile.live) { toast('Coming soon'); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'team-pins') { enterTeamPins(); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'jobs')      { enterJobs();     return; }\u001b[39m\n\u001b[31m  window.location.href = tile.url;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT SUB-VIEWS ── */\u001b[39m\n\u001b[31mfunction _mgmtHeader(title) {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-subview-back\\\" style=\\\"font-size:20px;font-weight:900\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 style=\\\"font-size:16px\\\">${title}</h1></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-subview-back').addEventListener('click', () => buildHome());\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubDash(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('Dashboard');\u001b[39m\n\u001b[31m  const hdr = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const refreshBtn = document.createElement('button');\u001b[39m\n\u001b[31m  refreshBtn.className = 'btn-icon';\u001b[39m\n\u001b[31m  refreshBtn.title = 'Refresh dashboard';\u001b[39m\n\u001b[31m  refreshBtn.setAttribute('aria-label', 'Refresh dashboard');\u001b[39m\n\u001b[31m  refreshBtn.style.cssText = 'font-size:18px';\u001b[39m\n\u001b[31m  refreshBtn.textContent = '🔄';\u001b[39m\n\u001b[31m  refreshBtn.addEventListener('click', loadKPIs);\u001b[39m\n\u001b[31m  hdr.insertBefore(refreshBtn, hdr.querySelector('#btn-logout'));\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Actions Required</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-actions\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Operations</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row kpi-4col\\\" id=\\\"kpi-ops\\\">${skel}${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Production</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-prod\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Installation</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-inst\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">HR</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-hr\\\">${skel}${skel}${skel}</div>`;\u001b[39m\n\u001b[31m  loadKPIs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubTools(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('All Tools');\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  body.innerHTML = `<div class=\\\"tile-grid mgmt\\\" id=\\\"tile-grid\\\"></div>`;\u001b[39m\n\u001b[31m  renderTiles(tiles, 'mgmt');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT AI CONFIG ── */\u001b[39m\n\u001b[31mconst HUB_FIN_HDR = { 'Content-Type': 'application/json', 'Accept-Profile': 'finance' };·\u001b[39m\n\u001b[31masync function openHubAiConfig() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r    = await fetch(`${CFG.api}/upi_config`, { headers: HUB_FIN_HDR });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const get  = key => rows.find(x => x.key === key)?.value;\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-tesseract').value = get('tesseract_enabled') || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-enabled').value   = get('ai_enabled')        || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-model').value     = get('ai_model')           || 'haiku';\u001b[39m\n\u001b[31m  } catch(_) { toast('Could not load AI config'); return; }\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-ai').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveHubAiConfig() {\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-hub-ai-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  const pairs = [\u001b[39m\n\u001b[31m    { key:'tesseract_enabled', value: document.getElementById('hub-ai-tesseract').value },\u001b[39m\n\u001b[31m    { key:'ai_enabled',        value: document.getElementById('hub-ai-enabled').value },\u001b[39m\n\u001b[31m    { key:'ai_model',          value: document.getElementById('hub-ai-model').value },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const results = await Promise.all(pairs.map(p =>\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/upi_config?key=eq.${p.key}`, {\u001b[39m\n\u001b[31m        method: 'PATCH',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'finance', 'Prefer': 'return=minimal' },\u001b[39m\n\u001b[31m        body: JSON.stringify({ value: p.value }),\u001b[39m\n\u001b[31m      })\u001b[39m\n\u001b[31m    ));\u001b[39m\n\u001b[31m    if (results.some(r => !r.ok)) throw new Error('partial failure');\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show');\u001b[39m\n\u001b[31m    toast('✅ AI config saved');\u001b[39m\n\u001b[31m  } catch(_) { toast('Save failed'); }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Save'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function checkApiKeyStatus() {\u001b[39m\n\u001b[31m  const btn  = document.getElementById('btn-check-keys');\u001b[39m\n\u001b[31m  const wrap = document.getElementById('api-key-status-wrap');\u001b[39m\n\u001b[31m  btn.textContent = 'Checking…';\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  wrap.innerHTML = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/tour-ai/key-status');\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Proxy error ' + r.status);\u001b[39m\n\u001b[31m    const d = await r.json();\u001b[39m\n\u001b[31m    function mkBadge(info, label, link) {\u001b[39m\n\u001b[31m      const col  = info.ok ? 'var(--success)' : 'var(--danger)';\u001b[39m\n\u001b[31m      const icon = info.ok ? '✅' : '❌';\u001b[39m\n\u001b[31m      const note = info.ok ? (info.model || 'Valid') : (info.error || 'Error');\u001b[39m\n\u001b[31m      const hint = info.hint ? ' · key ' + info.hint : '';\u001b[39m\n\u001b[31m      return `<div style=\\\"background:var(--card);border:1px solid var(--border);border-left:3px solid ${col};border-radius:8px;padding:10px 12px;display:flex;align-items:center;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:13px;font-weight:600;color:var(--text)\\\">${icon} ${label}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${note}${hint}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <a href=\\\"${link}\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:11px;color:var(--info);text-decoration:none;white-space:nowrap\\\">Billing ↗</a>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    wrap.innerHTML =\u001b[39m\n\u001b[31m      mkBadge(d.anthropic, 'Anthropic API',    'https://console.anthropic.com/settings/billing') +\u001b[39m\n\u001b[31m      mkBadge(d.maps,      'Google Maps API',  'https://console.cloud.google.com/billing');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    wrap.innerHTML = `<div style=\\\"font-size:12px;color:var(--danger)\\\">Error: ${e.message}</div>`;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.textContent = 'Check Again';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT QUICK ACTIONS ── */\u001b[39m\n\u001b[31mconst HUB_QA_DEFS = [\u001b[39m\n\u001b[31m  { id:'offer',       icon:'📋', label:'New Offer',    accessId:'sales',        fn:() => { window.location.href='/sales/'; } },\u001b[39m\n\u001b[31m  { id:'paytm',       icon:'📱', label:'UPI Pay',       accessId:'finance',      fn:() => { window.location.href='/finance/?qa=upi'; } },\u001b[39m\n\u001b[31m  { id:'expense',     icon:'🧾', label:'Expenses',      accessId:'expense',      fn:() => { window.location.href='/expense/'; } },\u001b[39m\n\u001b[31m  { id:'finance',     icon:'💰', label:'Finance',       accessId:'finance',      fn:() => { window.location.href='/finance/'; } },\u001b[39m\n\u001b[31m  { id:'dispatch',    icon:'🚚', label:'Dispatch',      accessId:'dispatch',     fn:() => { window.location.href='/dispatch/'; } },\u001b[39m\n\u001b[31m  { id:'stores',      icon:'📦', label:'Stores',        accessId:'stores',       fn:() => { window.location.href='/stores/'; } },\u001b[39m\n\u001b[31m  { id:'recce',       icon:'📍', label:'Recce',         accessId:'recce',        fn:() => { window.location.href='/recce/'; } },\u001b[39m\n\u001b[31m  { id:'production',  icon:'⚙️', label:'Production',   accessId:'production',   fn:() => { window.location.href='/production/'; } },\u001b[39m\n\u001b[31m  { id:'tours',       icon:'🗺️', label:'Tour Plan',   accessId:'installation', fn:() => { window.location.href='/tour-planner/'; } },\u001b[39m\n\u001b[31m  { id:'installation',icon:'🔧', label:'Installation',  accessId:'installation', fn:() => { window.location.href='/installation/current/'; } },\u001b[39m\n\u001b[31m  { id:'activity',    icon:'🎯', label:'Activity',      accessId:'activity',     fn:() => { window.location.href='/activity/'; } },\u001b[39m\n\u001b[31m];\u001b[39m\n\u001b[31mconst HUB_QA_DEFAULT = ['offer', 'paytm'];\u001b[39m\n\u001b[31mconst HUB_QA_STORE   = '360lm-hub-qa';\u001b[39m\n\u001b[31mlet   _hubQaTempSel  = [];·\u001b[39m\n\u001b[31mfunction getHubQA() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(HUB_QA_STORE));\u001b[39m\n\u001b[31m    if (Array.isArray(s) && s.length) return s;\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m  return [...HUB_QA_DEFAULT];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHubQA() {\u001b[39m\n\u001b[31m  const ids  = getHubQA();\u001b[39m\n\u001b[31m  const grid = document.getElementById('hub-qa-grid');\u001b[39m\n\u001b[31m  if (!grid) return;\u001b[39m\n\u001b[31m  if (!ids.length) {\u001b[39m\n\u001b[31m    grid.innerHTML = `<div style=\\\"color:var(--muted);font-size:13px;grid-column:1/-1;\u001b[39m\n\u001b[31m      text-align:center;padding:16px\\\">Tap ⚙️ to add quick actions</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  grid.innerHTML = ids.map(id => {\u001b[39m\n\u001b[31m    const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!d) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-tile\\\" onclick=\\\"hubQaRun('${id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction hubQaRun(id) {\u001b[39m\n\u001b[31m  const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m  if (d) d.fn();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openHubQaConfig() {\u001b[39m\n\u001b[31m  _hubQaTempSel = [...getHubQA()];\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const visibleDefs = HUB_QA_DEFS.filter(d => accessIds.includes(d.accessId));\u001b[39m\n\u001b[31m  const list = document.getElementById('hub-qa-config-list');\u001b[39m\n\u001b[31m  list.innerHTML = visibleDefs.map(d => {\u001b[39m\n\u001b[31m    const sel = _hubQaTempSel.includes(d.id);\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-item${sel?' selected':''}\\\" id=\\\"hub-qa-item-${d.id}\\\"\u001b[39m\n\u001b[31m        onclick=\\\"toggleHubQaItem('${d.id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-toggle\\\"></div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleHubQaItem(id) {\u001b[39m\n\u001b[31m  const idx = _hubQaTempSel.indexOf(id);\u001b[39m\n\u001b[31m  if (idx >= 0) {\u001b[39m\n\u001b[31m    _hubQaTempSel.splice(idx, 1);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    if (_hubQaTempSel.length >= 6) {\u001b[39m\n\u001b[31m      document.getElementById('hub-qa-cfg-err').textContent = 'Maximum 6 quick actions';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _hubQaTempSel.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  const el = document.getElementById('hub-qa-item-' + id);\u001b[39m\n\u001b[31m  if (el) el.className = 'hub-qa-item' + (_hubQaTempSel.includes(id) ? ' selected' : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction saveHubQaConfig() {\u001b[39m\n\u001b[31m  localStorage.setItem(HUB_QA_STORE, JSON.stringify(_hubQaTempSel));\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.remove('show');\u001b[39m\n\u001b[31m  renderHubQA();\u001b[39m\n\u001b[31m  toast('✅ Quick actions updated');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── KPI FETCH ── */\u001b[39m\n\u001b[31mfunction _hdr(schema) {\u001b[39m\n\u001b[31m  return { 'Content-Type': 'application/json', 'Accept-Profile': schema };\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _safe(p) { return p.catch(() => null); }·\u001b[39m\n\u001b[31masync function loadKPIs() {\u001b[39m\n\u001b[31m  const mon = firstOfMonth();\u001b[39m\n\u001b[31m  const curMonth = new Date().toISOString().slice(0, 7); // YYYY-MM\u001b[39m\n\u001b[31m  const todayIST = (() => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; })();·\u001b[39m\n\u001b[31m  // Reset sections to loading skeletons before fetching (makes refresh visible)\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  [['kpi-actions',3],['kpi-ops',4],['kpi-prod',3],['kpi-inst',3],['kpi-hr',3]].forEach(([id,n]) => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    if (el) el.innerHTML = skel.repeat(n);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  const [\u001b[39m\n\u001b[31m    sheetsRes, claimsRes, salaryRes,\u001b[39m\n\u001b[31m    cashRes,   dispRes,   recceRes,  prodRes,\u001b[39m\n\u001b[31m    instJobsRes, instCtrsRes, storeItemsRes,\u001b[39m\n\u001b[31m    hrAttRes, hrEmpRes, hrConcernRes, hrSlipsRes\u001b[39m\n\u001b[31m  ] = await Promise.all([\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/sheets?status=in.(submitted,escalated)&select=sheet_id`, { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/approved_pending_reimbursement?select=sheet_id`,         { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_register?status=in.(pending,partial)&month=eq.${curMonth}&select=record_id`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/accounts?account_id=eq.petty-cash&select=current_balance`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/dispatches?status=eq.active&select=dispatch_id`,         { headers: _hdr('dispatch') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/submissions?submitted_at=gte.${mon}&select=sub_id`,      { headers: _hdr('recce') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/orders?status=not.in.(dispatched)&select=order_id,status`, { headers: _hdr('production') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/jobs?select=job_id,status`,                              { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/counters?select=counter_id,status`,                      { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/items?active=eq.true&select=id,current_stock,min_stock`, { headers: _hdr('stores') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance?date=eq.${todayIST}&status=eq.present&select=attendance_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/employee_list?select=id`,                               { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance_concerns?status=eq.open&select=concern_id`,  { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_slips?month=eq.${curMonth}&status=eq.finalized&select=slip_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m  ]);·\u001b[39m\n\u001b[31m  const sheets    = sheetsRes?.ok    ? await sheetsRes.json()    : [];\u001b[39m\n\u001b[31m  const claims    = claimsRes?.ok    ? await claimsRes.json()    : [];\u001b[39m\n\u001b[31m  const salary    = salaryRes?.ok    ? await salaryRes.json()    : [];\u001b[39m\n\u001b[31m  const cashArr   = cashRes?.ok      ? await cashRes.json()      : [];\u001b[39m\n\u001b[31m  const disps     = dispRes?.ok      ? await dispRes.json()      : [];\u001b[39m\n\u001b[31m  const recces    = recceRes?.ok     ? await recceRes.json()     : [];\u001b[39m\n\u001b[31m  const prods     = prodRes?.ok      ? await prodRes.json()      : [];\u001b[39m\n\u001b[31m  const instJobs  = instJobsRes?.ok  ? await instJobsRes.json()  : [];\u001b[39m\n\u001b[31m  const instCtrs  = instCtrsRes?.ok  ? await instCtrsRes.json()  : [];\u001b[39m\n\u001b[31m  const storeItems = storeItemsRes?.ok ? await storeItemsRes.json() : [];\u001b[39m\n\u001b[31m  const hrAtt     = hrAttRes?.ok     ? await hrAttRes.json()     : [];\u001b[39m\n\u001b[31m  const hrEmp     = hrEmpRes?.ok     ? await hrEmpRes.json()     : [];\u001b[39m\n\u001b[31m  const hrConcern = hrConcernRes?.ok ? await hrConcernRes.json() : [];\u001b[39m\n\u001b[31m  const hrSlips   = hrSlipsRes?.ok   ? await hrSlipsRes.json()   : [];·\u001b[39m\n\u001b[31m  const cash = Number(cashArr[0]?.current_balance || 0);·\u001b[39m\n\u001b[31m  const actionsEl = document.getElementById('kpi-actions');\u001b[39m\n\u001b[31m  const opsEl     = document.getElementById('kpi-ops');\u001b[39m\n\u001b[31m  const prodEl    = document.getElementById('kpi-prod');\u001b[39m\n\u001b[31m  const instEl    = document.getElementById('kpi-inst');\u001b[39m\n\u001b[31m  const hrEl      = document.getElementById('kpi-hr');\u001b[39m\n\u001b[31m  if (!actionsEl || !opsEl || !prodEl || !instEl) return;·\u001b[39m\n\u001b[31m  // ── Actions Required ────────────────────────────────────────────\u001b[39m\n\u001b[31m  actionsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${sheets.length ? 'alert' : 'ok'}\\\" data-href=\\\"/expense/?tab=approvals\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${sheets.length ? 'red' : 'green'}\\\">${sheets.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Expense Approvals</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${claims.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=claims\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${claims.length ? 'amber' : 'green'}\\\">${claims.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Claims to Reimburse</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${salary.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=salary\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${salary.length ? 'amber' : 'green'}\\\">${salary.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Salary Due This Month</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Operations ──────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const cashColor = cash < 2000 ? 'red' : cash < 5000 ? 'amber' : 'green';\u001b[39m\n\u001b[31m  const cashCard  = cash < 2000 ? 'alert' : cash < 5000 ? 'warn' : 'ok';\u001b[39m\n\u001b[31m  const lowStock = storeItems.filter(i => i.current_stock < i.min_stock).length;\u001b[39m\n\u001b[31m  opsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${cashCard}\\\" data-href=\\\"/finance/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${cashColor}\\\">${fmtINR(cash)}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">${cash < 0 ? '⚠️ ' : ''}Petty Cash</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/dispatch/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${disps.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Dispatches</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/recce/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${recces.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Recce This Month</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${lowStock > 0 ? 'alert' : 'ok'}\\\" data-href=\\\"/stores/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${lowStock > 0 ? 'red' : 'green'}\\\">${lowStock}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Low Stock Items</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Production ───────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const prodPending = prods.filter(o => o.status === 'pending').length;\u001b[39m\n\u001b[31m  const prodActive  = prods.filter(o => o.status === 'in_production' || o.status === 'quality_check').length;\u001b[39m\n\u001b[31m  const prodReady   = prods.filter(o => o.status === 'ready').length;\u001b[39m\n\u001b[31m  prodEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodPending ? 'warn' : 'ok'}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodPending ? 'amber' : 'green'}\\\">${prodPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Pending Orders</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${prodActive}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">In Production</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodReady ? 'ok' : ''}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodReady ? 'green' : ''}\\\">${prodReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Ready to Dispatch</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Installation ─────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const activeJobs  = instJobs.filter(j => j.status === 'active').length;\u001b[39m\n\u001b[31m  const ctrsPending = instCtrs.filter(c => c.status === 'recce_pending' || c.status === 'mockup_pending' || c.status === 'unclear').length;\u001b[39m\n\u001b[31m  const ctrsReady   = instCtrs.filter(c => c.status === 'ready').length;\u001b[39m\n\u001b[31m  instEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${activeJobs ? 'info' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${activeJobs ? 'blue' : 'green'}\\\">${activeJobs}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Jobs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsPending ? 'warn' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsPending ? 'amber' : 'green'}\\\">${ctrsPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Pending</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsReady ? 'ok' : ''}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsReady ? 'green' : ''}\\\">${ctrsReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Ready</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── HR ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31m  if (hrEl) {\u001b[39m\n\u001b[31m    const totalEmp   = hrEmp.length || 0;\u001b[39m\n\u001b[31m    const presentCnt = hrAtt.length;\u001b[39m\n\u001b[31m    const attPct     = totalEmp > 0 ? Math.round(presentCnt / totalEmp * 100) : 0;\u001b[39m\n\u001b[31m    const attColor   = attPct >= 80 ? 'green' : attPct >= 50 ? 'amber' : 'red';\u001b[39m\n\u001b[31m    const attCard    = attPct >= 80 ? 'ok' : attPct >= 50 ? 'warn' : 'alert';\u001b[39m\n\u001b[31m    const concernCnt = hrConcern.length;\u001b[39m\n\u001b[31m    const slipsDue   = hrSlips.length;\u001b[39m\n\u001b[31m    hrEl.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${attCard}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${attColor}\\\">${attPct}%</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Attendance Today</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${concernCnt ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${concernCnt ? 'amber' : 'green'}\\\">${concernCnt}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Open Concerns</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${slipsDue ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${slipsDue ? 'amber' : 'green'}\\\">${slipsDue}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Slips to Pay</div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Wire tap-to-navigate on all KPI cards\u001b[39m\n\u001b[31m  document.querySelectorAll('.kpi-card[data-href]').forEach(card => {\u001b[39m\n\u001b[31m    card.addEventListener('click', () => window.location.href = card.dataset.href);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction firstOfMonth() {\u001b[39m\n\u001b[31m  const d = new Date();\u001b[39m\n\u001b[31m  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGOUT ── */\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  pinBuffer   = '';\u001b[39m\n\u001b[31m  pendingShare = false;\u001b[39m\n\u001b[31m  _loginAttempts = 0; _lockoutUntil = 0; _loginInFlight = false;\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  document.getElementById('login-name').value = '';\u001b[39m\n\u001b[31m  document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m  showScreen('login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── TEAM PINs ── */\u001b[39m\n\u001b[31masync function enterTeamPins() {\u001b[39m\n\u001b[31m  showScreen('team');\u001b[39m\n\u001b[31m  await loadTeamPins();\u001b[39m\n\u001b[31m  // Show icon admin section for harish/pramod\u001b[39m\n\u001b[31m  const sec = document.getElementById('icon-admin-section');\u001b[39m\n\u001b[31m  if (sec) {\u001b[39m\n\u001b[31m    sec.style.display = MGMT.includes(currentUser.id) ? 'block' : 'none';\u001b[39m\n\u001b[31m    if (MGMT.includes(currentUser.id)) refreshSecretDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── ICON ADMIN ────────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst ICON_SECRET_KEY    = 'lm360_icon_secret';\u001b[39m\n\u001b[31mconst ICON_SECRET_INITIAL = 'nlr8Zsqw6XsD0rEXPQ_RfpYiOeNhH-9VglYKoIsElHE';·\u001b[39m\n\u001b[31mfunction getIconSecret() {\u001b[39m\n\u001b[31m  return localStorage.getItem(ICON_SECRET_KEY) || ICON_SECRET_INITIAL;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction setIconSecret(s) {\u001b[39m\n\u001b[31m  localStorage.setItem(ICON_SECRET_KEY, s);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet secretVisible = false;\u001b[39m\n\u001b[31mfunction refreshSecretDisplay() {\u001b[39m\n\u001b[31m  const el = document.getElementById('icon-secret-display');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  const s = getIconSecret();\u001b[39m\n\u001b[31m  el.textContent = secretVisible ? s : s.slice(0,4) + '••••••••••••••••' + s.slice(-4);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleSecretVisible() {\u001b[39m\n\u001b[31m  secretVisible = !secretVisible;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-secret-show');\u001b[39m\n\u001b[31m  if (btn) btn.textContent = secretVisible ? 'Hide' : 'Show';\u001b[39m\n\u001b[31m  refreshSecretDisplay();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function changeIconSecret() {\u001b[39m\n\u001b[31m  const newSecret = document.getElementById('icon-new-secret').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-secret-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (newSecret.length < 16) { errEl.textContent = 'Minimum 16 characters'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/change-secret', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret(), 'X-New-Secret': newSecret }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Failed');\u001b[39m\n\u001b[31m    setIconSecret(newSecret);\u001b[39m\n\u001b[31m    document.getElementById('icon-new-secret').value = '';\u001b[39m\n\u001b[31m    refreshSecretDisplay();\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = '✓ Secret updated';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mdocument.addEventListener('DOMContentLoaded', () => {\u001b[39m\n\u001b[31m  const fileInput = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  if (fileInput) {\u001b[39m\n\u001b[31m    fileInput.addEventListener('change', function() {\u001b[39m\n\u001b[31m      const wrap = document.getElementById('icon-preview-wrap');\u001b[39m\n\u001b[31m      const img  = document.getElementById('icon-preview-img');\u001b[39m\n\u001b[31m      if (this.files[0]) { img.src = URL.createObjectURL(this.files[0]); wrap.style.display = 'block'; }\u001b[39m\n\u001b[31m      else { wrap.style.display = 'none'; }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function uploadIcon() {\u001b[39m\n\u001b[31m  const pwa   = document.getElementById('icon-pwa-select').value;\u001b[39m\n\u001b[31m  const size  = document.getElementById('icon-size-select').value;\u001b[39m\n\u001b[31m  const input = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-upload-err');\u001b[39m\n\u001b[31m  errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (!input.files[0]) { errEl.textContent = 'Choose an image file first'; return; }\u001b[39m\n\u001b[31m  const formData = new FormData();\u001b[39m\n\u001b[31m  formData.append('pwa', pwa);\u001b[39m\n\u001b[31m  formData.append('size', size);\u001b[39m\n\u001b[31m  formData.append('icon', input.files[0]);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret() },\u001b[39m\n\u001b[31m      body: formData\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Upload failed');\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = `✓ Icon updated for ${pwa} (${size}px). Clear browser cache / reinstall to see new icon.`;\u001b[39m\n\u001b[31m    input.value = '';\u001b[39m\n\u001b[31m    document.getElementById('icon-preview-wrap').style.display = 'none';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadTeamPins() {\u001b[39m\n\u001b[31m  const list = document.getElementById('team-list');\u001b[39m\n\u001b[31m  list.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    list.innerHTML = '';\u001b[39m\n\u001b[31m    emps.forEach(emp => {\u001b[39m\n\u001b[31m      const row = document.createElement('div');\u001b[39m\n\u001b[31m      row.className = 'emp-row';\u001b[39m\n\u001b[31m      row.innerHTML = `\u001b[39m\n\u001b[31m        <div style=\\\"min-width:0;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:15px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.name}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.role}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-secondary btn-sm\\\" data-id=\\\"${emp.id}\\\" data-name=\\\"${emp.name}\\\">Set PIN</button>`;\u001b[39m\n\u001b[31m      row.querySelector('button').addEventListener('click', () => openSetPin(emp.id, emp.name));\u001b[39m\n\u001b[31m      list.appendChild(row);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    list.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openSetPin(targetId, targetName) {\u001b[39m\n\u001b[31m  setPinTarget = targetId;\u001b[39m\n\u001b[31m  document.getElementById('setpin-target-name').textContent = targetName;\u001b[39m\n\u001b[31m  ['setpin-new','setpin-confirm','setpin-auth'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-setpin').classList.add('show');\u001b[39m\n\u001b[31m  setTimeout(() => document.getElementById('setpin-new').focus(), 50);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function doSetPin() {\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('setpin-new').value.trim();\u001b[39m\n\u001b[31m  const conf    = document.getElementById('setpin-confirm').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('setpin-auth').value.trim();\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('setpin-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  if (!newPin || !conf || !authPin) { errEl.textContent = 'All fields are required'; return; }\u001b[39m\n\u001b[31m  if (newPin !== conf)              { errEl.textContent = 'New PINs do not match'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))     { errEl.textContent = 'PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-setpin-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_admin_id:  currentUser.id,\u001b[39m\n\u001b[31m        p_admin_pin: authPin,\u001b[39m\n\u001b[31m        p_target_id: setPinTarget,\u001b[39m\n\u001b[31m        p_new_pin:   newPin,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const err = await resp.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Failed to set PIN';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m    toast('✅ PIN updated for ' + document.getElementById('setpin-target-name').textContent);\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Set PIN';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ═══════════════════════════════════════════\u001b[39m\n\u001b[31m   JOBS — Campaign + Tour management\u001b[39m\n\u001b[31m   Access: harish, pramod (campaigns + tours)\u001b[39m\n\u001b[31m           rakesh (tours only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ */\u001b[39m\n\u001b[31mconst INST_RD = { 'Accept-Profile': 'installation' };\u001b[39m\n\u001b[31mconst INST_WR = { 'Content-Type':'application/json','Accept-Profile':'installation','Content-Profile':'installation' };·\u001b[39m\n\u001b[31mlet _currentCampaignId = null;\u001b[39m\n\u001b[31mlet _allEmployees      = [];\u001b[39m\n\u001b[31mlet _editTourId        = null;  // null = create mode, string = edit mode·\u001b[39m\n\u001b[31m// ── Helpers ──────────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _slugify(s) {\u001b[39m\n\u001b[31m  return s.toUpperCase().trim()\u001b[39m\n\u001b[31m    .replace(/[^A-Z0-9]+/g, '_')\u001b[39m\n\u001b[31m    .replace(/^_|_$/g, '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _buildCampaignId(year, brand, client, campaign, phase) {\u001b[39m\n\u001b[31m  const nn = String(phase || 1).padStart(2, '0');\u001b[39m\n\u001b[31m  return `${year}-${_slugify(brand)}-${_slugify(client)}-${_slugify(campaign)}-${nn}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _campStatusBadge(s) {\u001b[39m\n\u001b[31m  const map = { active:'ok', completed:'', on_hold:'warn' };\u001b[39m\n\u001b[31m  const col = { active:'green', completed:'', on_hold:'amber' };\u001b[39m\n\u001b[31m  return `<span class=\\\"kpi-val ${col[s]||''}\\\" style=\\\"font-size:12px;font-weight:700\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _tourStatusBadge(s) {\u001b[39m\n\u001b[31m  const colors = { active:'var(--success)', planned:'var(--info)', completed:'var(--muted)', cancelled:'var(--danger)' };\u001b[39m\n\u001b[31m  return `<span style=\\\"font-size:11px;font-weight:700;padding:2px 8px;border-radius:10px;\u001b[39m\n\u001b[31m    background:var(--card);color:${colors[s]||'var(--muted)'}\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _canCreateCampaign() { return ['harish','pramod'].includes(currentUser?.id); }\u001b[39m\n\u001b[31mfunction _canCreateTour()     { return ['harish','pramod','rakesh'].includes(currentUser?.id); }·\u001b[39m\n\u001b[31m// ── Jobs screen ───────────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterJobs() {\u001b[39m\n\u001b[31m  showScreen('jobs');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').style.display = _canCreateCampaign() ? '' : 'none';\u001b[39m\n\u001b[31m  await loadJobs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadJobs() {\u001b[39m\n\u001b[31m  const el = document.getElementById('jobs-body');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?order=created_at.desc`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?select=tour_id,campaign_id,status`, { headers: INST_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const campaigns = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours     = tResp.ok ? await tResp.json() : [];·\u001b[39m\n\u001b[31m    if (!campaigns.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty\\\"><div style=\\\"font-size:32px\\\">📋</div><div>No campaigns yet</div></div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    el.innerHTML = campaigns.map(c => {\u001b[39m\n\u001b[31m      const cTours  = tours.filter(t => t.campaign_id === c.campaign_id);\u001b[39m\n\u001b[31m      const active  = cTours.filter(t => t.status === 'active').length;\u001b[39m\n\u001b[31m      const total   = cTours.length;\u001b[39m\n\u001b[31m      const statusColors = { active:'var(--success)', on_hold:'var(--accent2)', completed:'var(--muted)' };\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card tappable\\\" style=\\\"padding:14px\\\" onclick=\\\"enterCampaign('${c.campaign_id}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:flex-start;gap:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:15px;font-weight:700\\\">${c.brand} — ${c.campaign_name}\u001b[39m\n\u001b[31m                ${c.phase > 1 ? `<span style=\\\"font-size:11px;color:var(--muted)\\\"> Phase ${c.phase}</span>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:2px\\\">${c.client} · ${c.year}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:4px\\\">\u001b[39m\n\u001b[31m                ${total} tour${total!==1?'s':''} · ${active} active\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;align-items:flex-end;gap:6px\\\">\u001b[39m\n\u001b[31m              <span style=\\\"font-size:11px;font-weight:700;color:${statusColors[c.status]||'var(--muted)'}\\\">\u001b[39m\n\u001b[31m                ${(c.status||'').replace('_',' ')}\u001b[39m\n\u001b[31m              </span>\u001b[39m\n\u001b[31m              <span style=\\\"font-size:18px;color:var(--muted)\\\">›</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-top:8px;word-break:break-all\\\">${c.campaign_id}</div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign detail ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterCampaign(campaignId) {\u001b[39m\n\u001b[31m  _currentCampaignId = campaignId;\u001b[39m\n\u001b[31m  showScreen('campaign');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').style.display = _canCreateTour() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-sub').textContent   = '';\u001b[39m\n\u001b[31m  await loadCampaign(campaignId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCampaign(campaignId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('campaign-body');\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp, empResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(campaignId)}&order=start_date`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const camps = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours = tResp.ok ? await tResp.json() : [];\u001b[39m\n\u001b[31m    _allEmployees = empResp.ok ? await empResp.json() : [];·\u001b[39m\n\u001b[31m    if (!camps.length) { body.innerHTML = '<div class=\\\"empty\\\">Campaign not found</div>'; return; }\u001b[39m\n\u001b[31m    const c = camps[0];·\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-title').textContent = `${c.brand} — ${c.campaign_name}`;\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-sub').textContent   = `${c.client} · ${c.year}${c.phase>1?' · Phase '+c.phase:''}`;·\u001b[39m\n\u001b[31m    const empMap = {};\u001b[39m\n\u001b[31m    _allEmployees.forEach(e => empMap[e.id] = e.name);·\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"padding:12px 14px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Campaign ID</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:10px;color:var(--muted);word-break:break-all;text-align:right;max-width:65%\\\">${c.campaign_id}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Client</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:13px;font-weight:600\\\">${c.client}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${_canCreateCampaign() ? `\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div class=\\\"section-title\\\" style=\\\"margin-bottom:6px\\\">Campaign Status</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m            ${['active','on_hold','completed'].map(s =>\u001b[39m\n\u001b[31m              `<button class=\\\"status-chip${c.status===s?' sel':''}\\\" onclick=\\\"updateCampaignStatus('${c.campaign_id}','${s}')\\\">\u001b[39m\n\u001b[31m                ${s.replace('_',' ')}\u001b[39m\n\u001b[31m              </button>`).join('')}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>` : ''}\u001b[39m\n\u001b[31m      </div>·\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:4px\\\">Tours (${tours.length})</div>\u001b[39m\n\u001b[31m      ${tours.length === 0 ? `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\">\u001b[39m\n\u001b[31m        <div>No tours yet</div>\u001b[39m\n\u001b[31m        ${_canCreateTour() ? '<div style=\\\"font-size:12px\\\">Tap ＋ to add the first tour</div>' : ''}\u001b[39m\n\u001b[31m      </div>` :\u001b[39m\n\u001b[31m      tours.map(t => {\u001b[39m\n\u001b[31m        const empNames = (t.assigned_employees || []).map(id => empMap[id] || id).join(', ') || 'No one assigned';\u001b[39m\n\u001b[31m        const dates    = t.start_date ? `${t.start_date} → ${t.end_date||'?'}` : 'Dates not set';\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m          <div class=\\\"card\\\" style=\\\"padding:14px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;align-items:flex-start;gap:8px\\\">\u001b[39m\n\u001b[31m              <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m                <div style=\\\"font-size:14px;font-weight:700\\\">${t.title || t.tour_id}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">📅 ${dates}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">👥 ${empNames}</div>\u001b[39m\n\u001b[31m                ${t.notes ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">📝 ${t.notes}</div>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              ${_tourStatusBadge(t.status)}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            ${_canCreateTour() ? `\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;gap:6px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m              ${['planned','active','completed'].map(s =>\u001b[39m\n\u001b[31m                `<button class=\\\"status-chip${t.status===s?' sel':''}\\\"\u001b[39m\n\u001b[31m                  onclick=\\\"updateTourStatus('${t.tour_id}','${s}')\\\">${s}</button>`).join('')}\u001b[39m\n\u001b[31m              <button class=\\\"status-chip\\\" onclick=\\\"openEditTour('${t.tour_id}')\\\"\u001b[39m\n\u001b[31m                style=\\\"margin-left:auto;background:var(--surface);border-color:#3a3a6e;color:#8888ff\\\">\u001b[39m\n\u001b[31m                ✏️ Edit\u001b[39m\n\u001b[31m              </button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m            <div style=\\\"font-size:10px;color:var(--muted);margin-top:6px\\\">${t.tour_id}</div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m      <div style=\\\"height:20px\\\"></div>`;\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateTourStatus(tourId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Tour marked ${status}`);\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateCampaignStatus(campaignId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Campaign marked ${status.replace('_',' ')}`);\u001b[39m\n\u001b[31m    loadCampaign(campaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Campaign modal ────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewCampaign() {\u001b[39m\n\u001b[31m  if (!_canCreateCampaign()) { toast('Only Harish or Pramod can create campaigns'); return; }\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('nc-year').value    = new Date().getFullYear();\u001b[39m\n\u001b[31m  document.getElementById('nc-phase').value   = '1';\u001b[39m\n\u001b[31m  document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  document.getElementById('nc-err').textContent     = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-new-campaign').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateCampaignPreview() {\u001b[39m\n\u001b[31m  const year  = document.getElementById('nc-year').value;\u001b[39m\n\u001b[31m  const brand = document.getElementById('nc-brand').value;\u001b[39m\n\u001b[31m  const clt   = document.getElementById('nc-client').value;\u001b[39m\n\u001b[31m  const camp  = document.getElementById('nc-campaign').value;\u001b[39m\n\u001b[31m  const phase = document.getElementById('nc-phase').value;\u001b[39m\n\u001b[31m  if (brand && clt && camp) {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = _buildCampaignId(year, brand, clt, camp, phase);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewCampaign() {\u001b[39m\n\u001b[31m  const year   = Number(document.getElementById('nc-year').value);\u001b[39m\n\u001b[31m  const brand  = document.getElementById('nc-brand').value.trim();\u001b[39m\n\u001b[31m  const client = document.getElementById('nc-client').value.trim();\u001b[39m\n\u001b[31m  const cname  = document.getElementById('nc-campaign').value.trim();\u001b[39m\n\u001b[31m  const phase  = Number(document.getElementById('nc-phase').value) || 1;\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('nc-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const brandEl = document.getElementById('nc-brand');\u001b[39m\n\u001b[31m  const clientEl = document.getElementById('nc-client');\u001b[39m\n\u001b[31m  const cnameEl = document.getElementById('nc-campaign');\u001b[39m\n\u001b[31m  [brandEl, clientEl, cnameEl].forEach(el => el.style.borderColor = '');\u001b[39m\n\u001b[31m  let _missingField = false;\u001b[39m\n\u001b[31m  if (!brand)  { brandEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!client) { clientEl.style.borderColor = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!cname)  { cnameEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (_missingField) { errEl.textContent = 'Brand, Client and Campaign Name are required'; return; }\u001b[39m\n\u001b[31m  const campaignId = _buildCampaignId(year, brand, client, cname, phase);·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nc-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/campaigns`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ campaign_id:campaignId, year, brand, client, campaign_name:cname, phase, created_by:currentUser.id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || 'Failed to create';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show');\u001b[39m\n\u001b[31m    toast(`✅ Campaign created`);\u001b[39m\n\u001b[31m    loadJobs();\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Create'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Tour modal ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _buildEmpCheckboxes(assigned = []) {\u001b[39m\n\u001b[31m  return _allEmployees.map(e =>\u001b[39m\n\u001b[31m    `<label style=\\\"display:flex;align-items:center;gap:10px;padding:8px 10px;background:var(--card);\u001b[39m\n\u001b[31m      border:1px solid var(--border);border-radius:8px;cursor:pointer\\\">\u001b[39m\n\u001b[31m      <input type=\\\"checkbox\\\" value=\\\"${e.id}\\\" ${assigned.includes(e.id)?'checked':''} style=\\\"width:16px;height:16px;accent-color:var(--accent)\\\">\u001b[39m\n\u001b[31m      <span style=\\\"font-size:14px;font-weight:600\\\">${e.name}</span>\u001b[39m\n\u001b[31m      <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${e.role}</span>\u001b[39m\n\u001b[31m    </label>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openNewTour() {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = null;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '🗺️ New Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Create Tour';\u001b[39m\n\u001b[31m  document.getElementById('nt-title').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-start').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-end').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-notes').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(_currentCampaignId)}&select=tour_id`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const existing = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    const nn = String(existing.length + 1).padStart(2, '0');\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T${nn}`;\u001b[39m\n\u001b[31m  } catch(_) {\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T01`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (_allEmployees.length)\u001b[39m\n\u001b[31m    document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes();·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEditTour(tourId) {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = tourId;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '✏️ Edit Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Save Changes';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const tours = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    if (!tours.length) { toast('Tour not found'); return; }\u001b[39m\n\u001b[31m    const t = tours[0];\u001b[39m\n\u001b[31m    document.getElementById('nt-title').value = t.title || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-start').value = t.start_date || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-end').value   = t.end_date   || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-notes').value = t.notes      || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = tourId;\u001b[39m\n\u001b[31m    if (_allEmployees.length)\u001b[39m\n\u001b[31m      document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes(t.assigned_employees || []);\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to load tour'); return; }·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewTour() {\u001b[39m\n\u001b[31m  const title = document.getElementById('nt-title').value.trim();\u001b[39m\n\u001b[31m  const start = document.getElementById('nt-start').value || null;\u001b[39m\n\u001b[31m  const end   = document.getElementById('nt-end').value   || null;\u001b[39m\n\u001b[31m  const notes = document.getElementById('nt-notes').value.trim() || null;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('nt-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const checked = [...document.querySelectorAll('#nt-employees input:checked')].map(cb => cb.value);\u001b[39m\n\u001b[31m  if (!checked.length) { errEl.textContent = 'Assign at least one employee'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nt-save');\u001b[39m\n\u001b[31m  const wasEdit = !!_editTourId;\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = wasEdit ? 'Saving…' : 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editTourId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(_editTourId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      const tourId = document.getElementById('nt-id-preview').textContent;\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          tour_id: tourId, campaign_id: _currentCampaignId,\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes, status: 'planned',\u001b[39m\n\u001b[31m          created_by: currentUser.id,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || (_editTourId ? 'Failed to save' : 'Failed to create');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show');\u001b[39m\n\u001b[31m    toast(_editTourId ? '✅ Tour updated' : '✅ Tour created');\u001b[39m\n\u001b[31m    _editTourId = null;\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = wasEdit ? 'Save Changes' : 'Create Tour';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── EVENT WIRING ── */\u001b[39m\n\u001b[31mfunction wireEvents() {\u001b[39m\n\u001b[31m  // Jobs screens\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-back').addEventListener('click',     () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-refresh').addEventListener('click',  loadJobs);\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').addEventListener('click',  openNewCampaign);\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-back').addEventListener('click', () => showScreen('jobs'));\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-refresh').addEventListener('click', () => loadCampaign(_currentCampaignId));\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').addEventListener('click',      openNewTour);·\u001b[39m\n\u001b[31m  // New Campaign modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-save').addEventListener('click', saveNewCampaign);\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign','nc-year','nc-phase'].forEach(id =>\u001b[39m\n\u001b[31m    document.getElementById(id).addEventListener('input', updateCampaignPreview));·\u001b[39m\n\u001b[31m  // New Tour modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').addEventListener('click', saveNewTour);·\u001b[39m\n\u001b[31m  document.getElementById('btn-team-back').addEventListener('click', () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-team-refresh').addEventListener('click', loadTeamPins);\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-cancel').addEventListener('click', () => {\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-save').addEventListener('click', doSetPin);·\u001b[39m\n\u001b[31m  // Set PIN — progressive field reveal\u001b[39m\n\u001b[31m  document.getElementById('setpin-new').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const val = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const showConf = val.length === 4;\u001b[39m\n\u001b[31m    document.getElementById('setpin-confirm-group').style.display = showConf ? '' : 'none';\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m    if (!showConf) { document.getElementById('setpin-confirm').value = ''; document.getElementById('setpin-auth').value = ''; }\u001b[39m\n\u001b[31m    if (showConf) setTimeout(() => document.getElementById('setpin-confirm').focus(), 20);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const newVal = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const conf   = document.getElementById('setpin-confirm').value;\u001b[39m\n\u001b[31m    const showAuth = conf.length === 4 && conf === newVal;\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = showAuth ? '' : 'none';\u001b[39m\n\u001b[31m    if (!showAuth) document.getElementById('setpin-auth').value = '';\u001b[39m\n\u001b[31m    if (showAuth) setTimeout(() => document.getElementById('setpin-auth').focus(), 20);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // Hub AI config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-save').addEventListener('click',   saveHubAiConfig);\u001b[39m\n\u001b[31m  document.getElementById('btn-check-keys').addEventListener('click',    checkApiKeyStatus);·\u001b[39m\n\u001b[31m  // Hub QA config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-qa').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-save').addEventListener('click', saveHubQaConfig);·\u001b[39m\n\u001b[31m  // Share picker\u001b[39m\n\u001b[31m  document.getElementById('share-to-expense').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/expense/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-recce').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/recce/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-installation').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/installation/current/?share=1';\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-04: employee dropdown change resets PIN buffer and lockout\u001b[39m\n\u001b[31m  document.getElementById('login-name').addEventListener('change', () => {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-07: ESC key closes any open modal\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (e.key === 'Escape') {\u001b[39m\n\u001b[31m      document.querySelectorAll('.modal-overlay.show').forEach(m => m.classList.remove('show'));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-11: keyboard digit/backspace input on login screen\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (!document.getElementById('screen-login').classList.contains('active')) return;\u001b[39m\n\u001b[31m    if (e.key >= '0' && e.key <= '9') { handlePinKey(e.key); }\u001b[39m\n\u001b[31m    else if (e.key === 'Backspace') { e.preventDefault(); handlePinKey('B'); }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-02/B-08: re-check session TTL when user returns to the tab\u001b[39m\n\u001b[31m  document.addEventListener('visibilitychange', () => {\u001b[39m\n\u001b[31m    if (document.visibilityState === 'visible' && currentUser) {\u001b[39m\n\u001b[31m      if (!loadSession()) doLogout();\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── BOOT ── */\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  wireEvents();·\u001b[39m\n\u001b[31m  if (location.search.includes('share=1')) {\u001b[39m\n\u001b[31m    pendingShare = true;\u001b[39m\n\u001b[31m    history.replaceState({}, '', '/hub/');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Always populate the login dropdown (needed if user logs out later)\u001b[39m\n\u001b[31m  initLogin(); // fire-and-forget — does not block home screen path·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess) {\u001b[39m\n\u001b[31m    currentUser = { id: sess.empId, name: sess.name, role: sess.role };\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  setTimeout(() => showScreen('login'), 1500);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SERVICE WORKER: install + update ── */\u001b[39m\n\u001b[31mif ('serviceWorker' in navigator) {\u001b[39m\n\u001b[31m  navigator.serviceWorker.register('/hub/sw.js').then(reg => {·\u001b[39m\n\u001b[31m    // Install banner: show when browser signals app is installable\u001b[39m\n\u001b[31m    let deferredInstall = null;\u001b[39m\n\u001b[31m    window.addEventListener('beforeinstallprompt', e => {\u001b[39m\n\u001b[31m      e.preventDefault();\u001b[39m\n\u001b[31m      deferredInstall = e;\u001b[39m\n\u001b[31m      if (!window.matchMedia('(display-mode: standalone)').matches) {\u001b[39m\n\u001b[31m        const delay = document.getElementById('update-banner').classList.contains('show') ? 3500 : 0;\u001b[39m\n\u001b[31m        setTimeout(() => document.getElementById('install-banner').classList.add('show'), delay);\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    window.addEventListener('appinstalled', () => {\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install').addEventListener('click', async () => {\u001b[39m\n\u001b[31m      if (!deferredInstall) return;\u001b[39m\n\u001b[31m      deferredInstall.prompt();\u001b[39m\n\u001b[31m      await deferredInstall.userChoice;\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m    // Update banner: show when a new SW version is waiting to activate\u001b[39m\n\u001b[31m    function showUpdateBanner() {\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.add('show');\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (reg.waiting) showUpdateBanner();·\u001b[39m\n\u001b[31m    reg.addEventListener('updatefound', () => {\u001b[39m\n\u001b[31m      const newSW = reg.installing;\u001b[39m\n\u001b[31m      newSW.addEventListener('statechange', () => {\u001b[39m\n\u001b[31m        if (newSW.state === 'installed' && navigator.serviceWorker.controller) {\u001b[39m\n\u001b[31m          showUpdateBanner();\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    document.getElementById('btn-update').addEventListener('click', () => {\u001b[39m\n\u001b[31m      if (reg.waiting) {\u001b[39m\n\u001b[31m        reg.waiting.postMessage({ type: 'SKIP_WAITING' });\u001b[39m\n\u001b[31m        navigator.serviceWorker.addEventListener('controllerchange', () => {\u001b[39m\n\u001b[31m          window.location.reload();\u001b[39m\n\u001b[31m        }, { once: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-update-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m  }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mboot();\u001b[39m\n\u001b[31m</script>··\u001b[39m\n\u001b[31m</body></html>\"\u001b[39m\n\n  329 |     await page.goto(BASE_URL);\n  330 |     const src = await page.content();\n> 331 |     expect(src).toContain('recce-v10');\n      |                 ^\n  332 |     expect(src).not.toContain('recce-v9');\n  333 |   });\n  334 | });\n    at /var/www/360lm/tests/recce.spec.js:331:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:53.195Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-5e896-tos-SW-version-is-recce-v10-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-5e896-tos-SW-version-is-recce-v10-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 331
                      }
                    },
                    {
                      "workerIndex": 4,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 1096,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"recce-v10\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html><html lang=\\\"en\\\"><head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#FF6B35\\\">\u001b[39m\n\u001b[31m<meta name=\\\"mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-status-bar-style\\\" content=\\\"black-translucent\\\">\u001b[39m\n\u001b[31m<title>360LM</title>\u001b[39m\n\u001b[31m<meta name=\\\"description\\\" content=\\\"360LM Operations Hub — employee tools\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/hub/manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"icon\\\" href=\\\"/hub/icons/icon-192.png\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--surface:#161616;--card:#1e1e1e;--border:#2a2a2a;\u001b[39m\n\u001b[31m  --accent:#FF6B35;--accent2:#FFB800;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --danger:#e74c3c;--success:#2ecc71;--info:#3498db;\u001b[39m\n\u001b[31m  --radius:12px;--font:'Segoe UI',system-ui,sans-serif;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:var(--font);min-height:100vh;overflow-x:hidden}\u001b[39m\n\u001b[31m.screen{display:none;min-height:100vh;flex-direction:column}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── HEADER ── */\u001b[39m\n\u001b[31m.header{background:var(--surface);border-bottom:1px solid var(--border);padding:14px 16px;\u001b[39m\n\u001b[31m  display:flex;align-items:center;gap:12px;position:sticky;top:0;z-index:10;flex-shrink:0}\u001b[39m\n\u001b[31m.header h1{font-size:18px;font-weight:700;color:var(--accent);flex:1}\u001b[39m\n\u001b[31m.header .sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.btn-icon{background:none;border:none;color:var(--muted);font-size:22px;cursor:pointer;\u001b[39m\n\u001b[31m  padding:4px;border-radius:8px;line-height:1;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m.btn-icon:active{background:var(--border)}·\u001b[39m\n\u001b[31m/* ── BODY ── */\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:16px}·\u001b[39m\n\u001b[31m/* ── SPLASH ── */\u001b[39m\n\u001b[31m#screen-splash{align-items:center;justify-content:center;gap:8px}\u001b[39m\n\u001b[31m.splash-logo{font-size:72px;font-weight:900;color:var(--accent);letter-spacing:-2px;line-height:1}\u001b[39m\n\u001b[31m.splash-sub{font-size:15px;color:var(--muted);letter-spacing:2px;text-transform:uppercase}\u001b[39m\n\u001b[31m.splash-dot{width:8px;height:8px;border-radius:50%;background:var(--accent);\u001b[39m\n\u001b[31m  animation:pulse 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes pulse{0%,100%{opacity:.3;transform:scale(.8)}50%{opacity:1;transform:scale(1)}}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  min-height:100dvh;padding:24px;gap:20px;overflow-y:auto}\u001b[39m\n\u001b[31m.login-logo{font-size:44px;font-weight:900;color:var(--accent);letter-spacing:-1px}\u001b[39m\n\u001b[31m.login-sub{font-size:13px;color:var(--muted);text-transform:uppercase;letter-spacing:2px}\u001b[39m\n\u001b[31m.form-group{width:100%;max-width:360px;display:flex;flex-direction:column;gap:8px}\u001b[39m\n\u001b[31m.form-group label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px}\u001b[39m\n\u001b[31mselect{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31mselect:focus{border-color:var(--accent)}·\u001b[39m\n\u001b[31m/* ── PIN PAD ── */\u001b[39m\n\u001b[31m.pin-display{display:flex;gap:14px;justify-content:center;margin:4px 0}\u001b[39m\n\u001b[31m.pin-dot{width:16px;height:16px;border-radius:50%;border:2px solid var(--muted);background:transparent;transition:.15s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.pin-pad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;max-width:260px;margin:0 auto;width:100%}\u001b[39m\n\u001b[31m.pin-key{background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  font-size:22px;font-weight:600;padding:16px;border-radius:var(--radius);cursor:pointer;\u001b[39m\n\u001b[31m  text-align:center;line-height:1;user-select:none}\u001b[39m\n\u001b[31m.pin-key:active{background:var(--border)}\u001b[39m\n\u001b[31m.pin-key.clr{color:var(--danger);font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.pin-key.back{color:var(--accent2)}\u001b[39m\n\u001b[31m.login-err{font-size:13px;color:var(--danger);text-align:center;min-height:18px}\u001b[39m\n\u001b[31m@keyframes shake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-6px)}40%,80%{transform:translateX(6px)}}\u001b[39m\n\u001b[31m.pin-display.shake{animation:shake .4s ease}·\u001b[39m\n\u001b[31m/* ── KPI CARDS ── */\u001b[39m\n\u001b[31m.kpi-row{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\u001b[39m\n\u001b[31m.kpi-4col{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m#home-body{gap:10px}\u001b[39m\n\u001b[31m.kpi-card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:10px 8px;text-align:center;border-left:3px solid var(--accent);cursor:default}\u001b[39m\n\u001b[31m.kpi-card.alert{border-left-color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-card.warn{border-left-color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-card.ok{border-left-color:var(--success)}\u001b[39m\n\u001b[31m.kpi-card.info{border-left-color:var(--info)}\u001b[39m\n\u001b[31m.kpi-val{font-size:18px;font-weight:900;color:var(--accent);line-height:1.2}\u001b[39m\n\u001b[31m.kpi-val.red{color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-val.amber{color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-val.green{color:var(--success)}\u001b[39m\n\u001b[31m.kpi-val.blue{color:var(--info)}\u001b[39m\n\u001b[31m.kpi-lbl{font-size:10px;color:var(--muted);margin-top:4px;text-transform:uppercase;letter-spacing:.4px;line-height:1.3}\u001b[39m\n\u001b[31m.kpi-skel{background:var(--border);border-radius:4px;height:24px;margin-bottom:4px;\u001b[39m\n\u001b[31m  animation:shimmer 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes shimmer{0%,100%{opacity:.5}50%{opacity:1}}·\u001b[39m\n\u001b[31m/* ── SECTION TITLE ── */\u001b[39m\n\u001b[31m.section-title{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.8px}·\u001b[39m\n\u001b[31m/* ── MGMT HOME: MAIN TILES ── */\u001b[39m\n\u001b[31m.hub-main-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px}\u001b[39m\n\u001b[31m.hub-main-tile{background:var(--card);border:2px solid var(--border);border-radius:18px;\u001b[39m\n\u001b[31m  padding:36px 12px 28px;text-align:center;cursor:pointer;transition:background .15s,border-color .15s}\u001b[39m\n\u001b[31m.hub-main-tile:active{background:#1a2a1a;border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-main-icon{font-size:40px;line-height:1;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-main-label{font-size:15px;font-weight:800;color:var(--text)}\u001b[39m\n\u001b[31m/* ── MGMT HOME: QUICK ACTIONS ── */\u001b[39m\n\u001b[31m.hub-qa-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-qa-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(88px,1fr));gap:10px}\u001b[39m\n\u001b[31m.hub-qa-tile{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px 8px;text-align:center;cursor:pointer;display:flex;flex-direction:column;\u001b[39m\n\u001b[31m  align-items:center;gap:6px;transition:background .15s}\u001b[39m\n\u001b[31m.hub-qa-tile:active{background:var(--border)}\u001b[39m\n\u001b[31m.hub-qa-icon{font-size:26px;line-height:1}\u001b[39m\n\u001b[31m.hub-qa-label{font-size:11px;font-weight:700;color:var(--text);line-height:1.3}\u001b[39m\n\u001b[31m.hub-qa-item{display:flex;align-items:center;gap:12px;padding:10px 0;\u001b[39m\n\u001b[31m  border-bottom:1px solid var(--border);cursor:pointer}\u001b[39m\n\u001b[31m.hub-qa-item:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.hub-qa-item-icon{font-size:22px;width:32px;text-align:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item-label{flex:1;font-size:14px;font-weight:600}\u001b[39m\n\u001b[31m.hub-qa-item-toggle{width:20px;height:20px;border-radius:4px;border:2px solid var(--border);\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle::after{content:'✓';color:#fff;font-weight:700;font-size:12px}·\u001b[39m\n\u001b[31m/* ── TILE GRID ── */\u001b[39m\n\u001b[31m.tile-grid{display:grid;gap:12px}\u001b[39m\n\u001b[31m.tile-grid.mgmt{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m.tile-grid.field{grid-template-columns:repeat(2,1fr)}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:18px 8px;display:flex;flex-direction:column;align-items:center;gap:8px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s;position:relative}\u001b[39m\n\u001b[31m.tile:active{background:var(--border);transform:scale(.97)}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px;line-height:1}\u001b[39m\n\u001b[31m.tile-label{font-size:11px;font-weight:700;color:var(--text);text-align:center;text-transform:uppercase;letter-spacing:.4px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-icon{font-size:24px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-label{font-size:10px}\u001b[39m\n\u001b[31m.tile.coming{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.tile.coming:active{transform:none;background:var(--card)}\u001b[39m\n\u001b[31m.soon-badge{position:absolute;top:6px;right:6px;background:var(--accent2);color:#000;\u001b[39m\n\u001b[31m  font-size:8px;font-weight:800;padding:2px 5px;border-radius:20px;text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── USER BADGE ── */\u001b[39m\n\u001b[31m.user-badge{display:flex;align-items:center;gap:12px}\u001b[39m\n\u001b[31m.avatar{width:44px;height:44px;background:var(--accent);border-radius:50%;\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:800;\u001b[39m\n\u001b[31m  color:#fff;flex-shrink:0;text-transform:uppercase}\u001b[39m\n\u001b[31m.user-name{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.user-role{font-size:12px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── TEAM PINS ── */\u001b[39m\n\u001b[31m.emp-row{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px}\u001b[39m\n\u001b[31m.btn{display:flex;align-items:center;justify-content:center;gap:6px;border:none;\u001b[39m\n\u001b[31m  border-radius:var(--radius);font-size:15px;font-weight:700;padding:12px 18px;\u001b[39m\n\u001b[31m  cursor:pointer;width:100%;transition:.15s;font-family:var(--font)}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--card);border:1px solid var(--border);color:var(--text)}\u001b[39m\n\u001b[31m.btn-sm{padding:7px 12px;font-size:12px;font-weight:700;width:auto;flex-shrink:0}\u001b[39m\n\u001b[31m.info-note{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;font-size:13px;color:var(--muted);line-height:1.5}·\u001b[39m\n\u001b[31m/* ── MODAL ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:200;\u001b[39m\n\u001b[31m  align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;\u001b[39m\n\u001b[31m  padding:24px;width:100%;max-width:480px;display:flex;flex-direction:column;gap:14px;\u001b[39m\n\u001b[31m  max-height:90dvh;overflow-y:auto;-webkit-overflow-scrolling:touch}\u001b[39m\n\u001b[31m.modal-title{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.modal-box input[type=password]{\u001b[39m\n\u001b[31m  width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31m.modal-box input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.action-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px}·\u001b[39m\n\u001b[31m/* ── SHARE PICKER ── */\u001b[39m\n\u001b[31m#screen-share .body{align-items:center;justify-content:center;gap:24px}\u001b[39m\n\u001b[31m.share-prompt{font-size:15px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.share-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;width:100%;max-width:360px}\u001b[39m\n\u001b[31m.share-tile{background:var(--card);border:2px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:24px 8px;display:flex;flex-direction:column;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s}\u001b[39m\n\u001b[31m.share-tile:active{background:var(--border);transform:scale(.97);border-color:var(--accent)}\u001b[39m\n\u001b[31m.share-tile-icon{font-size:36px;line-height:1}\u001b[39m\n\u001b[31m.share-tile-label{font-size:12px;font-weight:700;color:var(--text);text-align:center;\u001b[39m\n\u001b[31m  text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── INSTALL BANNER ── */\u001b[39m\n\u001b[31m#install-banner{display:none;position:fixed;bottom:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:linear-gradient(135deg,#1a1a1a,#0d2010);\u001b[39m\n\u001b[31m  border-top:1px solid var(--accent);padding:14px 16px;\u001b[39m\n\u001b[31m  align-items:center;gap:12px;box-shadow:0 -4px 24px rgba(0,0,0,.7)}\u001b[39m\n\u001b[31m#install-banner.show{display:flex}\u001b[39m\n\u001b[31m#install-banner .ib-icon{font-size:30px;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-text{flex:1}\u001b[39m\n\u001b[31m#install-banner .ib-title{font-size:14px;font-weight:700;color:var(--text)}\u001b[39m\n\u001b[31m#install-banner .ib-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#install-banner .ib-btn{background:var(--accent);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-close{background:none;border:none;color:var(--muted);font-size:20px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── UPDATE BANNER ── */\u001b[39m\n\u001b[31m#update-banner{display:none;position:fixed;top:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:#1a2a1a;border-bottom:2px solid var(--success);\u001b[39m\n\u001b[31m  padding:12px 16px;align-items:center;gap:12px}\u001b[39m\n\u001b[31m#update-banner.show{display:flex}\u001b[39m\n\u001b[31m#update-banner .ub-text{flex:1;font-size:14px;font-weight:600;color:var(--text)}\u001b[39m\n\u001b[31m#update-banner .ub-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#update-banner .ub-btn{background:var(--success);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:8px 14px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#update-banner .ub-close{background:none;border:none;color:var(--muted);font-size:18px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#222;color:#fff;padding:10px 20px;border-radius:20px;font-size:14px;font-weight:600;\u001b[39m\n\u001b[31m  opacity:0;transition:.3s;pointer-events:none;white-space:nowrap;z-index:500}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  padding:40px;color:var(--muted);font-size:14px;gap:12px}\u001b[39m\n\u001b[31m.spinner{width:36px;height:36px;border:3px solid var(--border);border-top-color:var(--accent);\u001b[39m\n\u001b[31m  border-radius:50%;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* ── CARD (jobs / campaign screens) ── */\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius)}\u001b[39m\n\u001b[31m.card.tappable:active{border-color:var(--accent);opacity:.85}·\u001b[39m\n\u001b[31m/* ── STATUS CHIPS (tour / campaign) ── */\u001b[39m\n\u001b[31m.status-chip{font-size:11px;padding:4px 11px;border-radius:8px;cursor:pointer;\u001b[39m\n\u001b[31m  background:var(--card);color:var(--text);border:1px solid var(--border);font-weight:600;\u001b[39m\n\u001b[31m  font-family:var(--font);transition:.15s}\u001b[39m\n\u001b[31m.status-chip.sel{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.status-chip:active{opacity:.8}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SPLASH\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-splash\\\" class=\\\"screen active\\\">\u001b[39m\n\u001b[31m  <div class=\\\"splash-logo\\\">360LM</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-sub\\\">Operations Hub</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-dot\\\" style=\\\"margin-top:16px\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     LOGIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-login\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-wrap\\\">\u001b[39m\n\u001b[31m    <div class=\\\"login-logo\\\">360LM</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">Operations Hub</div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Select Your Name</label>\u001b[39m\n\u001b[31m      <select id=\\\"login-name\\\"><option value=\\\"\\\">— Choose employee —</option><option value=\\\"amit\\\">Amit</option><option value=\\\"ashok\\\">Ashok</option><option value=\\\"gurpreet\\\">Gurpreet</option><option value=\\\"harish\\\">Harish Kumar Lal</option><option value=\\\"mukesh\\\">Mukesh</option><option value=\\\"narender\\\">Narender</option><option value=\\\"pramod\\\">Pramod Narang</option><option value=\\\"rakesh\\\">Rakesh</option><option value=\\\"sachin\\\">Sachin</option><option value=\\\"shambhu\\\">Shambhu</option><option value=\\\"umashankar\\\">Umashankar</option></select>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m      <label style=\\\"text-align:center\\\">Enter PIN</label>\u001b[39m\n\u001b[31m      <div class=\\\"pin-display\\\" id=\\\"pin-display\\\">\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd0\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd1\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd2\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd3\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"login-err\\\" id=\\\"login-err\\\"></div>\u001b[39m\n\u001b[31m      <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-1\\\" data-k=\\\"1\\\" aria-label=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-2\\\" data-k=\\\"2\\\" aria-label=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-3\\\" data-k=\\\"3\\\" aria-label=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-4\\\" data-k=\\\"4\\\" aria-label=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-5\\\" data-k=\\\"5\\\" aria-label=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-6\\\" data-k=\\\"6\\\" aria-label=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-7\\\" data-k=\\\"7\\\" aria-label=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-8\\\" data-k=\\\"8\\\" aria-label=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-9\\\" data-k=\\\"9\\\" aria-label=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key clr\\\" id=\\\"pk-C\\\" data-k=\\\"C\\\" aria-label=\\\"Clear\\\">CLR</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-0\\\" data-k=\\\"0\\\" aria-label=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key back\\\" id=\\\"pk-B\\\" data-k=\\\"B\\\" aria-label=\\\"Backspace\\\">⌫</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     HOME  (built dynamically)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\" id=\\\"home-header\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"home-body\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SHARE PICKER\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-share\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>Share Image To</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">Choose where to send this photo</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"share-prompt\\\">Which tool should receive this image?</div>\u001b[39m\n\u001b[31m    <div class=\\\"share-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-expense\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🧾</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Expenses</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-recce\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">📍</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Recce</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-installation\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🔧</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Installation</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     TEAM PINs  (admin only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-team\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-back\\\" style=\\\"font-size:20px\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Team PINs</h1><div class=\\\"sub\\\">Set employee login PINs</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-refresh\\\" title=\\\"Refresh\\\">🔄</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"info-note\\\">PINs are stored encrypted — current PIN cannot be viewed. You can set a new PIN for any employee below.</div>\u001b[39m\n\u001b[31m    <div id=\\\"team-list\\\"></div>·\u001b[39m\n\u001b[31m    <!-- Icon Admin — Harish & Pramod only -->\u001b[39m\n\u001b[31m    <div id=\\\"icon-admin-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:8px\\\">🎨 PWA Icons (Admin)</div>\u001b[39m\n\u001b[31m      <div class=\\\"info-note\\\" style=\\\"margin-bottom:10px\\\">Upload a new icon for any PWA. Upload both 192×192 and 512×512 sizes. PNG/JPG/WEBP accepted — auto-resized on server.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;flex-direction:column;gap:10px\\\">·\u001b[39m\n\u001b[31m        <!-- Secret management -->\u001b[39m\n\u001b[31m        <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:12px;padding:14px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px\\\">🔑 Admin Secret</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:center;gap:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"font-family:monospace;font-size:13px;color:var(--accent);background:var(--bg);\u001b[39m\n\u001b[31m              border-radius:6px;padding:8px 12px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\" id=\\\"icon-secret-display\\\">—</div>\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"toggleSecretVisible()\\\" id=\\\"btn-secret-show\\\">Show</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:10px\\\">Change secret (min 16 chars):</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m            <input id=\\\"icon-new-secret\\\" type=\\\"text\\\" placeholder=\\\"Enter new secret…\\\" style=\\\"flex:1;background:var(--bg);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m              padding:10px 12px;border-radius:8px;font-size:13px;font-family:monospace;outline:none\\\">\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"changeIconSecret()\\\" style=\\\"white-space:nowrap\\\">Update</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div id=\\\"icon-secret-err\\\" style=\\\"font-size:11px;color:var(--danger);min-height:14px;margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>·\u001b[39m\n\u001b[31m        <!-- Upload form -->\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Select PWA</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-pwa-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"hub\\\">Hub</option>\u001b[39m\n\u001b[31m            <option value=\\\"expense\\\">Expenses</option>\u001b[39m\n\u001b[31m            <option value=\\\"finance\\\">Finance</option>\u001b[39m\n\u001b[31m            <option value=\\\"installation\\\">Installation</option>\u001b[39m\n\u001b[31m            <option value=\\\"recce\\\">Recce</option>\u001b[39m\n\u001b[31m            <option value=\\\"dispatch\\\">Dispatch</option>\u001b[39m\n\u001b[31m            <option value=\\\"production\\\">Production</option>\u001b[39m\n\u001b[31m            <option value=\\\"vehicle\\\">Vehicle Log</option>\u001b[39m\n\u001b[31m            <option value=\\\"vrs\\\">Vehicle Request</option>\u001b[39m\n\u001b[31m            <option value=\\\"sales\\\">Sales</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Icon Size</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-size-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"192\\\">192 × 192 (standard)</option>\u001b[39m\n\u001b[31m            <option value=\\\"512\\\">512 × 512 (large)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Choose Image File</label>\u001b[39m\n\u001b[31m          <input type=\\\"file\\\" id=\\\"icon-file-input\\\" accept=\\\"image/png,image/jpeg,image/webp\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:14px;width:100%\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-preview-wrap\\\" style=\\\"display:none;text-align:center\\\">\u001b[39m\n\u001b[31m          <img id=\\\"icon-preview-img\\\" style=\\\"width:96px;height:96px;border-radius:16px;border:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-upload-err\\\" style=\\\"font-size:12px;color:var(--danger);min-height:16px\\\"></div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" onclick=\\\"uploadIcon()\\\">Upload Icon</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     MODAL: Admin set PIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-setpin\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🔑 Set PIN — <span id=\\\"setpin-target-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>New PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Enter new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-confirm-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Confirm New PIN</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-confirm\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Repeat new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-auth-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Your own current PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"setpin-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-setpin-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-setpin-save\\\">Set PIN</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     JOBS — Campaign list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-jobs\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Jobs</h1><div class=\\\"sub\\\">Campaigns &amp; Tours</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh jobs\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-campaign\\\" title=\\\"New Campaign\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"jobs-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     CAMPAIGN DETAIL — Tour list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-campaign\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 id=\\\"campaign-hdr-title\\\">Campaign</h1><div class=\\\"sub\\\" id=\\\"campaign-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh campaign\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-tour\\\" title=\\\"New Tour\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"campaign-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Campaign -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-campaign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">📋 New Campaign</div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Year</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-year\\\" min=\\\"2024\\\" max=\\\"2030\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Phase #</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-phase\\\" value=\\\"1\\\" min=\\\"1\\\" max=\\\"99\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Brand <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-brand\\\" placeholder=\\\"e.g. Lenovo\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Client <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-client\\\" placeholder=\\\"e.g. Lenovo India\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Campaign Name <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-campaign\\\" placeholder=\\\"e.g. Chandigarh BTL\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Generated Campaign ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nc-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nc-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nc-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nc-save\\\">Create</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Tour -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-tour\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px;max-height:88vh;overflow-y:auto\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🗺️ New Tour</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Tour Title</div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nt-title\\\" placeholder=\\\"e.g. Chandigarh Week 1\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Start Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-start\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">End Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-end\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:6px\\\">Assign Employees <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-employees\\\" style=\\\"display:flex;flex-direction:column;gap:6px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"color:var(--muted);font-size:13px\\\">Loading…</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Notes</div>\u001b[39m\n\u001b[31m      <textarea id=\\\"nt-notes\\\" rows=\\\"2\\\" placeholder=\\\"Optional instructions or remarks\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px;font-family:inherit;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Tour ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-id-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nt-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nt-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nt-save\\\">Create Tour</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- UPDATE BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"update-banner\\\">\u001b[39m\n\u001b[31m  <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ub-text\\\">App update available</div>\u001b[39m\n\u001b[31m    <div class=\\\"ub-sub\\\">Tap to refresh and get the latest version</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ub-btn\\\" id=\\\"btn-update\\\">Update now</button>\u001b[39m\n\u001b[31m  <button class=\\\"ub-close\\\" id=\\\"btn-update-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- INSTALL BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"install-banner\\\">\u001b[39m\n\u001b[31m  <div class=\\\"ib-icon\\\">📲</div>\u001b[39m\n\u001b[31m  <div class=\\\"ib-text\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ib-title\\\">Install 360LM Hub</div>\u001b[39m\n\u001b[31m    <div class=\\\"ib-sub\\\">Add to home screen for the best experience</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ib-btn\\\" id=\\\"btn-install\\\">Install</button>\u001b[39m\n\u001b[31m  <button class=\\\"ib-close\\\" id=\\\"btn-install-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: AI Config (mgmt only) -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-ai\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🤖 AI Config — Screenshot Extraction</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Tesseract OCR</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-tesseract\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Extraction Fallback</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-enabled\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Model</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-model\\\">\u001b[39m\n\u001b[31m        <option value=\\\"haiku\\\">Haiku (fast, cost-effective)</option>\u001b[39m\n\u001b[31m        <option value=\\\"sonnet\\\">Sonnet (higher accuracy)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-ai-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-ai-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"border-top:1px solid var(--border);padding-top:12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">API Key Health</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px;margin-bottom:10px\\\">Live check — result is always the same for all users</div>\u001b[39m\n\u001b[31m      <div id=\\\"api-key-status-wrap\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-check-keys\\\" style=\\\"margin-top:10px;width:100%\\\">Check API Keys</button>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:14px;border-top:1px solid var(--border);padding-top:12px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">View Usage &amp; Billing</div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Anthropic</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/usage\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Google Maps</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/google/maps-apis/api-list\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: Hub Quick-Action Config -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-qa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">⚡ Quick Actions</div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-config-list\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-cfg-err\\\" style=\\\"color:var(--danger);font-size:13px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-qa-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-qa-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SCRIPT\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31m/* ── CONFIG ── */\u001b[39m\n\u001b[31mconst CFG = {\u001b[39m\n\u001b[31m  api:        '/db',\u001b[39m\n\u001b[31m  apiHeaders: { 'Content-Type': 'application/json', 'Accept-Profile': 'expense' },\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31m/* ── SERVER ACCESS MAP (fetched from hub-access.json, overrides ACCESS for non-mgmt) ── */\u001b[39m\n\u001b[31mlet _serverAccess = {};\u001b[39m\n\u001b[31masync function loadHubAccess() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/hub/hub-access.json?t=' + Date.now());\u001b[39m\n\u001b[31m    if (r.ok) _serverAccess = await r.json();\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── ROLE ACCESS MAP (hardcoded fallback) ── */\u001b[39m\n\u001b[31mconst ACCESS = {\u001b[39m\n\u001b[31m  harish:     ['expense','installation','finance','recce','dispatch','production','jobs','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  pramod:     ['expense','installation','finance','recce','dispatch','production','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  rakesh:     ['installation','expense','production','jobs','vehicle','vrs','sales','hr'],\u001b[39m\n\u001b[31m  ashok:      ['expense','production','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  shambhu:    ['finance','expense','vehicle','vrs','stores','activity','hr'],\u001b[39m\n\u001b[31m  gurpreet:   ['expense','dispatch','vehicle','vrs','stores','hr'],\u001b[39m\n\u001b[31m  sachin:     ['installation','expense','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  mukesh:     ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  umashankar: ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  narender:   ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  amit:       ['vehicle','vrs','hr'],\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst TILES = [\u001b[39m\n\u001b[31m  { id:'expense',      label:'Expenses',     icon:'🧾', url:'/expense/',               live:true  },\u001b[39m\n\u001b[31m  { id:'installation', label:'Installation', icon:'🔧', url:'/installation/current/',  live:true  },\u001b[39m\n\u001b[31m  { id:'finance',      label:'Finance',      icon:'💰', url:'/finance/',               live:true  },\u001b[39m\n\u001b[31m  { id:'recce',        label:'Recce',        icon:'📍', url:'/recce/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'dispatch',     label:'Dispatch',     icon:'🚚', url:'/dispatch/',              live:true  },\u001b[39m\n\u001b[31m  { id:'production',   label:'Production',   icon:'⚙️', url:'/production/',            live:true  },\u001b[39m\n\u001b[31m  { id:'jobs',         label:'Jobs',         icon:'📋', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'vehicle',      label:'Vehicle Log',  icon:'🚐', url:'/vehicle/',               live:true  },\u001b[39m\n\u001b[31m  { id:'vrs',          label:'Veh. Request', icon:'📋', url:'/vrs/',                   live:true  },\u001b[39m\n\u001b[31m  { id:'sales',        label:'Sales',        icon:'💼', url:'/sales/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'stores',       label:'Stores',       icon:'📦', url:'/stores/',                live:true  },\u001b[39m\n\u001b[31m  { id:'client',       label:'Client',       icon:'👤', url:'/client/',                live:true  },\u001b[39m\n\u001b[31m  { id:'team-pins',    label:'Team PINs',    icon:'🔑', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'admin',        label:'Admin',        icon:'🛠️', url:'/admin/',                  live:true  },\u001b[39m\n\u001b[31m  { id:'activity',     label:'Activity',     icon:'🎯', url:'/activity/',               live:true  },\u001b[39m\n\u001b[31m  { id:'hr',           label:'HR',           icon:'👥', url:'/hr/',                     live:true  },\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31mconst MGMT = ['harish', 'pramod'];·\u001b[39m\n\u001b[31m/* ── STATE ── */\u001b[39m\n\u001b[31mlet currentUser     = null;\u001b[39m\n\u001b[31mlet pinBuffer       = '';\u001b[39m\n\u001b[31mlet setPinTarget    = null;\u001b[39m\n\u001b[31mlet pendingShare    = false;\u001b[39m\n\u001b[31mlet _loginAttempts  = 0;\u001b[39m\n\u001b[31mlet _lockoutUntil   = 0;\u001b[39m\n\u001b[31mlet _loginInFlight  = false;·\u001b[39m\n\u001b[31m/* ── UTILS ── */\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => { s.classList.remove('active'); s.style.display = ''; });\u001b[39m\n\u001b[31m  document.getElementById('screen-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet toastTimer;\u001b[39m\n\u001b[31mfunction toast(msg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.classList.add('show');\u001b[39m\n\u001b[31m  clearTimeout(toastTimer);\u001b[39m\n\u001b[31m  toastTimer = setTimeout(() => el.classList.remove('show'), 2800);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fmtINR(n) {\u001b[39m\n\u001b[31m  if (n >= 100000) return '₹' + (n / 100000).toFixed(1) + 'L';\u001b[39m\n\u001b[31m  if (n >= 1000)   return '₹' + (n / 1000).toFixed(1) + 'K';\u001b[39m\n\u001b[31m  return '₹' + Math.round(n);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SESSION ── */\u001b[39m\n\u001b[31mconst SESSION_KEY  = 'lm360-session';\u001b[39m\n\u001b[31mconst EXP_SES_KEY  = '360exp-session';\u001b[39m\n\u001b[31mconst SESSION_TTL  = 12 * 3600 * 1000;·\u001b[39m\n\u001b[31mfunction saveSession(user) {\u001b[39m\n\u001b[31m  const payload = { empId: user.id, name: user.name, role: user.role, loginAt: new Date().toISOString(), source: 'hub' };\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m  localStorage.setItem(EXP_SES_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearSession() {\u001b[39m\n\u001b[31m  localStorage.removeItem(SESSION_KEY);\u001b[39m\n\u001b[31m  localStorage.removeItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const raw = localStorage.getItem(SESSION_KEY) || localStorage.getItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m    if (!raw) return null;\u001b[39m\n\u001b[31m    const s = JSON.parse(raw);\u001b[39m\n\u001b[31m    if (!s.empId || !s.name || !s.role) return null;\u001b[39m\n\u001b[31m    if (Date.now() - new Date(s.loginAt).getTime() > SESSION_TTL) { clearSession(); return null; }\u001b[39m\n\u001b[31m    return s;\u001b[39m\n\u001b[31m  } catch (_) { return null; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGIN FLOW ── */\u001b[39m\n\u001b[31masync function initLogin() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('login-name');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— Choose employee —</option>'; // clear before repopulating\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    emps.forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    [\u001b[39m\n\u001b[31m      { id:'amit',        name:'Amit'             },\u001b[39m\n\u001b[31m      { id:'ashok',       name:'Ashok'            },\u001b[39m\n\u001b[31m      { id:'gurpreet',    name:'Gurpreet'         },\u001b[39m\n\u001b[31m      { id:'harish',      name:'Harish Kumar Lal' },\u001b[39m\n\u001b[31m      { id:'mukesh',      name:'Mukesh'           },\u001b[39m\n\u001b[31m      { id:'narender',    name:'Narender'         },\u001b[39m\n\u001b[31m      { id:'pramod',      name:'Pramod Narang'    },\u001b[39m\n\u001b[31m      { id:'rakesh',      name:'Rakesh'           },\u001b[39m\n\u001b[31m      { id:'sachin',      name:'Sachin'           },\u001b[39m\n\u001b[31m      { id:'shambhu',     name:'Shambhu'          },\u001b[39m\n\u001b[31m      { id:'umashankar',  name:'Umashankar'       },\u001b[39m\n\u001b[31m    ].forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  document.querySelectorAll('.pin-key').forEach(btn =>\u001b[39m\n\u001b[31m    btn.addEventListener('click', () => handlePinKey(btn.dataset.k))\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handlePinKey(k) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (_loginInFlight) return;\u001b[39m\n\u001b[31m  // Lockout guard\u001b[39m\n\u001b[31m  if (_lockoutUntil && Date.now() < _lockoutUntil) {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    errEl.textContent = `Too many attempts — wait ${secs}s`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (_lockoutUntil) { _lockoutUntil = 0; _loginAttempts = 0; }\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (k === 'C') { pinBuffer = ''; }\u001b[39m\n\u001b[31m  else if (k === 'B') { pinBuffer = pinBuffer.slice(0, -1); }\u001b[39m\n\u001b[31m  else {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (!empId) { toast('Select your name first'); return; }\u001b[39m\n\u001b[31m    if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  if (pinBuffer.length === 4) {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (empId) attemptLogin(empId, pinBuffer);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updatePinDisplay() {\u001b[39m\n\u001b[31m  for (let i = 0; i < 4; i++)\u001b[39m\n\u001b[31m    document.getElementById('pd' + i).classList.toggle('filled', i < pinBuffer.length);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _shakePin() {\u001b[39m\n\u001b[31m  const pd = document.getElementById('pin-display');\u001b[39m\n\u001b[31m  pd.classList.add('shake');\u001b[39m\n\u001b[31m  setTimeout(() => pd.classList.remove('shake'), 400);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _startLockout() {\u001b[39m\n\u001b[31m  _lockoutUntil = Date.now() + 30000;\u001b[39m\n\u001b[31m  _loginAttempts = 0;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  const tick = () => {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    if (secs > 0) { errEl.textContent = `Too many attempts — wait ${secs}s`; setTimeout(tick, 500); }\u001b[39m\n\u001b[31m    else { _lockoutUntil = 0; errEl.textContent = ''; }\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  tick();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function attemptLogin(empId, pin) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  _loginInFlight = true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pin }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      _loginAttempts++;\u001b[39m\n\u001b[31m      _shakePin();\u001b[39m\n\u001b[31m      if (_loginAttempts >= 5) { _startLockout(); return; }\u001b[39m\n\u001b[31m      errEl.textContent = `Wrong PIN — ${5 - _loginAttempts} attempt${5 - _loginAttempts !== 1 ? 's' : ''} left`;\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    currentUser = { id: user.id, name: user.name, role: user.role };\u001b[39m\n\u001b[31m    saveSession(currentUser);\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — check connection';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    _loginInFlight = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── HOME SCREEN ── */\u001b[39m\n\u001b[31masync function buildHome() {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const body   = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  // Management users always use hardcoded ACCESS; others prefer server-side hub-access.json\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const tiles  = accessIds.map(id => TILES.find(t => t.id === id)).filter(Boolean);·\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"avatar\\\">${initials}</div>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1 style=\\\"font-size:16px\\\">${currentUser.name}</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">${currentUser.role}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${isMgmt ? '<button class=\\\"btn-icon\\\" id=\\\"btn-hub-ai\\\" title=\\\"AI Config\\\" style=\\\"font-size:18px\\\">🤖</button>' : ''}\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m  if (isMgmt) document.getElementById('btn-hub-ai').addEventListener('click', openHubAiConfig);·\u001b[39m\n\u001b[31m  if (isMgmt) {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"hub-main-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-dash\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">📊</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">Dashboard</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-tools\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">🧰</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">All Tools</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-header\\\">\u001b[39m\n\u001b[31m        <div class=\\\"section-title\\\" style=\\\"margin:0\\\">Quick Actions</div>\u001b[39m\n\u001b[31m        <button class=\\\"btn-icon\\\" id=\\\"btn-hub-qa-cfg\\\" title=\\\"Configure Quick Actions\\\" style=\\\"font-size:18px;padding:8px;min-width:44px;min-height:44px\\\">⚙️</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-grid\\\" id=\\\"hub-qa-grid\\\"></div>`;\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-dash').addEventListener('click',  () => enterHubDash(tiles));\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-tools').addEventListener('click', () => enterHubTools(tiles));\u001b[39m\n\u001b[31m    document.getElementById('btn-hub-qa-cfg').addEventListener('click', openHubQaConfig);\u001b[39m\n\u001b[31m    renderHubQA();\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"tile-grid field\\\" id=\\\"tile-grid\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"text-align:center;font-size:12px;color:var(--muted);padding-top:8px\\\">Tap a tile to open your tool</div>`;\u001b[39m\n\u001b[31m    renderTiles(tiles, 'field');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderTiles(tiles, mode) {\u001b[39m\n\u001b[31m  const grid = document.getElementById('tile-grid');\u001b[39m\n\u001b[31m  tiles.forEach(tile => {\u001b[39m\n\u001b[31m    const div = document.createElement('div');\u001b[39m\n\u001b[31m    div.className = 'tile ' + mode + (tile.live ? '' : ' coming');\u001b[39m\n\u001b[31m    div.innerHTML = `<div class=\\\"tile-icon\\\">${tile.icon}</div><div class=\\\"tile-label\\\">${tile.label}</div>` +\u001b[39m\n\u001b[31m      (tile.live ? '' : '<div class=\\\"soon-badge\\\">Soon</div>');\u001b[39m\n\u001b[31m    div.addEventListener('click', () => handleTile(tile));\u001b[39m\n\u001b[31m    grid.appendChild(div);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handleTile(tile) {\u001b[39m\n\u001b[31m  if (!tile.live) { toast('Coming soon'); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'team-pins') { enterTeamPins(); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'jobs')      { enterJobs();     return; }\u001b[39m\n\u001b[31m  window.location.href = tile.url;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT SUB-VIEWS ── */\u001b[39m\n\u001b[31mfunction _mgmtHeader(title) {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-subview-back\\\" style=\\\"font-size:20px;font-weight:900\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 style=\\\"font-size:16px\\\">${title}</h1></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-subview-back').addEventListener('click', () => buildHome());\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubDash(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('Dashboard');\u001b[39m\n\u001b[31m  const hdr = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const refreshBtn = document.createElement('button');\u001b[39m\n\u001b[31m  refreshBtn.className = 'btn-icon';\u001b[39m\n\u001b[31m  refreshBtn.title = 'Refresh dashboard';\u001b[39m\n\u001b[31m  refreshBtn.setAttribute('aria-label', 'Refresh dashboard');\u001b[39m\n\u001b[31m  refreshBtn.style.cssText = 'font-size:18px';\u001b[39m\n\u001b[31m  refreshBtn.textContent = '🔄';\u001b[39m\n\u001b[31m  refreshBtn.addEventListener('click', loadKPIs);\u001b[39m\n\u001b[31m  hdr.insertBefore(refreshBtn, hdr.querySelector('#btn-logout'));\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Actions Required</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-actions\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Operations</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row kpi-4col\\\" id=\\\"kpi-ops\\\">${skel}${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Production</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-prod\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Installation</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-inst\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">HR</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-hr\\\">${skel}${skel}${skel}</div>`;\u001b[39m\n\u001b[31m  loadKPIs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubTools(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('All Tools');\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  body.innerHTML = `<div class=\\\"tile-grid mgmt\\\" id=\\\"tile-grid\\\"></div>`;\u001b[39m\n\u001b[31m  renderTiles(tiles, 'mgmt');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT AI CONFIG ── */\u001b[39m\n\u001b[31mconst HUB_FIN_HDR = { 'Content-Type': 'application/json', 'Accept-Profile': 'finance' };·\u001b[39m\n\u001b[31masync function openHubAiConfig() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r    = await fetch(`${CFG.api}/upi_config`, { headers: HUB_FIN_HDR });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const get  = key => rows.find(x => x.key === key)?.value;\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-tesseract').value = get('tesseract_enabled') || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-enabled').value   = get('ai_enabled')        || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-model').value     = get('ai_model')           || 'haiku';\u001b[39m\n\u001b[31m  } catch(_) { toast('Could not load AI config'); return; }\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-ai').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveHubAiConfig() {\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-hub-ai-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  const pairs = [\u001b[39m\n\u001b[31m    { key:'tesseract_enabled', value: document.getElementById('hub-ai-tesseract').value },\u001b[39m\n\u001b[31m    { key:'ai_enabled',        value: document.getElementById('hub-ai-enabled').value },\u001b[39m\n\u001b[31m    { key:'ai_model',          value: document.getElementById('hub-ai-model').value },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const results = await Promise.all(pairs.map(p =>\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/upi_config?key=eq.${p.key}`, {\u001b[39m\n\u001b[31m        method: 'PATCH',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'finance', 'Prefer': 'return=minimal' },\u001b[39m\n\u001b[31m        body: JSON.stringify({ value: p.value }),\u001b[39m\n\u001b[31m      })\u001b[39m\n\u001b[31m    ));\u001b[39m\n\u001b[31m    if (results.some(r => !r.ok)) throw new Error('partial failure');\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show');\u001b[39m\n\u001b[31m    toast('✅ AI config saved');\u001b[39m\n\u001b[31m  } catch(_) { toast('Save failed'); }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Save'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function checkApiKeyStatus() {\u001b[39m\n\u001b[31m  const btn  = document.getElementById('btn-check-keys');\u001b[39m\n\u001b[31m  const wrap = document.getElementById('api-key-status-wrap');\u001b[39m\n\u001b[31m  btn.textContent = 'Checking…';\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  wrap.innerHTML = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/tour-ai/key-status');\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Proxy error ' + r.status);\u001b[39m\n\u001b[31m    const d = await r.json();\u001b[39m\n\u001b[31m    function mkBadge(info, label, link) {\u001b[39m\n\u001b[31m      const col  = info.ok ? 'var(--success)' : 'var(--danger)';\u001b[39m\n\u001b[31m      const icon = info.ok ? '✅' : '❌';\u001b[39m\n\u001b[31m      const note = info.ok ? (info.model || 'Valid') : (info.error || 'Error');\u001b[39m\n\u001b[31m      const hint = info.hint ? ' · key ' + info.hint : '';\u001b[39m\n\u001b[31m      return `<div style=\\\"background:var(--card);border:1px solid var(--border);border-left:3px solid ${col};border-radius:8px;padding:10px 12px;display:flex;align-items:center;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:13px;font-weight:600;color:var(--text)\\\">${icon} ${label}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${note}${hint}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <a href=\\\"${link}\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:11px;color:var(--info);text-decoration:none;white-space:nowrap\\\">Billing ↗</a>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    wrap.innerHTML =\u001b[39m\n\u001b[31m      mkBadge(d.anthropic, 'Anthropic API',    'https://console.anthropic.com/settings/billing') +\u001b[39m\n\u001b[31m      mkBadge(d.maps,      'Google Maps API',  'https://console.cloud.google.com/billing');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    wrap.innerHTML = `<div style=\\\"font-size:12px;color:var(--danger)\\\">Error: ${e.message}</div>`;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.textContent = 'Check Again';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT QUICK ACTIONS ── */\u001b[39m\n\u001b[31mconst HUB_QA_DEFS = [\u001b[39m\n\u001b[31m  { id:'offer',       icon:'📋', label:'New Offer',    accessId:'sales',        fn:() => { window.location.href='/sales/'; } },\u001b[39m\n\u001b[31m  { id:'paytm',       icon:'📱', label:'UPI Pay',       accessId:'finance',      fn:() => { window.location.href='/finance/?qa=upi'; } },\u001b[39m\n\u001b[31m  { id:'expense',     icon:'🧾', label:'Expenses',      accessId:'expense',      fn:() => { window.location.href='/expense/'; } },\u001b[39m\n\u001b[31m  { id:'finance',     icon:'💰', label:'Finance',       accessId:'finance',      fn:() => { window.location.href='/finance/'; } },\u001b[39m\n\u001b[31m  { id:'dispatch',    icon:'🚚', label:'Dispatch',      accessId:'dispatch',     fn:() => { window.location.href='/dispatch/'; } },\u001b[39m\n\u001b[31m  { id:'stores',      icon:'📦', label:'Stores',        accessId:'stores',       fn:() => { window.location.href='/stores/'; } },\u001b[39m\n\u001b[31m  { id:'recce',       icon:'📍', label:'Recce',         accessId:'recce',        fn:() => { window.location.href='/recce/'; } },\u001b[39m\n\u001b[31m  { id:'production',  icon:'⚙️', label:'Production',   accessId:'production',   fn:() => { window.location.href='/production/'; } },\u001b[39m\n\u001b[31m  { id:'tours',       icon:'🗺️', label:'Tour Plan',   accessId:'installation', fn:() => { window.location.href='/tour-planner/'; } },\u001b[39m\n\u001b[31m  { id:'installation',icon:'🔧', label:'Installation',  accessId:'installation', fn:() => { window.location.href='/installation/current/'; } },\u001b[39m\n\u001b[31m  { id:'activity',    icon:'🎯', label:'Activity',      accessId:'activity',     fn:() => { window.location.href='/activity/'; } },\u001b[39m\n\u001b[31m];\u001b[39m\n\u001b[31mconst HUB_QA_DEFAULT = ['offer', 'paytm'];\u001b[39m\n\u001b[31mconst HUB_QA_STORE   = '360lm-hub-qa';\u001b[39m\n\u001b[31mlet   _hubQaTempSel  = [];·\u001b[39m\n\u001b[31mfunction getHubQA() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(HUB_QA_STORE));\u001b[39m\n\u001b[31m    if (Array.isArray(s) && s.length) return s;\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m  return [...HUB_QA_DEFAULT];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHubQA() {\u001b[39m\n\u001b[31m  const ids  = getHubQA();\u001b[39m\n\u001b[31m  const grid = document.getElementById('hub-qa-grid');\u001b[39m\n\u001b[31m  if (!grid) return;\u001b[39m\n\u001b[31m  if (!ids.length) {\u001b[39m\n\u001b[31m    grid.innerHTML = `<div style=\\\"color:var(--muted);font-size:13px;grid-column:1/-1;\u001b[39m\n\u001b[31m      text-align:center;padding:16px\\\">Tap ⚙️ to add quick actions</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  grid.innerHTML = ids.map(id => {\u001b[39m\n\u001b[31m    const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!d) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-tile\\\" onclick=\\\"hubQaRun('${id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction hubQaRun(id) {\u001b[39m\n\u001b[31m  const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m  if (d) d.fn();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openHubQaConfig() {\u001b[39m\n\u001b[31m  _hubQaTempSel = [...getHubQA()];\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const visibleDefs = HUB_QA_DEFS.filter(d => accessIds.includes(d.accessId));\u001b[39m\n\u001b[31m  const list = document.getElementById('hub-qa-config-list');\u001b[39m\n\u001b[31m  list.innerHTML = visibleDefs.map(d => {\u001b[39m\n\u001b[31m    const sel = _hubQaTempSel.includes(d.id);\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-item${sel?' selected':''}\\\" id=\\\"hub-qa-item-${d.id}\\\"\u001b[39m\n\u001b[31m        onclick=\\\"toggleHubQaItem('${d.id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-toggle\\\"></div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleHubQaItem(id) {\u001b[39m\n\u001b[31m  const idx = _hubQaTempSel.indexOf(id);\u001b[39m\n\u001b[31m  if (idx >= 0) {\u001b[39m\n\u001b[31m    _hubQaTempSel.splice(idx, 1);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    if (_hubQaTempSel.length >= 6) {\u001b[39m\n\u001b[31m      document.getElementById('hub-qa-cfg-err').textContent = 'Maximum 6 quick actions';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _hubQaTempSel.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  const el = document.getElementById('hub-qa-item-' + id);\u001b[39m\n\u001b[31m  if (el) el.className = 'hub-qa-item' + (_hubQaTempSel.includes(id) ? ' selected' : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction saveHubQaConfig() {\u001b[39m\n\u001b[31m  localStorage.setItem(HUB_QA_STORE, JSON.stringify(_hubQaTempSel));\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.remove('show');\u001b[39m\n\u001b[31m  renderHubQA();\u001b[39m\n\u001b[31m  toast('✅ Quick actions updated');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── KPI FETCH ── */\u001b[39m\n\u001b[31mfunction _hdr(schema) {\u001b[39m\n\u001b[31m  return { 'Content-Type': 'application/json', 'Accept-Profile': schema };\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _safe(p) { return p.catch(() => null); }·\u001b[39m\n\u001b[31masync function loadKPIs() {\u001b[39m\n\u001b[31m  const mon = firstOfMonth();\u001b[39m\n\u001b[31m  const curMonth = new Date().toISOString().slice(0, 7); // YYYY-MM\u001b[39m\n\u001b[31m  const todayIST = (() => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; })();·\u001b[39m\n\u001b[31m  // Reset sections to loading skeletons before fetching (makes refresh visible)\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  [['kpi-actions',3],['kpi-ops',4],['kpi-prod',3],['kpi-inst',3],['kpi-hr',3]].forEach(([id,n]) => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    if (el) el.innerHTML = skel.repeat(n);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  const [\u001b[39m\n\u001b[31m    sheetsRes, claimsRes, salaryRes,\u001b[39m\n\u001b[31m    cashRes,   dispRes,   recceRes,  prodRes,\u001b[39m\n\u001b[31m    instJobsRes, instCtrsRes, storeItemsRes,\u001b[39m\n\u001b[31m    hrAttRes, hrEmpRes, hrConcernRes, hrSlipsRes\u001b[39m\n\u001b[31m  ] = await Promise.all([\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/sheets?status=in.(submitted,escalated)&select=sheet_id`, { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/approved_pending_reimbursement?select=sheet_id`,         { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_register?status=in.(pending,partial)&month=eq.${curMonth}&select=record_id`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/accounts?account_id=eq.petty-cash&select=current_balance`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/dispatches?status=eq.active&select=dispatch_id`,         { headers: _hdr('dispatch') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/submissions?submitted_at=gte.${mon}&select=sub_id`,      { headers: _hdr('recce') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/orders?status=not.in.(dispatched)&select=order_id,status`, { headers: _hdr('production') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/jobs?select=job_id,status`,                              { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/counters?select=counter_id,status`,                      { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/items?active=eq.true&select=id,current_stock,min_stock`, { headers: _hdr('stores') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance?date=eq.${todayIST}&status=eq.present&select=attendance_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/employee_list?select=id`,                               { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance_concerns?status=eq.open&select=concern_id`,  { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_slips?month=eq.${curMonth}&status=eq.finalized&select=slip_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m  ]);·\u001b[39m\n\u001b[31m  const sheets    = sheetsRes?.ok    ? await sheetsRes.json()    : [];\u001b[39m\n\u001b[31m  const claims    = claimsRes?.ok    ? await claimsRes.json()    : [];\u001b[39m\n\u001b[31m  const salary    = salaryRes?.ok    ? await salaryRes.json()    : [];\u001b[39m\n\u001b[31m  const cashArr   = cashRes?.ok      ? await cashRes.json()      : [];\u001b[39m\n\u001b[31m  const disps     = dispRes?.ok      ? await dispRes.json()      : [];\u001b[39m\n\u001b[31m  const recces    = recceRes?.ok     ? await recceRes.json()     : [];\u001b[39m\n\u001b[31m  const prods     = prodRes?.ok      ? await prodRes.json()      : [];\u001b[39m\n\u001b[31m  const instJobs  = instJobsRes?.ok  ? await instJobsRes.json()  : [];\u001b[39m\n\u001b[31m  const instCtrs  = instCtrsRes?.ok  ? await instCtrsRes.json()  : [];\u001b[39m\n\u001b[31m  const storeItems = storeItemsRes?.ok ? await storeItemsRes.json() : [];\u001b[39m\n\u001b[31m  const hrAtt     = hrAttRes?.ok     ? await hrAttRes.json()     : [];\u001b[39m\n\u001b[31m  const hrEmp     = hrEmpRes?.ok     ? await hrEmpRes.json()     : [];\u001b[39m\n\u001b[31m  const hrConcern = hrConcernRes?.ok ? await hrConcernRes.json() : [];\u001b[39m\n\u001b[31m  const hrSlips   = hrSlipsRes?.ok   ? await hrSlipsRes.json()   : [];·\u001b[39m\n\u001b[31m  const cash = Number(cashArr[0]?.current_balance || 0);·\u001b[39m\n\u001b[31m  const actionsEl = document.getElementById('kpi-actions');\u001b[39m\n\u001b[31m  const opsEl     = document.getElementById('kpi-ops');\u001b[39m\n\u001b[31m  const prodEl    = document.getElementById('kpi-prod');\u001b[39m\n\u001b[31m  const instEl    = document.getElementById('kpi-inst');\u001b[39m\n\u001b[31m  const hrEl      = document.getElementById('kpi-hr');\u001b[39m\n\u001b[31m  if (!actionsEl || !opsEl || !prodEl || !instEl) return;·\u001b[39m\n\u001b[31m  // ── Actions Required ────────────────────────────────────────────\u001b[39m\n\u001b[31m  actionsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${sheets.length ? 'alert' : 'ok'}\\\" data-href=\\\"/expense/?tab=approvals\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${sheets.length ? 'red' : 'green'}\\\">${sheets.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Expense Approvals</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${claims.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=claims\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${claims.length ? 'amber' : 'green'}\\\">${claims.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Claims to Reimburse</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${salary.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=salary\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${salary.length ? 'amber' : 'green'}\\\">${salary.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Salary Due This Month</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Operations ──────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const cashColor = cash < 2000 ? 'red' : cash < 5000 ? 'amber' : 'green';\u001b[39m\n\u001b[31m  const cashCard  = cash < 2000 ? 'alert' : cash < 5000 ? 'warn' : 'ok';\u001b[39m\n\u001b[31m  const lowStock = storeItems.filter(i => i.current_stock < i.min_stock).length;\u001b[39m\n\u001b[31m  opsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${cashCard}\\\" data-href=\\\"/finance/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${cashColor}\\\">${fmtINR(cash)}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">${cash < 0 ? '⚠️ ' : ''}Petty Cash</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/dispatch/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${disps.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Dispatches</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/recce/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${recces.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Recce This Month</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${lowStock > 0 ? 'alert' : 'ok'}\\\" data-href=\\\"/stores/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${lowStock > 0 ? 'red' : 'green'}\\\">${lowStock}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Low Stock Items</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Production ───────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const prodPending = prods.filter(o => o.status === 'pending').length;\u001b[39m\n\u001b[31m  const prodActive  = prods.filter(o => o.status === 'in_production' || o.status === 'quality_check').length;\u001b[39m\n\u001b[31m  const prodReady   = prods.filter(o => o.status === 'ready').length;\u001b[39m\n\u001b[31m  prodEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodPending ? 'warn' : 'ok'}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodPending ? 'amber' : 'green'}\\\">${prodPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Pending Orders</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${prodActive}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">In Production</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodReady ? 'ok' : ''}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodReady ? 'green' : ''}\\\">${prodReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Ready to Dispatch</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Installation ─────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const activeJobs  = instJobs.filter(j => j.status === 'active').length;\u001b[39m\n\u001b[31m  const ctrsPending = instCtrs.filter(c => c.status === 'recce_pending' || c.status === 'mockup_pending' || c.status === 'unclear').length;\u001b[39m\n\u001b[31m  const ctrsReady   = instCtrs.filter(c => c.status === 'ready').length;\u001b[39m\n\u001b[31m  instEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${activeJobs ? 'info' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${activeJobs ? 'blue' : 'green'}\\\">${activeJobs}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Jobs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsPending ? 'warn' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsPending ? 'amber' : 'green'}\\\">${ctrsPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Pending</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsReady ? 'ok' : ''}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsReady ? 'green' : ''}\\\">${ctrsReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Ready</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── HR ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31m  if (hrEl) {\u001b[39m\n\u001b[31m    const totalEmp   = hrEmp.length || 0;\u001b[39m\n\u001b[31m    const presentCnt = hrAtt.length;\u001b[39m\n\u001b[31m    const attPct     = totalEmp > 0 ? Math.round(presentCnt / totalEmp * 100) : 0;\u001b[39m\n\u001b[31m    const attColor   = attPct >= 80 ? 'green' : attPct >= 50 ? 'amber' : 'red';\u001b[39m\n\u001b[31m    const attCard    = attPct >= 80 ? 'ok' : attPct >= 50 ? 'warn' : 'alert';\u001b[39m\n\u001b[31m    const concernCnt = hrConcern.length;\u001b[39m\n\u001b[31m    const slipsDue   = hrSlips.length;\u001b[39m\n\u001b[31m    hrEl.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${attCard}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${attColor}\\\">${attPct}%</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Attendance Today</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${concernCnt ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${concernCnt ? 'amber' : 'green'}\\\">${concernCnt}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Open Concerns</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${slipsDue ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${slipsDue ? 'amber' : 'green'}\\\">${slipsDue}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Slips to Pay</div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Wire tap-to-navigate on all KPI cards\u001b[39m\n\u001b[31m  document.querySelectorAll('.kpi-card[data-href]').forEach(card => {\u001b[39m\n\u001b[31m    card.addEventListener('click', () => window.location.href = card.dataset.href);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction firstOfMonth() {\u001b[39m\n\u001b[31m  const d = new Date();\u001b[39m\n\u001b[31m  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGOUT ── */\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  pinBuffer   = '';\u001b[39m\n\u001b[31m  pendingShare = false;\u001b[39m\n\u001b[31m  _loginAttempts = 0; _lockoutUntil = 0; _loginInFlight = false;\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  document.getElementById('login-name').value = '';\u001b[39m\n\u001b[31m  document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m  showScreen('login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── TEAM PINs ── */\u001b[39m\n\u001b[31masync function enterTeamPins() {\u001b[39m\n\u001b[31m  showScreen('team');\u001b[39m\n\u001b[31m  await loadTeamPins();\u001b[39m\n\u001b[31m  // Show icon admin section for harish/pramod\u001b[39m\n\u001b[31m  const sec = document.getElementById('icon-admin-section');\u001b[39m\n\u001b[31m  if (sec) {\u001b[39m\n\u001b[31m    sec.style.display = MGMT.includes(currentUser.id) ? 'block' : 'none';\u001b[39m\n\u001b[31m    if (MGMT.includes(currentUser.id)) refreshSecretDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── ICON ADMIN ────────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst ICON_SECRET_KEY    = 'lm360_icon_secret';\u001b[39m\n\u001b[31mconst ICON_SECRET_INITIAL = 'nlr8Zsqw6XsD0rEXPQ_RfpYiOeNhH-9VglYKoIsElHE';·\u001b[39m\n\u001b[31mfunction getIconSecret() {\u001b[39m\n\u001b[31m  return localStorage.getItem(ICON_SECRET_KEY) || ICON_SECRET_INITIAL;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction setIconSecret(s) {\u001b[39m\n\u001b[31m  localStorage.setItem(ICON_SECRET_KEY, s);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet secretVisible = false;\u001b[39m\n\u001b[31mfunction refreshSecretDisplay() {\u001b[39m\n\u001b[31m  const el = document.getElementById('icon-secret-display');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  const s = getIconSecret();\u001b[39m\n\u001b[31m  el.textContent = secretVisible ? s : s.slice(0,4) + '••••••••••••••••' + s.slice(-4);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleSecretVisible() {\u001b[39m\n\u001b[31m  secretVisible = !secretVisible;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-secret-show');\u001b[39m\n\u001b[31m  if (btn) btn.textContent = secretVisible ? 'Hide' : 'Show';\u001b[39m\n\u001b[31m  refreshSecretDisplay();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function changeIconSecret() {\u001b[39m\n\u001b[31m  const newSecret = document.getElementById('icon-new-secret').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-secret-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (newSecret.length < 16) { errEl.textContent = 'Minimum 16 characters'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/change-secret', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret(), 'X-New-Secret': newSecret }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Failed');\u001b[39m\n\u001b[31m    setIconSecret(newSecret);\u001b[39m\n\u001b[31m    document.getElementById('icon-new-secret').value = '';\u001b[39m\n\u001b[31m    refreshSecretDisplay();\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = '✓ Secret updated';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mdocument.addEventListener('DOMContentLoaded', () => {\u001b[39m\n\u001b[31m  const fileInput = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  if (fileInput) {\u001b[39m\n\u001b[31m    fileInput.addEventListener('change', function() {\u001b[39m\n\u001b[31m      const wrap = document.getElementById('icon-preview-wrap');\u001b[39m\n\u001b[31m      const img  = document.getElementById('icon-preview-img');\u001b[39m\n\u001b[31m      if (this.files[0]) { img.src = URL.createObjectURL(this.files[0]); wrap.style.display = 'block'; }\u001b[39m\n\u001b[31m      else { wrap.style.display = 'none'; }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function uploadIcon() {\u001b[39m\n\u001b[31m  const pwa   = document.getElementById('icon-pwa-select').value;\u001b[39m\n\u001b[31m  const size  = document.getElementById('icon-size-select').value;\u001b[39m\n\u001b[31m  const input = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-upload-err');\u001b[39m\n\u001b[31m  errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (!input.files[0]) { errEl.textContent = 'Choose an image file first'; return; }\u001b[39m\n\u001b[31m  const formData = new FormData();\u001b[39m\n\u001b[31m  formData.append('pwa', pwa);\u001b[39m\n\u001b[31m  formData.append('size', size);\u001b[39m\n\u001b[31m  formData.append('icon', input.files[0]);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret() },\u001b[39m\n\u001b[31m      body: formData\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Upload failed');\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = `✓ Icon updated for ${pwa} (${size}px). Clear browser cache / reinstall to see new icon.`;\u001b[39m\n\u001b[31m    input.value = '';\u001b[39m\n\u001b[31m    document.getElementById('icon-preview-wrap').style.display = 'none';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadTeamPins() {\u001b[39m\n\u001b[31m  const list = document.getElementById('team-list');\u001b[39m\n\u001b[31m  list.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    list.innerHTML = '';\u001b[39m\n\u001b[31m    emps.forEach(emp => {\u001b[39m\n\u001b[31m      const row = document.createElement('div');\u001b[39m\n\u001b[31m      row.className = 'emp-row';\u001b[39m\n\u001b[31m      row.innerHTML = `\u001b[39m\n\u001b[31m        <div style=\\\"min-width:0;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:15px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.name}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.role}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-secondary btn-sm\\\" data-id=\\\"${emp.id}\\\" data-name=\\\"${emp.name}\\\">Set PIN</button>`;\u001b[39m\n\u001b[31m      row.querySelector('button').addEventListener('click', () => openSetPin(emp.id, emp.name));\u001b[39m\n\u001b[31m      list.appendChild(row);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    list.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openSetPin(targetId, targetName) {\u001b[39m\n\u001b[31m  setPinTarget = targetId;\u001b[39m\n\u001b[31m  document.getElementById('setpin-target-name').textContent = targetName;\u001b[39m\n\u001b[31m  ['setpin-new','setpin-confirm','setpin-auth'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-setpin').classList.add('show');\u001b[39m\n\u001b[31m  setTimeout(() => document.getElementById('setpin-new').focus(), 50);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function doSetPin() {\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('setpin-new').value.trim();\u001b[39m\n\u001b[31m  const conf    = document.getElementById('setpin-confirm').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('setpin-auth').value.trim();\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('setpin-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  if (!newPin || !conf || !authPin) { errEl.textContent = 'All fields are required'; return; }\u001b[39m\n\u001b[31m  if (newPin !== conf)              { errEl.textContent = 'New PINs do not match'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))     { errEl.textContent = 'PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-setpin-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_admin_id:  currentUser.id,\u001b[39m\n\u001b[31m        p_admin_pin: authPin,\u001b[39m\n\u001b[31m        p_target_id: setPinTarget,\u001b[39m\n\u001b[31m        p_new_pin:   newPin,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const err = await resp.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Failed to set PIN';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m    toast('✅ PIN updated for ' + document.getElementById('setpin-target-name').textContent);\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Set PIN';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ═══════════════════════════════════════════\u001b[39m\n\u001b[31m   JOBS — Campaign + Tour management\u001b[39m\n\u001b[31m   Access: harish, pramod (campaigns + tours)\u001b[39m\n\u001b[31m           rakesh (tours only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ */\u001b[39m\n\u001b[31mconst INST_RD = { 'Accept-Profile': 'installation' };\u001b[39m\n\u001b[31mconst INST_WR = { 'Content-Type':'application/json','Accept-Profile':'installation','Content-Profile':'installation' };·\u001b[39m\n\u001b[31mlet _currentCampaignId = null;\u001b[39m\n\u001b[31mlet _allEmployees      = [];\u001b[39m\n\u001b[31mlet _editTourId        = null;  // null = create mode, string = edit mode·\u001b[39m\n\u001b[31m// ── Helpers ──────────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _slugify(s) {\u001b[39m\n\u001b[31m  return s.toUpperCase().trim()\u001b[39m\n\u001b[31m    .replace(/[^A-Z0-9]+/g, '_')\u001b[39m\n\u001b[31m    .replace(/^_|_$/g, '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _buildCampaignId(year, brand, client, campaign, phase) {\u001b[39m\n\u001b[31m  const nn = String(phase || 1).padStart(2, '0');\u001b[39m\n\u001b[31m  return `${year}-${_slugify(brand)}-${_slugify(client)}-${_slugify(campaign)}-${nn}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _campStatusBadge(s) {\u001b[39m\n\u001b[31m  const map = { active:'ok', completed:'', on_hold:'warn' };\u001b[39m\n\u001b[31m  const col = { active:'green', completed:'', on_hold:'amber' };\u001b[39m\n\u001b[31m  return `<span class=\\\"kpi-val ${col[s]||''}\\\" style=\\\"font-size:12px;font-weight:700\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _tourStatusBadge(s) {\u001b[39m\n\u001b[31m  const colors = { active:'var(--success)', planned:'var(--info)', completed:'var(--muted)', cancelled:'var(--danger)' };\u001b[39m\n\u001b[31m  return `<span style=\\\"font-size:11px;font-weight:700;padding:2px 8px;border-radius:10px;\u001b[39m\n\u001b[31m    background:var(--card);color:${colors[s]||'var(--muted)'}\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _canCreateCampaign() { return ['harish','pramod'].includes(currentUser?.id); }\u001b[39m\n\u001b[31mfunction _canCreateTour()     { return ['harish','pramod','rakesh'].includes(currentUser?.id); }·\u001b[39m\n\u001b[31m// ── Jobs screen ───────────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterJobs() {\u001b[39m\n\u001b[31m  showScreen('jobs');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').style.display = _canCreateCampaign() ? '' : 'none';\u001b[39m\n\u001b[31m  await loadJobs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadJobs() {\u001b[39m\n\u001b[31m  const el = document.getElementById('jobs-body');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?order=created_at.desc`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?select=tour_id,campaign_id,status`, { headers: INST_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const campaigns = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours     = tResp.ok ? await tResp.json() : [];·\u001b[39m\n\u001b[31m    if (!campaigns.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty\\\"><div style=\\\"font-size:32px\\\">📋</div><div>No campaigns yet</div></div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    el.innerHTML = campaigns.map(c => {\u001b[39m\n\u001b[31m      const cTours  = tours.filter(t => t.campaign_id === c.campaign_id);\u001b[39m\n\u001b[31m      const active  = cTours.filter(t => t.status === 'active').length;\u001b[39m\n\u001b[31m      const total   = cTours.length;\u001b[39m\n\u001b[31m      const statusColors = { active:'var(--success)', on_hold:'var(--accent2)', completed:'var(--muted)' };\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card tappable\\\" style=\\\"padding:14px\\\" onclick=\\\"enterCampaign('${c.campaign_id}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:flex-start;gap:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:15px;font-weight:700\\\">${c.brand} — ${c.campaign_name}\u001b[39m\n\u001b[31m                ${c.phase > 1 ? `<span style=\\\"font-size:11px;color:var(--muted)\\\"> Phase ${c.phase}</span>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:2px\\\">${c.client} · ${c.year}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:4px\\\">\u001b[39m\n\u001b[31m                ${total} tour${total!==1?'s':''} · ${active} active\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;align-items:flex-end;gap:6px\\\">\u001b[39m\n\u001b[31m              <span style=\\\"font-size:11px;font-weight:700;color:${statusColors[c.status]||'var(--muted)'}\\\">\u001b[39m\n\u001b[31m                ${(c.status||'').replace('_',' ')}\u001b[39m\n\u001b[31m              </span>\u001b[39m\n\u001b[31m              <span style=\\\"font-size:18px;color:var(--muted)\\\">›</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-top:8px;word-break:break-all\\\">${c.campaign_id}</div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign detail ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterCampaign(campaignId) {\u001b[39m\n\u001b[31m  _currentCampaignId = campaignId;\u001b[39m\n\u001b[31m  showScreen('campaign');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').style.display = _canCreateTour() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-sub').textContent   = '';\u001b[39m\n\u001b[31m  await loadCampaign(campaignId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCampaign(campaignId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('campaign-body');\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp, empResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(campaignId)}&order=start_date`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const camps = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours = tResp.ok ? await tResp.json() : [];\u001b[39m\n\u001b[31m    _allEmployees = empResp.ok ? await empResp.json() : [];·\u001b[39m\n\u001b[31m    if (!camps.length) { body.innerHTML = '<div class=\\\"empty\\\">Campaign not found</div>'; return; }\u001b[39m\n\u001b[31m    const c = camps[0];·\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-title').textContent = `${c.brand} — ${c.campaign_name}`;\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-sub').textContent   = `${c.client} · ${c.year}${c.phase>1?' · Phase '+c.phase:''}`;·\u001b[39m\n\u001b[31m    const empMap = {};\u001b[39m\n\u001b[31m    _allEmployees.forEach(e => empMap[e.id] = e.name);·\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"padding:12px 14px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Campaign ID</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:10px;color:var(--muted);word-break:break-all;text-align:right;max-width:65%\\\">${c.campaign_id}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Client</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:13px;font-weight:600\\\">${c.client}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${_canCreateCampaign() ? `\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div class=\\\"section-title\\\" style=\\\"margin-bottom:6px\\\">Campaign Status</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m            ${['active','on_hold','completed'].map(s =>\u001b[39m\n\u001b[31m              `<button class=\\\"status-chip${c.status===s?' sel':''}\\\" onclick=\\\"updateCampaignStatus('${c.campaign_id}','${s}')\\\">\u001b[39m\n\u001b[31m                ${s.replace('_',' ')}\u001b[39m\n\u001b[31m              </button>`).join('')}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>` : ''}\u001b[39m\n\u001b[31m      </div>·\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:4px\\\">Tours (${tours.length})</div>\u001b[39m\n\u001b[31m      ${tours.length === 0 ? `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\">\u001b[39m\n\u001b[31m        <div>No tours yet</div>\u001b[39m\n\u001b[31m        ${_canCreateTour() ? '<div style=\\\"font-size:12px\\\">Tap ＋ to add the first tour</div>' : ''}\u001b[39m\n\u001b[31m      </div>` :\u001b[39m\n\u001b[31m      tours.map(t => {\u001b[39m\n\u001b[31m        const empNames = (t.assigned_employees || []).map(id => empMap[id] || id).join(', ') || 'No one assigned';\u001b[39m\n\u001b[31m        const dates    = t.start_date ? `${t.start_date} → ${t.end_date||'?'}` : 'Dates not set';\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m          <div class=\\\"card\\\" style=\\\"padding:14px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;align-items:flex-start;gap:8px\\\">\u001b[39m\n\u001b[31m              <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m                <div style=\\\"font-size:14px;font-weight:700\\\">${t.title || t.tour_id}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">📅 ${dates}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">👥 ${empNames}</div>\u001b[39m\n\u001b[31m                ${t.notes ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">📝 ${t.notes}</div>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              ${_tourStatusBadge(t.status)}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            ${_canCreateTour() ? `\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;gap:6px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m              ${['planned','active','completed'].map(s =>\u001b[39m\n\u001b[31m                `<button class=\\\"status-chip${t.status===s?' sel':''}\\\"\u001b[39m\n\u001b[31m                  onclick=\\\"updateTourStatus('${t.tour_id}','${s}')\\\">${s}</button>`).join('')}\u001b[39m\n\u001b[31m              <button class=\\\"status-chip\\\" onclick=\\\"openEditTour('${t.tour_id}')\\\"\u001b[39m\n\u001b[31m                style=\\\"margin-left:auto;background:var(--surface);border-color:#3a3a6e;color:#8888ff\\\">\u001b[39m\n\u001b[31m                ✏️ Edit\u001b[39m\n\u001b[31m              </button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m            <div style=\\\"font-size:10px;color:var(--muted);margin-top:6px\\\">${t.tour_id}</div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m      <div style=\\\"height:20px\\\"></div>`;\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateTourStatus(tourId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Tour marked ${status}`);\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateCampaignStatus(campaignId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Campaign marked ${status.replace('_',' ')}`);\u001b[39m\n\u001b[31m    loadCampaign(campaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Campaign modal ────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewCampaign() {\u001b[39m\n\u001b[31m  if (!_canCreateCampaign()) { toast('Only Harish or Pramod can create campaigns'); return; }\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('nc-year').value    = new Date().getFullYear();\u001b[39m\n\u001b[31m  document.getElementById('nc-phase').value   = '1';\u001b[39m\n\u001b[31m  document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  document.getElementById('nc-err').textContent     = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-new-campaign').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateCampaignPreview() {\u001b[39m\n\u001b[31m  const year  = document.getElementById('nc-year').value;\u001b[39m\n\u001b[31m  const brand = document.getElementById('nc-brand').value;\u001b[39m\n\u001b[31m  const clt   = document.getElementById('nc-client').value;\u001b[39m\n\u001b[31m  const camp  = document.getElementById('nc-campaign').value;\u001b[39m\n\u001b[31m  const phase = document.getElementById('nc-phase').value;\u001b[39m\n\u001b[31m  if (brand && clt && camp) {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = _buildCampaignId(year, brand, clt, camp, phase);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewCampaign() {\u001b[39m\n\u001b[31m  const year   = Number(document.getElementById('nc-year').value);\u001b[39m\n\u001b[31m  const brand  = document.getElementById('nc-brand').value.trim();\u001b[39m\n\u001b[31m  const client = document.getElementById('nc-client').value.trim();\u001b[39m\n\u001b[31m  const cname  = document.getElementById('nc-campaign').value.trim();\u001b[39m\n\u001b[31m  const phase  = Number(document.getElementById('nc-phase').value) || 1;\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('nc-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const brandEl = document.getElementById('nc-brand');\u001b[39m\n\u001b[31m  const clientEl = document.getElementById('nc-client');\u001b[39m\n\u001b[31m  const cnameEl = document.getElementById('nc-campaign');\u001b[39m\n\u001b[31m  [brandEl, clientEl, cnameEl].forEach(el => el.style.borderColor = '');\u001b[39m\n\u001b[31m  let _missingField = false;\u001b[39m\n\u001b[31m  if (!brand)  { brandEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!client) { clientEl.style.borderColor = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!cname)  { cnameEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (_missingField) { errEl.textContent = 'Brand, Client and Campaign Name are required'; return; }\u001b[39m\n\u001b[31m  const campaignId = _buildCampaignId(year, brand, client, cname, phase);·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nc-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/campaigns`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ campaign_id:campaignId, year, brand, client, campaign_name:cname, phase, created_by:currentUser.id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || 'Failed to create';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show');\u001b[39m\n\u001b[31m    toast(`✅ Campaign created`);\u001b[39m\n\u001b[31m    loadJobs();\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Create'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Tour modal ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _buildEmpCheckboxes(assigned = []) {\u001b[39m\n\u001b[31m  return _allEmployees.map(e =>\u001b[39m\n\u001b[31m    `<label style=\\\"display:flex;align-items:center;gap:10px;padding:8px 10px;background:var(--card);\u001b[39m\n\u001b[31m      border:1px solid var(--border);border-radius:8px;cursor:pointer\\\">\u001b[39m\n\u001b[31m      <input type=\\\"checkbox\\\" value=\\\"${e.id}\\\" ${assigned.includes(e.id)?'checked':''} style=\\\"width:16px;height:16px;accent-color:var(--accent)\\\">\u001b[39m\n\u001b[31m      <span style=\\\"font-size:14px;font-weight:600\\\">${e.name}</span>\u001b[39m\n\u001b[31m      <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${e.role}</span>\u001b[39m\n\u001b[31m    </label>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openNewTour() {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = null;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '🗺️ New Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Create Tour';\u001b[39m\n\u001b[31m  document.getElementById('nt-title').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-start').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-end').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-notes').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(_currentCampaignId)}&select=tour_id`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const existing = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    const nn = String(existing.length + 1).padStart(2, '0');\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T${nn}`;\u001b[39m\n\u001b[31m  } catch(_) {\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T01`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (_allEmployees.length)\u001b[39m\n\u001b[31m    document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes();·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEditTour(tourId) {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = tourId;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '✏️ Edit Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Save Changes';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const tours = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    if (!tours.length) { toast('Tour not found'); return; }\u001b[39m\n\u001b[31m    const t = tours[0];\u001b[39m\n\u001b[31m    document.getElementById('nt-title').value = t.title || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-start').value = t.start_date || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-end').value   = t.end_date   || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-notes').value = t.notes      || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = tourId;\u001b[39m\n\u001b[31m    if (_allEmployees.length)\u001b[39m\n\u001b[31m      document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes(t.assigned_employees || []);\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to load tour'); return; }·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewTour() {\u001b[39m\n\u001b[31m  const title = document.getElementById('nt-title').value.trim();\u001b[39m\n\u001b[31m  const start = document.getElementById('nt-start').value || null;\u001b[39m\n\u001b[31m  const end   = document.getElementById('nt-end').value   || null;\u001b[39m\n\u001b[31m  const notes = document.getElementById('nt-notes').value.trim() || null;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('nt-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const checked = [...document.querySelectorAll('#nt-employees input:checked')].map(cb => cb.value);\u001b[39m\n\u001b[31m  if (!checked.length) { errEl.textContent = 'Assign at least one employee'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nt-save');\u001b[39m\n\u001b[31m  const wasEdit = !!_editTourId;\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = wasEdit ? 'Saving…' : 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editTourId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(_editTourId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      const tourId = document.getElementById('nt-id-preview').textContent;\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          tour_id: tourId, campaign_id: _currentCampaignId,\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes, status: 'planned',\u001b[39m\n\u001b[31m          created_by: currentUser.id,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || (_editTourId ? 'Failed to save' : 'Failed to create');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show');\u001b[39m\n\u001b[31m    toast(_editTourId ? '✅ Tour updated' : '✅ Tour created');\u001b[39m\n\u001b[31m    _editTourId = null;\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = wasEdit ? 'Save Changes' : 'Create Tour';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── EVENT WIRING ── */\u001b[39m\n\u001b[31mfunction wireEvents() {\u001b[39m\n\u001b[31m  // Jobs screens\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-back').addEventListener('click',     () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-refresh').addEventListener('click',  loadJobs);\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').addEventListener('click',  openNewCampaign);\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-back').addEventListener('click', () => showScreen('jobs'));\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-refresh').addEventListener('click', () => loadCampaign(_currentCampaignId));\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').addEventListener('click',      openNewTour);·\u001b[39m\n\u001b[31m  // New Campaign modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-save').addEventListener('click', saveNewCampaign);\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign','nc-year','nc-phase'].forEach(id =>\u001b[39m\n\u001b[31m    document.getElementById(id).addEventListener('input', updateCampaignPreview));·\u001b[39m\n\u001b[31m  // New Tour modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').addEventListener('click', saveNewTour);·\u001b[39m\n\u001b[31m  document.getElementById('btn-team-back').addEventListener('click', () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-team-refresh').addEventListener('click', loadTeamPins);\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-cancel').addEventListener('click', () => {\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-save').addEventListener('click', doSetPin);·\u001b[39m\n\u001b[31m  // Set PIN — progressive field reveal\u001b[39m\n\u001b[31m  document.getElementById('setpin-new').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const val = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const showConf = val.length === 4;\u001b[39m\n\u001b[31m    document.getElementById('setpin-confirm-group').style.display = showConf ? '' : 'none';\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m    if (!showConf) { document.getElementById('setpin-confirm').value = ''; document.getElementById('setpin-auth').value = ''; }\u001b[39m\n\u001b[31m    if (showConf) setTimeout(() => document.getElementById('setpin-confirm').focus(), 20);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const newVal = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const conf   = document.getElementById('setpin-confirm').value;\u001b[39m\n\u001b[31m    const showAuth = conf.length === 4 && conf === newVal;\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = showAuth ? '' : 'none';\u001b[39m\n\u001b[31m    if (!showAuth) document.getElementById('setpin-auth').value = '';\u001b[39m\n\u001b[31m    if (showAuth) setTimeout(() => document.getElementById('setpin-auth').focus(), 20);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // Hub AI config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-save').addEventListener('click',   saveHubAiConfig);\u001b[39m\n\u001b[31m  document.getElementById('btn-check-keys').addEventListener('click',    checkApiKeyStatus);·\u001b[39m\n\u001b[31m  // Hub QA config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-qa').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-save').addEventListener('click', saveHubQaConfig);·\u001b[39m\n\u001b[31m  // Share picker\u001b[39m\n\u001b[31m  document.getElementById('share-to-expense').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/expense/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-recce').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/recce/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-installation').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/installation/current/?share=1';\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-04: employee dropdown change resets PIN buffer and lockout\u001b[39m\n\u001b[31m  document.getElementById('login-name').addEventListener('change', () => {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-07: ESC key closes any open modal\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (e.key === 'Escape') {\u001b[39m\n\u001b[31m      document.querySelectorAll('.modal-overlay.show').forEach(m => m.classList.remove('show'));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-11: keyboard digit/backspace input on login screen\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (!document.getElementById('screen-login').classList.contains('active')) return;\u001b[39m\n\u001b[31m    if (e.key >= '0' && e.key <= '9') { handlePinKey(e.key); }\u001b[39m\n\u001b[31m    else if (e.key === 'Backspace') { e.preventDefault(); handlePinKey('B'); }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-02/B-08: re-check session TTL when user returns to the tab\u001b[39m\n\u001b[31m  document.addEventListener('visibilitychange', () => {\u001b[39m\n\u001b[31m    if (document.visibilityState === 'visible' && currentUser) {\u001b[39m\n\u001b[31m      if (!loadSession()) doLogout();\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── BOOT ── */\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  wireEvents();·\u001b[39m\n\u001b[31m  if (location.search.includes('share=1')) {\u001b[39m\n\u001b[31m    pendingShare = true;\u001b[39m\n\u001b[31m    history.replaceState({}, '', '/hub/');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Always populate the login dropdown (needed if user logs out later)\u001b[39m\n\u001b[31m  initLogin(); // fire-and-forget — does not block home screen path·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess) {\u001b[39m\n\u001b[31m    currentUser = { id: sess.empId, name: sess.name, role: sess.role };\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  setTimeout(() => showScreen('login'), 1500);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SERVICE WORKER: install + update ── */\u001b[39m\n\u001b[31mif ('serviceWorker' in navigator) {\u001b[39m\n\u001b[31m  navigator.serviceWorker.register('/hub/sw.js').then(reg => {·\u001b[39m\n\u001b[31m    // Install banner: show when browser signals app is installable\u001b[39m\n\u001b[31m    let deferredInstall = null;\u001b[39m\n\u001b[31m    window.addEventListener('beforeinstallprompt', e => {\u001b[39m\n\u001b[31m      e.preventDefault();\u001b[39m\n\u001b[31m      deferredInstall = e;\u001b[39m\n\u001b[31m      if (!window.matchMedia('(display-mode: standalone)').matches) {\u001b[39m\n\u001b[31m        const delay = document.getElementById('update-banner').classList.contains('show') ? 3500 : 0;\u001b[39m\n\u001b[31m        setTimeout(() => document.getElementById('install-banner').classList.add('show'), delay);\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    window.addEventListener('appinstalled', () => {\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install').addEventListener('click', async () => {\u001b[39m\n\u001b[31m      if (!deferredInstall) return;\u001b[39m\n\u001b[31m      deferredInstall.prompt();\u001b[39m\n\u001b[31m      await deferredInstall.userChoice;\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m    // Update banner: show when a new SW version is waiting to activate\u001b[39m\n\u001b[31m    function showUpdateBanner() {\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.add('show');\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (reg.waiting) showUpdateBanner();·\u001b[39m\n\u001b[31m    reg.addEventListener('updatefound', () => {\u001b[39m\n\u001b[31m      const newSW = reg.installing;\u001b[39m\n\u001b[31m      newSW.addEventListener('statechange', () => {\u001b[39m\n\u001b[31m        if (newSW.state === 'installed' && navigator.serviceWorker.controller) {\u001b[39m\n\u001b[31m          showUpdateBanner();\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    document.getElementById('btn-update').addEventListener('click', () => {\u001b[39m\n\u001b[31m      if (reg.waiting) {\u001b[39m\n\u001b[31m        reg.waiting.postMessage({ type: 'SKIP_WAITING' });\u001b[39m\n\u001b[31m        navigator.serviceWorker.addEventListener('controllerchange', () => {\u001b[39m\n\u001b[31m          window.location.reload();\u001b[39m\n\u001b[31m        }, { once: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-update-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m  }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mboot();\u001b[39m\n\u001b[31m</script>··\u001b[39m\n\u001b[31m</body></html>\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"recce-v10\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html><html lang=\\\"en\\\"><head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#FF6B35\\\">\u001b[39m\n\u001b[31m<meta name=\\\"mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-status-bar-style\\\" content=\\\"black-translucent\\\">\u001b[39m\n\u001b[31m<title>360LM</title>\u001b[39m\n\u001b[31m<meta name=\\\"description\\\" content=\\\"360LM Operations Hub — employee tools\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/hub/manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"icon\\\" href=\\\"/hub/icons/icon-192.png\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--surface:#161616;--card:#1e1e1e;--border:#2a2a2a;\u001b[39m\n\u001b[31m  --accent:#FF6B35;--accent2:#FFB800;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --danger:#e74c3c;--success:#2ecc71;--info:#3498db;\u001b[39m\n\u001b[31m  --radius:12px;--font:'Segoe UI',system-ui,sans-serif;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:var(--font);min-height:100vh;overflow-x:hidden}\u001b[39m\n\u001b[31m.screen{display:none;min-height:100vh;flex-direction:column}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── HEADER ── */\u001b[39m\n\u001b[31m.header{background:var(--surface);border-bottom:1px solid var(--border);padding:14px 16px;\u001b[39m\n\u001b[31m  display:flex;align-items:center;gap:12px;position:sticky;top:0;z-index:10;flex-shrink:0}\u001b[39m\n\u001b[31m.header h1{font-size:18px;font-weight:700;color:var(--accent);flex:1}\u001b[39m\n\u001b[31m.header .sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.btn-icon{background:none;border:none;color:var(--muted);font-size:22px;cursor:pointer;\u001b[39m\n\u001b[31m  padding:4px;border-radius:8px;line-height:1;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m.btn-icon:active{background:var(--border)}·\u001b[39m\n\u001b[31m/* ── BODY ── */\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:16px}·\u001b[39m\n\u001b[31m/* ── SPLASH ── */\u001b[39m\n\u001b[31m#screen-splash{align-items:center;justify-content:center;gap:8px}\u001b[39m\n\u001b[31m.splash-logo{font-size:72px;font-weight:900;color:var(--accent);letter-spacing:-2px;line-height:1}\u001b[39m\n\u001b[31m.splash-sub{font-size:15px;color:var(--muted);letter-spacing:2px;text-transform:uppercase}\u001b[39m\n\u001b[31m.splash-dot{width:8px;height:8px;border-radius:50%;background:var(--accent);\u001b[39m\n\u001b[31m  animation:pulse 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes pulse{0%,100%{opacity:.3;transform:scale(.8)}50%{opacity:1;transform:scale(1)}}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  min-height:100dvh;padding:24px;gap:20px;overflow-y:auto}\u001b[39m\n\u001b[31m.login-logo{font-size:44px;font-weight:900;color:var(--accent);letter-spacing:-1px}\u001b[39m\n\u001b[31m.login-sub{font-size:13px;color:var(--muted);text-transform:uppercase;letter-spacing:2px}\u001b[39m\n\u001b[31m.form-group{width:100%;max-width:360px;display:flex;flex-direction:column;gap:8px}\u001b[39m\n\u001b[31m.form-group label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px}\u001b[39m\n\u001b[31mselect{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31mselect:focus{border-color:var(--accent)}·\u001b[39m\n\u001b[31m/* ── PIN PAD ── */\u001b[39m\n\u001b[31m.pin-display{display:flex;gap:14px;justify-content:center;margin:4px 0}\u001b[39m\n\u001b[31m.pin-dot{width:16px;height:16px;border-radius:50%;border:2px solid var(--muted);background:transparent;transition:.15s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.pin-pad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;max-width:260px;margin:0 auto;width:100%}\u001b[39m\n\u001b[31m.pin-key{background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  font-size:22px;font-weight:600;padding:16px;border-radius:var(--radius);cursor:pointer;\u001b[39m\n\u001b[31m  text-align:center;line-height:1;user-select:none}\u001b[39m\n\u001b[31m.pin-key:active{background:var(--border)}\u001b[39m\n\u001b[31m.pin-key.clr{color:var(--danger);font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.pin-key.back{color:var(--accent2)}\u001b[39m\n\u001b[31m.login-err{font-size:13px;color:var(--danger);text-align:center;min-height:18px}\u001b[39m\n\u001b[31m@keyframes shake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-6px)}40%,80%{transform:translateX(6px)}}\u001b[39m\n\u001b[31m.pin-display.shake{animation:shake .4s ease}·\u001b[39m\n\u001b[31m/* ── KPI CARDS ── */\u001b[39m\n\u001b[31m.kpi-row{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\u001b[39m\n\u001b[31m.kpi-4col{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m#home-body{gap:10px}\u001b[39m\n\u001b[31m.kpi-card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:10px 8px;text-align:center;border-left:3px solid var(--accent);cursor:default}\u001b[39m\n\u001b[31m.kpi-card.alert{border-left-color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-card.warn{border-left-color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-card.ok{border-left-color:var(--success)}\u001b[39m\n\u001b[31m.kpi-card.info{border-left-color:var(--info)}\u001b[39m\n\u001b[31m.kpi-val{font-size:18px;font-weight:900;color:var(--accent);line-height:1.2}\u001b[39m\n\u001b[31m.kpi-val.red{color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-val.amber{color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-val.green{color:var(--success)}\u001b[39m\n\u001b[31m.kpi-val.blue{color:var(--info)}\u001b[39m\n\u001b[31m.kpi-lbl{font-size:10px;color:var(--muted);margin-top:4px;text-transform:uppercase;letter-spacing:.4px;line-height:1.3}\u001b[39m\n\u001b[31m.kpi-skel{background:var(--border);border-radius:4px;height:24px;margin-bottom:4px;\u001b[39m\n\u001b[31m  animation:shimmer 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes shimmer{0%,100%{opacity:.5}50%{opacity:1}}·\u001b[39m\n\u001b[31m/* ── SECTION TITLE ── */\u001b[39m\n\u001b[31m.section-title{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.8px}·\u001b[39m\n\u001b[31m/* ── MGMT HOME: MAIN TILES ── */\u001b[39m\n\u001b[31m.hub-main-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px}\u001b[39m\n\u001b[31m.hub-main-tile{background:var(--card);border:2px solid var(--border);border-radius:18px;\u001b[39m\n\u001b[31m  padding:36px 12px 28px;text-align:center;cursor:pointer;transition:background .15s,border-color .15s}\u001b[39m\n\u001b[31m.hub-main-tile:active{background:#1a2a1a;border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-main-icon{font-size:40px;line-height:1;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-main-label{font-size:15px;font-weight:800;color:var(--text)}\u001b[39m\n\u001b[31m/* ── MGMT HOME: QUICK ACTIONS ── */\u001b[39m\n\u001b[31m.hub-qa-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-qa-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(88px,1fr));gap:10px}\u001b[39m\n\u001b[31m.hub-qa-tile{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px 8px;text-align:center;cursor:pointer;display:flex;flex-direction:column;\u001b[39m\n\u001b[31m  align-items:center;gap:6px;transition:background .15s}\u001b[39m\n\u001b[31m.hub-qa-tile:active{background:var(--border)}\u001b[39m\n\u001b[31m.hub-qa-icon{font-size:26px;line-height:1}\u001b[39m\n\u001b[31m.hub-qa-label{font-size:11px;font-weight:700;color:var(--text);line-height:1.3}\u001b[39m\n\u001b[31m.hub-qa-item{display:flex;align-items:center;gap:12px;padding:10px 0;\u001b[39m\n\u001b[31m  border-bottom:1px solid var(--border);cursor:pointer}\u001b[39m\n\u001b[31m.hub-qa-item:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.hub-qa-item-icon{font-size:22px;width:32px;text-align:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item-label{flex:1;font-size:14px;font-weight:600}\u001b[39m\n\u001b[31m.hub-qa-item-toggle{width:20px;height:20px;border-radius:4px;border:2px solid var(--border);\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle::after{content:'✓';color:#fff;font-weight:700;font-size:12px}·\u001b[39m\n\u001b[31m/* ── TILE GRID ── */\u001b[39m\n\u001b[31m.tile-grid{display:grid;gap:12px}\u001b[39m\n\u001b[31m.tile-grid.mgmt{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m.tile-grid.field{grid-template-columns:repeat(2,1fr)}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:18px 8px;display:flex;flex-direction:column;align-items:center;gap:8px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s;position:relative}\u001b[39m\n\u001b[31m.tile:active{background:var(--border);transform:scale(.97)}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px;line-height:1}\u001b[39m\n\u001b[31m.tile-label{font-size:11px;font-weight:700;color:var(--text);text-align:center;text-transform:uppercase;letter-spacing:.4px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-icon{font-size:24px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-label{font-size:10px}\u001b[39m\n\u001b[31m.tile.coming{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.tile.coming:active{transform:none;background:var(--card)}\u001b[39m\n\u001b[31m.soon-badge{position:absolute;top:6px;right:6px;background:var(--accent2);color:#000;\u001b[39m\n\u001b[31m  font-size:8px;font-weight:800;padding:2px 5px;border-radius:20px;text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── USER BADGE ── */\u001b[39m\n\u001b[31m.user-badge{display:flex;align-items:center;gap:12px}\u001b[39m\n\u001b[31m.avatar{width:44px;height:44px;background:var(--accent);border-radius:50%;\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:800;\u001b[39m\n\u001b[31m  color:#fff;flex-shrink:0;text-transform:uppercase}\u001b[39m\n\u001b[31m.user-name{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.user-role{font-size:12px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── TEAM PINS ── */\u001b[39m\n\u001b[31m.emp-row{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px}\u001b[39m\n\u001b[31m.btn{display:flex;align-items:center;justify-content:center;gap:6px;border:none;\u001b[39m\n\u001b[31m  border-radius:var(--radius);font-size:15px;font-weight:700;padding:12px 18px;\u001b[39m\n\u001b[31m  cursor:pointer;width:100%;transition:.15s;font-family:var(--font)}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--card);border:1px solid var(--border);color:var(--text)}\u001b[39m\n\u001b[31m.btn-sm{padding:7px 12px;font-size:12px;font-weight:700;width:auto;flex-shrink:0}\u001b[39m\n\u001b[31m.info-note{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;font-size:13px;color:var(--muted);line-height:1.5}·\u001b[39m\n\u001b[31m/* ── MODAL ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:200;\u001b[39m\n\u001b[31m  align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;\u001b[39m\n\u001b[31m  padding:24px;width:100%;max-width:480px;display:flex;flex-direction:column;gap:14px;\u001b[39m\n\u001b[31m  max-height:90dvh;overflow-y:auto;-webkit-overflow-scrolling:touch}\u001b[39m\n\u001b[31m.modal-title{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.modal-box input[type=password]{\u001b[39m\n\u001b[31m  width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31m.modal-box input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.action-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px}·\u001b[39m\n\u001b[31m/* ── SHARE PICKER ── */\u001b[39m\n\u001b[31m#screen-share .body{align-items:center;justify-content:center;gap:24px}\u001b[39m\n\u001b[31m.share-prompt{font-size:15px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.share-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;width:100%;max-width:360px}\u001b[39m\n\u001b[31m.share-tile{background:var(--card);border:2px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:24px 8px;display:flex;flex-direction:column;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s}\u001b[39m\n\u001b[31m.share-tile:active{background:var(--border);transform:scale(.97);border-color:var(--accent)}\u001b[39m\n\u001b[31m.share-tile-icon{font-size:36px;line-height:1}\u001b[39m\n\u001b[31m.share-tile-label{font-size:12px;font-weight:700;color:var(--text);text-align:center;\u001b[39m\n\u001b[31m  text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── INSTALL BANNER ── */\u001b[39m\n\u001b[31m#install-banner{display:none;position:fixed;bottom:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:linear-gradient(135deg,#1a1a1a,#0d2010);\u001b[39m\n\u001b[31m  border-top:1px solid var(--accent);padding:14px 16px;\u001b[39m\n\u001b[31m  align-items:center;gap:12px;box-shadow:0 -4px 24px rgba(0,0,0,.7)}\u001b[39m\n\u001b[31m#install-banner.show{display:flex}\u001b[39m\n\u001b[31m#install-banner .ib-icon{font-size:30px;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-text{flex:1}\u001b[39m\n\u001b[31m#install-banner .ib-title{font-size:14px;font-weight:700;color:var(--text)}\u001b[39m\n\u001b[31m#install-banner .ib-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#install-banner .ib-btn{background:var(--accent);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-close{background:none;border:none;color:var(--muted);font-size:20px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── UPDATE BANNER ── */\u001b[39m\n\u001b[31m#update-banner{display:none;position:fixed;top:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:#1a2a1a;border-bottom:2px solid var(--success);\u001b[39m\n\u001b[31m  padding:12px 16px;align-items:center;gap:12px}\u001b[39m\n\u001b[31m#update-banner.show{display:flex}\u001b[39m\n\u001b[31m#update-banner .ub-text{flex:1;font-size:14px;font-weight:600;color:var(--text)}\u001b[39m\n\u001b[31m#update-banner .ub-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#update-banner .ub-btn{background:var(--success);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:8px 14px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#update-banner .ub-close{background:none;border:none;color:var(--muted);font-size:18px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#222;color:#fff;padding:10px 20px;border-radius:20px;font-size:14px;font-weight:600;\u001b[39m\n\u001b[31m  opacity:0;transition:.3s;pointer-events:none;white-space:nowrap;z-index:500}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  padding:40px;color:var(--muted);font-size:14px;gap:12px}\u001b[39m\n\u001b[31m.spinner{width:36px;height:36px;border:3px solid var(--border);border-top-color:var(--accent);\u001b[39m\n\u001b[31m  border-radius:50%;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* ── CARD (jobs / campaign screens) ── */\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius)}\u001b[39m\n\u001b[31m.card.tappable:active{border-color:var(--accent);opacity:.85}·\u001b[39m\n\u001b[31m/* ── STATUS CHIPS (tour / campaign) ── */\u001b[39m\n\u001b[31m.status-chip{font-size:11px;padding:4px 11px;border-radius:8px;cursor:pointer;\u001b[39m\n\u001b[31m  background:var(--card);color:var(--text);border:1px solid var(--border);font-weight:600;\u001b[39m\n\u001b[31m  font-family:var(--font);transition:.15s}\u001b[39m\n\u001b[31m.status-chip.sel{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.status-chip:active{opacity:.8}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SPLASH\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-splash\\\" class=\\\"screen active\\\">\u001b[39m\n\u001b[31m  <div class=\\\"splash-logo\\\">360LM</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-sub\\\">Operations Hub</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-dot\\\" style=\\\"margin-top:16px\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     LOGIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-login\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-wrap\\\">\u001b[39m\n\u001b[31m    <div class=\\\"login-logo\\\">360LM</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">Operations Hub</div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Select Your Name</label>\u001b[39m\n\u001b[31m      <select id=\\\"login-name\\\"><option value=\\\"\\\">— Choose employee —</option><option value=\\\"amit\\\">Amit</option><option value=\\\"ashok\\\">Ashok</option><option value=\\\"gurpreet\\\">Gurpreet</option><option value=\\\"harish\\\">Harish Kumar Lal</option><option value=\\\"mukesh\\\">Mukesh</option><option value=\\\"narender\\\">Narender</option><option value=\\\"pramod\\\">Pramod Narang</option><option value=\\\"rakesh\\\">Rakesh</option><option value=\\\"sachin\\\">Sachin</option><option value=\\\"shambhu\\\">Shambhu</option><option value=\\\"umashankar\\\">Umashankar</option></select>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m      <label style=\\\"text-align:center\\\">Enter PIN</label>\u001b[39m\n\u001b[31m      <div class=\\\"pin-display\\\" id=\\\"pin-display\\\">\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd0\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd1\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd2\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd3\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"login-err\\\" id=\\\"login-err\\\"></div>\u001b[39m\n\u001b[31m      <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-1\\\" data-k=\\\"1\\\" aria-label=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-2\\\" data-k=\\\"2\\\" aria-label=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-3\\\" data-k=\\\"3\\\" aria-label=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-4\\\" data-k=\\\"4\\\" aria-label=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-5\\\" data-k=\\\"5\\\" aria-label=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-6\\\" data-k=\\\"6\\\" aria-label=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-7\\\" data-k=\\\"7\\\" aria-label=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-8\\\" data-k=\\\"8\\\" aria-label=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-9\\\" data-k=\\\"9\\\" aria-label=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key clr\\\" id=\\\"pk-C\\\" data-k=\\\"C\\\" aria-label=\\\"Clear\\\">CLR</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-0\\\" data-k=\\\"0\\\" aria-label=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key back\\\" id=\\\"pk-B\\\" data-k=\\\"B\\\" aria-label=\\\"Backspace\\\">⌫</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     HOME  (built dynamically)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\" id=\\\"home-header\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"home-body\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SHARE PICKER\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-share\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>Share Image To</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">Choose where to send this photo</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"share-prompt\\\">Which tool should receive this image?</div>\u001b[39m\n\u001b[31m    <div class=\\\"share-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-expense\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🧾</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Expenses</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-recce\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">📍</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Recce</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-installation\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🔧</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Installation</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     TEAM PINs  (admin only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-team\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-back\\\" style=\\\"font-size:20px\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Team PINs</h1><div class=\\\"sub\\\">Set employee login PINs</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-refresh\\\" title=\\\"Refresh\\\">🔄</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"info-note\\\">PINs are stored encrypted — current PIN cannot be viewed. You can set a new PIN for any employee below.</div>\u001b[39m\n\u001b[31m    <div id=\\\"team-list\\\"></div>·\u001b[39m\n\u001b[31m    <!-- Icon Admin — Harish & Pramod only -->\u001b[39m\n\u001b[31m    <div id=\\\"icon-admin-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:8px\\\">🎨 PWA Icons (Admin)</div>\u001b[39m\n\u001b[31m      <div class=\\\"info-note\\\" style=\\\"margin-bottom:10px\\\">Upload a new icon for any PWA. Upload both 192×192 and 512×512 sizes. PNG/JPG/WEBP accepted — auto-resized on server.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;flex-direction:column;gap:10px\\\">·\u001b[39m\n\u001b[31m        <!-- Secret management -->\u001b[39m\n\u001b[31m        <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:12px;padding:14px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px\\\">🔑 Admin Secret</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:center;gap:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"font-family:monospace;font-size:13px;color:var(--accent);background:var(--bg);\u001b[39m\n\u001b[31m              border-radius:6px;padding:8px 12px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\" id=\\\"icon-secret-display\\\">—</div>\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"toggleSecretVisible()\\\" id=\\\"btn-secret-show\\\">Show</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:10px\\\">Change secret (min 16 chars):</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m            <input id=\\\"icon-new-secret\\\" type=\\\"text\\\" placeholder=\\\"Enter new secret…\\\" style=\\\"flex:1;background:var(--bg);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m              padding:10px 12px;border-radius:8px;font-size:13px;font-family:monospace;outline:none\\\">\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"changeIconSecret()\\\" style=\\\"white-space:nowrap\\\">Update</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div id=\\\"icon-secret-err\\\" style=\\\"font-size:11px;color:var(--danger);min-height:14px;margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>·\u001b[39m\n\u001b[31m        <!-- Upload form -->\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Select PWA</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-pwa-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"hub\\\">Hub</option>\u001b[39m\n\u001b[31m            <option value=\\\"expense\\\">Expenses</option>\u001b[39m\n\u001b[31m            <option value=\\\"finance\\\">Finance</option>\u001b[39m\n\u001b[31m            <option value=\\\"installation\\\">Installation</option>\u001b[39m\n\u001b[31m            <option value=\\\"recce\\\">Recce</option>\u001b[39m\n\u001b[31m            <option value=\\\"dispatch\\\">Dispatch</option>\u001b[39m\n\u001b[31m            <option value=\\\"production\\\">Production</option>\u001b[39m\n\u001b[31m            <option value=\\\"vehicle\\\">Vehicle Log</option>\u001b[39m\n\u001b[31m            <option value=\\\"vrs\\\">Vehicle Request</option>\u001b[39m\n\u001b[31m            <option value=\\\"sales\\\">Sales</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Icon Size</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-size-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"192\\\">192 × 192 (standard)</option>\u001b[39m\n\u001b[31m            <option value=\\\"512\\\">512 × 512 (large)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Choose Image File</label>\u001b[39m\n\u001b[31m          <input type=\\\"file\\\" id=\\\"icon-file-input\\\" accept=\\\"image/png,image/jpeg,image/webp\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:14px;width:100%\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-preview-wrap\\\" style=\\\"display:none;text-align:center\\\">\u001b[39m\n\u001b[31m          <img id=\\\"icon-preview-img\\\" style=\\\"width:96px;height:96px;border-radius:16px;border:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-upload-err\\\" style=\\\"font-size:12px;color:var(--danger);min-height:16px\\\"></div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" onclick=\\\"uploadIcon()\\\">Upload Icon</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     MODAL: Admin set PIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-setpin\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🔑 Set PIN — <span id=\\\"setpin-target-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>New PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Enter new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-confirm-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Confirm New PIN</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-confirm\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Repeat new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-auth-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Your own current PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"setpin-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-setpin-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-setpin-save\\\">Set PIN</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     JOBS — Campaign list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-jobs\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Jobs</h1><div class=\\\"sub\\\">Campaigns &amp; Tours</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh jobs\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-campaign\\\" title=\\\"New Campaign\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"jobs-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     CAMPAIGN DETAIL — Tour list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-campaign\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 id=\\\"campaign-hdr-title\\\">Campaign</h1><div class=\\\"sub\\\" id=\\\"campaign-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh campaign\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-tour\\\" title=\\\"New Tour\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"campaign-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Campaign -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-campaign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">📋 New Campaign</div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Year</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-year\\\" min=\\\"2024\\\" max=\\\"2030\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Phase #</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-phase\\\" value=\\\"1\\\" min=\\\"1\\\" max=\\\"99\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Brand <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-brand\\\" placeholder=\\\"e.g. Lenovo\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Client <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-client\\\" placeholder=\\\"e.g. Lenovo India\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Campaign Name <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-campaign\\\" placeholder=\\\"e.g. Chandigarh BTL\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Generated Campaign ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nc-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nc-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nc-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nc-save\\\">Create</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Tour -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-tour\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px;max-height:88vh;overflow-y:auto\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🗺️ New Tour</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Tour Title</div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nt-title\\\" placeholder=\\\"e.g. Chandigarh Week 1\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Start Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-start\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">End Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-end\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:6px\\\">Assign Employees <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-employees\\\" style=\\\"display:flex;flex-direction:column;gap:6px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"color:var(--muted);font-size:13px\\\">Loading…</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Notes</div>\u001b[39m\n\u001b[31m      <textarea id=\\\"nt-notes\\\" rows=\\\"2\\\" placeholder=\\\"Optional instructions or remarks\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px;font-family:inherit;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Tour ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-id-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nt-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nt-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nt-save\\\">Create Tour</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- UPDATE BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"update-banner\\\">\u001b[39m\n\u001b[31m  <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ub-text\\\">App update available</div>\u001b[39m\n\u001b[31m    <div class=\\\"ub-sub\\\">Tap to refresh and get the latest version</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ub-btn\\\" id=\\\"btn-update\\\">Update now</button>\u001b[39m\n\u001b[31m  <button class=\\\"ub-close\\\" id=\\\"btn-update-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- INSTALL BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"install-banner\\\">\u001b[39m\n\u001b[31m  <div class=\\\"ib-icon\\\">📲</div>\u001b[39m\n\u001b[31m  <div class=\\\"ib-text\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ib-title\\\">Install 360LM Hub</div>\u001b[39m\n\u001b[31m    <div class=\\\"ib-sub\\\">Add to home screen for the best experience</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ib-btn\\\" id=\\\"btn-install\\\">Install</button>\u001b[39m\n\u001b[31m  <button class=\\\"ib-close\\\" id=\\\"btn-install-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: AI Config (mgmt only) -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-ai\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🤖 AI Config — Screenshot Extraction</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Tesseract OCR</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-tesseract\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Extraction Fallback</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-enabled\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Model</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-model\\\">\u001b[39m\n\u001b[31m        <option value=\\\"haiku\\\">Haiku (fast, cost-effective)</option>\u001b[39m\n\u001b[31m        <option value=\\\"sonnet\\\">Sonnet (higher accuracy)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-ai-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-ai-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"border-top:1px solid var(--border);padding-top:12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">API Key Health</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px;margin-bottom:10px\\\">Live check — result is always the same for all users</div>\u001b[39m\n\u001b[31m      <div id=\\\"api-key-status-wrap\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-check-keys\\\" style=\\\"margin-top:10px;width:100%\\\">Check API Keys</button>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:14px;border-top:1px solid var(--border);padding-top:12px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">View Usage &amp; Billing</div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Anthropic</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/usage\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Google Maps</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/google/maps-apis/api-list\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: Hub Quick-Action Config -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-qa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">⚡ Quick Actions</div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-config-list\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-cfg-err\\\" style=\\\"color:var(--danger);font-size:13px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-qa-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-qa-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SCRIPT\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31m/* ── CONFIG ── */\u001b[39m\n\u001b[31mconst CFG = {\u001b[39m\n\u001b[31m  api:        '/db',\u001b[39m\n\u001b[31m  apiHeaders: { 'Content-Type': 'application/json', 'Accept-Profile': 'expense' },\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31m/* ── SERVER ACCESS MAP (fetched from hub-access.json, overrides ACCESS for non-mgmt) ── */\u001b[39m\n\u001b[31mlet _serverAccess = {};\u001b[39m\n\u001b[31masync function loadHubAccess() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/hub/hub-access.json?t=' + Date.now());\u001b[39m\n\u001b[31m    if (r.ok) _serverAccess = await r.json();\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── ROLE ACCESS MAP (hardcoded fallback) ── */\u001b[39m\n\u001b[31mconst ACCESS = {\u001b[39m\n\u001b[31m  harish:     ['expense','installation','finance','recce','dispatch','production','jobs','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  pramod:     ['expense','installation','finance','recce','dispatch','production','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  rakesh:     ['installation','expense','production','jobs','vehicle','vrs','sales','hr'],\u001b[39m\n\u001b[31m  ashok:      ['expense','production','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  shambhu:    ['finance','expense','vehicle','vrs','stores','activity','hr'],\u001b[39m\n\u001b[31m  gurpreet:   ['expense','dispatch','vehicle','vrs','stores','hr'],\u001b[39m\n\u001b[31m  sachin:     ['installation','expense','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  mukesh:     ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  umashankar: ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  narender:   ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  amit:       ['vehicle','vrs','hr'],\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst TILES = [\u001b[39m\n\u001b[31m  { id:'expense',      label:'Expenses',     icon:'🧾', url:'/expense/',               live:true  },\u001b[39m\n\u001b[31m  { id:'installation', label:'Installation', icon:'🔧', url:'/installation/current/',  live:true  },\u001b[39m\n\u001b[31m  { id:'finance',      label:'Finance',      icon:'💰', url:'/finance/',               live:true  },\u001b[39m\n\u001b[31m  { id:'recce',        label:'Recce',        icon:'📍', url:'/recce/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'dispatch',     label:'Dispatch',     icon:'🚚', url:'/dispatch/',              live:true  },\u001b[39m\n\u001b[31m  { id:'production',   label:'Production',   icon:'⚙️', url:'/production/',            live:true  },\u001b[39m\n\u001b[31m  { id:'jobs',         label:'Jobs',         icon:'📋', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'vehicle',      label:'Vehicle Log',  icon:'🚐', url:'/vehicle/',               live:true  },\u001b[39m\n\u001b[31m  { id:'vrs',          label:'Veh. Request', icon:'📋', url:'/vrs/',                   live:true  },\u001b[39m\n\u001b[31m  { id:'sales',        label:'Sales',        icon:'💼', url:'/sales/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'stores',       label:'Stores',       icon:'📦', url:'/stores/',                live:true  },\u001b[39m\n\u001b[31m  { id:'client',       label:'Client',       icon:'👤', url:'/client/',                live:true  },\u001b[39m\n\u001b[31m  { id:'team-pins',    label:'Team PINs',    icon:'🔑', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'admin',        label:'Admin',        icon:'🛠️', url:'/admin/',                  live:true  },\u001b[39m\n\u001b[31m  { id:'activity',     label:'Activity',     icon:'🎯', url:'/activity/',               live:true  },\u001b[39m\n\u001b[31m  { id:'hr',           label:'HR',           icon:'👥', url:'/hr/',                     live:true  },\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31mconst MGMT = ['harish', 'pramod'];·\u001b[39m\n\u001b[31m/* ── STATE ── */\u001b[39m\n\u001b[31mlet currentUser     = null;\u001b[39m\n\u001b[31mlet pinBuffer       = '';\u001b[39m\n\u001b[31mlet setPinTarget    = null;\u001b[39m\n\u001b[31mlet pendingShare    = false;\u001b[39m\n\u001b[31mlet _loginAttempts  = 0;\u001b[39m\n\u001b[31mlet _lockoutUntil   = 0;\u001b[39m\n\u001b[31mlet _loginInFlight  = false;·\u001b[39m\n\u001b[31m/* ── UTILS ── */\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => { s.classList.remove('active'); s.style.display = ''; });\u001b[39m\n\u001b[31m  document.getElementById('screen-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet toastTimer;\u001b[39m\n\u001b[31mfunction toast(msg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.classList.add('show');\u001b[39m\n\u001b[31m  clearTimeout(toastTimer);\u001b[39m\n\u001b[31m  toastTimer = setTimeout(() => el.classList.remove('show'), 2800);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fmtINR(n) {\u001b[39m\n\u001b[31m  if (n >= 100000) return '₹' + (n / 100000).toFixed(1) + 'L';\u001b[39m\n\u001b[31m  if (n >= 1000)   return '₹' + (n / 1000).toFixed(1) + 'K';\u001b[39m\n\u001b[31m  return '₹' + Math.round(n);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SESSION ── */\u001b[39m\n\u001b[31mconst SESSION_KEY  = 'lm360-session';\u001b[39m\n\u001b[31mconst EXP_SES_KEY  = '360exp-session';\u001b[39m\n\u001b[31mconst SESSION_TTL  = 12 * 3600 * 1000;·\u001b[39m\n\u001b[31mfunction saveSession(user) {\u001b[39m\n\u001b[31m  const payload = { empId: user.id, name: user.name, role: user.role, loginAt: new Date().toISOString(), source: 'hub' };\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m  localStorage.setItem(EXP_SES_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearSession() {\u001b[39m\n\u001b[31m  localStorage.removeItem(SESSION_KEY);\u001b[39m\n\u001b[31m  localStorage.removeItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const raw = localStorage.getItem(SESSION_KEY) || localStorage.getItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m    if (!raw) return null;\u001b[39m\n\u001b[31m    const s = JSON.parse(raw);\u001b[39m\n\u001b[31m    if (!s.empId || !s.name || !s.role) return null;\u001b[39m\n\u001b[31m    if (Date.now() - new Date(s.loginAt).getTime() > SESSION_TTL) { clearSession(); return null; }\u001b[39m\n\u001b[31m    return s;\u001b[39m\n\u001b[31m  } catch (_) { return null; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGIN FLOW ── */\u001b[39m\n\u001b[31masync function initLogin() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('login-name');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— Choose employee —</option>'; // clear before repopulating\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    emps.forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    [\u001b[39m\n\u001b[31m      { id:'amit',        name:'Amit'             },\u001b[39m\n\u001b[31m      { id:'ashok',       name:'Ashok'            },\u001b[39m\n\u001b[31m      { id:'gurpreet',    name:'Gurpreet'         },\u001b[39m\n\u001b[31m      { id:'harish',      name:'Harish Kumar Lal' },\u001b[39m\n\u001b[31m      { id:'mukesh',      name:'Mukesh'           },\u001b[39m\n\u001b[31m      { id:'narender',    name:'Narender'         },\u001b[39m\n\u001b[31m      { id:'pramod',      name:'Pramod Narang'    },\u001b[39m\n\u001b[31m      { id:'rakesh',      name:'Rakesh'           },\u001b[39m\n\u001b[31m      { id:'sachin',      name:'Sachin'           },\u001b[39m\n\u001b[31m      { id:'shambhu',     name:'Shambhu'          },\u001b[39m\n\u001b[31m      { id:'umashankar',  name:'Umashankar'       },\u001b[39m\n\u001b[31m    ].forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  document.querySelectorAll('.pin-key').forEach(btn =>\u001b[39m\n\u001b[31m    btn.addEventListener('click', () => handlePinKey(btn.dataset.k))\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handlePinKey(k) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (_loginInFlight) return;\u001b[39m\n\u001b[31m  // Lockout guard\u001b[39m\n\u001b[31m  if (_lockoutUntil && Date.now() < _lockoutUntil) {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    errEl.textContent = `Too many attempts — wait ${secs}s`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (_lockoutUntil) { _lockoutUntil = 0; _loginAttempts = 0; }\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (k === 'C') { pinBuffer = ''; }\u001b[39m\n\u001b[31m  else if (k === 'B') { pinBuffer = pinBuffer.slice(0, -1); }\u001b[39m\n\u001b[31m  else {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (!empId) { toast('Select your name first'); return; }\u001b[39m\n\u001b[31m    if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  if (pinBuffer.length === 4) {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (empId) attemptLogin(empId, pinBuffer);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updatePinDisplay() {\u001b[39m\n\u001b[31m  for (let i = 0; i < 4; i++)\u001b[39m\n\u001b[31m    document.getElementById('pd' + i).classList.toggle('filled', i < pinBuffer.length);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _shakePin() {\u001b[39m\n\u001b[31m  const pd = document.getElementById('pin-display');\u001b[39m\n\u001b[31m  pd.classList.add('shake');\u001b[39m\n\u001b[31m  setTimeout(() => pd.classList.remove('shake'), 400);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _startLockout() {\u001b[39m\n\u001b[31m  _lockoutUntil = Date.now() + 30000;\u001b[39m\n\u001b[31m  _loginAttempts = 0;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  const tick = () => {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    if (secs > 0) { errEl.textContent = `Too many attempts — wait ${secs}s`; setTimeout(tick, 500); }\u001b[39m\n\u001b[31m    else { _lockoutUntil = 0; errEl.textContent = ''; }\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  tick();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function attemptLogin(empId, pin) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  _loginInFlight = true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pin }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      _loginAttempts++;\u001b[39m\n\u001b[31m      _shakePin();\u001b[39m\n\u001b[31m      if (_loginAttempts >= 5) { _startLockout(); return; }\u001b[39m\n\u001b[31m      errEl.textContent = `Wrong PIN — ${5 - _loginAttempts} attempt${5 - _loginAttempts !== 1 ? 's' : ''} left`;\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    currentUser = { id: user.id, name: user.name, role: user.role };\u001b[39m\n\u001b[31m    saveSession(currentUser);\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — check connection';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    _loginInFlight = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── HOME SCREEN ── */\u001b[39m\n\u001b[31masync function buildHome() {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const body   = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  // Management users always use hardcoded ACCESS; others prefer server-side hub-access.json\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const tiles  = accessIds.map(id => TILES.find(t => t.id === id)).filter(Boolean);·\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"avatar\\\">${initials}</div>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1 style=\\\"font-size:16px\\\">${currentUser.name}</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">${currentUser.role}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${isMgmt ? '<button class=\\\"btn-icon\\\" id=\\\"btn-hub-ai\\\" title=\\\"AI Config\\\" style=\\\"font-size:18px\\\">🤖</button>' : ''}\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m  if (isMgmt) document.getElementById('btn-hub-ai').addEventListener('click', openHubAiConfig);·\u001b[39m\n\u001b[31m  if (isMgmt) {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"hub-main-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-dash\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">📊</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">Dashboard</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-tools\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">🧰</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">All Tools</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-header\\\">\u001b[39m\n\u001b[31m        <div class=\\\"section-title\\\" style=\\\"margin:0\\\">Quick Actions</div>\u001b[39m\n\u001b[31m        <button class=\\\"btn-icon\\\" id=\\\"btn-hub-qa-cfg\\\" title=\\\"Configure Quick Actions\\\" style=\\\"font-size:18px;padding:8px;min-width:44px;min-height:44px\\\">⚙️</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-grid\\\" id=\\\"hub-qa-grid\\\"></div>`;\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-dash').addEventListener('click',  () => enterHubDash(tiles));\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-tools').addEventListener('click', () => enterHubTools(tiles));\u001b[39m\n\u001b[31m    document.getElementById('btn-hub-qa-cfg').addEventListener('click', openHubQaConfig);\u001b[39m\n\u001b[31m    renderHubQA();\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"tile-grid field\\\" id=\\\"tile-grid\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"text-align:center;font-size:12px;color:var(--muted);padding-top:8px\\\">Tap a tile to open your tool</div>`;\u001b[39m\n\u001b[31m    renderTiles(tiles, 'field');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderTiles(tiles, mode) {\u001b[39m\n\u001b[31m  const grid = document.getElementById('tile-grid');\u001b[39m\n\u001b[31m  tiles.forEach(tile => {\u001b[39m\n\u001b[31m    const div = document.createElement('div');\u001b[39m\n\u001b[31m    div.className = 'tile ' + mode + (tile.live ? '' : ' coming');\u001b[39m\n\u001b[31m    div.innerHTML = `<div class=\\\"tile-icon\\\">${tile.icon}</div><div class=\\\"tile-label\\\">${tile.label}</div>` +\u001b[39m\n\u001b[31m      (tile.live ? '' : '<div class=\\\"soon-badge\\\">Soon</div>');\u001b[39m\n\u001b[31m    div.addEventListener('click', () => handleTile(tile));\u001b[39m\n\u001b[31m    grid.appendChild(div);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handleTile(tile) {\u001b[39m\n\u001b[31m  if (!tile.live) { toast('Coming soon'); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'team-pins') { enterTeamPins(); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'jobs')      { enterJobs();     return; }\u001b[39m\n\u001b[31m  window.location.href = tile.url;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT SUB-VIEWS ── */\u001b[39m\n\u001b[31mfunction _mgmtHeader(title) {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-subview-back\\\" style=\\\"font-size:20px;font-weight:900\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 style=\\\"font-size:16px\\\">${title}</h1></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-subview-back').addEventListener('click', () => buildHome());\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubDash(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('Dashboard');\u001b[39m\n\u001b[31m  const hdr = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const refreshBtn = document.createElement('button');\u001b[39m\n\u001b[31m  refreshBtn.className = 'btn-icon';\u001b[39m\n\u001b[31m  refreshBtn.title = 'Refresh dashboard';\u001b[39m\n\u001b[31m  refreshBtn.setAttribute('aria-label', 'Refresh dashboard');\u001b[39m\n\u001b[31m  refreshBtn.style.cssText = 'font-size:18px';\u001b[39m\n\u001b[31m  refreshBtn.textContent = '🔄';\u001b[39m\n\u001b[31m  refreshBtn.addEventListener('click', loadKPIs);\u001b[39m\n\u001b[31m  hdr.insertBefore(refreshBtn, hdr.querySelector('#btn-logout'));\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Actions Required</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-actions\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Operations</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row kpi-4col\\\" id=\\\"kpi-ops\\\">${skel}${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Production</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-prod\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Installation</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-inst\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">HR</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-hr\\\">${skel}${skel}${skel}</div>`;\u001b[39m\n\u001b[31m  loadKPIs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubTools(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('All Tools');\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  body.innerHTML = `<div class=\\\"tile-grid mgmt\\\" id=\\\"tile-grid\\\"></div>`;\u001b[39m\n\u001b[31m  renderTiles(tiles, 'mgmt');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT AI CONFIG ── */\u001b[39m\n\u001b[31mconst HUB_FIN_HDR = { 'Content-Type': 'application/json', 'Accept-Profile': 'finance' };·\u001b[39m\n\u001b[31masync function openHubAiConfig() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r    = await fetch(`${CFG.api}/upi_config`, { headers: HUB_FIN_HDR });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const get  = key => rows.find(x => x.key === key)?.value;\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-tesseract').value = get('tesseract_enabled') || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-enabled').value   = get('ai_enabled')        || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-model').value     = get('ai_model')           || 'haiku';\u001b[39m\n\u001b[31m  } catch(_) { toast('Could not load AI config'); return; }\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-ai').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveHubAiConfig() {\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-hub-ai-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  const pairs = [\u001b[39m\n\u001b[31m    { key:'tesseract_enabled', value: document.getElementById('hub-ai-tesseract').value },\u001b[39m\n\u001b[31m    { key:'ai_enabled',        value: document.getElementById('hub-ai-enabled').value },\u001b[39m\n\u001b[31m    { key:'ai_model',          value: document.getElementById('hub-ai-model').value },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const results = await Promise.all(pairs.map(p =>\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/upi_config?key=eq.${p.key}`, {\u001b[39m\n\u001b[31m        method: 'PATCH',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'finance', 'Prefer': 'return=minimal' },\u001b[39m\n\u001b[31m        body: JSON.stringify({ value: p.value }),\u001b[39m\n\u001b[31m      })\u001b[39m\n\u001b[31m    ));\u001b[39m\n\u001b[31m    if (results.some(r => !r.ok)) throw new Error('partial failure');\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show');\u001b[39m\n\u001b[31m    toast('✅ AI config saved');\u001b[39m\n\u001b[31m  } catch(_) { toast('Save failed'); }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Save'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function checkApiKeyStatus() {\u001b[39m\n\u001b[31m  const btn  = document.getElementById('btn-check-keys');\u001b[39m\n\u001b[31m  const wrap = document.getElementById('api-key-status-wrap');\u001b[39m\n\u001b[31m  btn.textContent = 'Checking…';\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  wrap.innerHTML = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/tour-ai/key-status');\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Proxy error ' + r.status);\u001b[39m\n\u001b[31m    const d = await r.json();\u001b[39m\n\u001b[31m    function mkBadge(info, label, link) {\u001b[39m\n\u001b[31m      const col  = info.ok ? 'var(--success)' : 'var(--danger)';\u001b[39m\n\u001b[31m      const icon = info.ok ? '✅' : '❌';\u001b[39m\n\u001b[31m      const note = info.ok ? (info.model || 'Valid') : (info.error || 'Error');\u001b[39m\n\u001b[31m      const hint = info.hint ? ' · key ' + info.hint : '';\u001b[39m\n\u001b[31m      return `<div style=\\\"background:var(--card);border:1px solid var(--border);border-left:3px solid ${col};border-radius:8px;padding:10px 12px;display:flex;align-items:center;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:13px;font-weight:600;color:var(--text)\\\">${icon} ${label}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${note}${hint}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <a href=\\\"${link}\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:11px;color:var(--info);text-decoration:none;white-space:nowrap\\\">Billing ↗</a>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    wrap.innerHTML =\u001b[39m\n\u001b[31m      mkBadge(d.anthropic, 'Anthropic API',    'https://console.anthropic.com/settings/billing') +\u001b[39m\n\u001b[31m      mkBadge(d.maps,      'Google Maps API',  'https://console.cloud.google.com/billing');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    wrap.innerHTML = `<div style=\\\"font-size:12px;color:var(--danger)\\\">Error: ${e.message}</div>`;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.textContent = 'Check Again';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT QUICK ACTIONS ── */\u001b[39m\n\u001b[31mconst HUB_QA_DEFS = [\u001b[39m\n\u001b[31m  { id:'offer',       icon:'📋', label:'New Offer',    accessId:'sales',        fn:() => { window.location.href='/sales/'; } },\u001b[39m\n\u001b[31m  { id:'paytm',       icon:'📱', label:'UPI Pay',       accessId:'finance',      fn:() => { window.location.href='/finance/?qa=upi'; } },\u001b[39m\n\u001b[31m  { id:'expense',     icon:'🧾', label:'Expenses',      accessId:'expense',      fn:() => { window.location.href='/expense/'; } },\u001b[39m\n\u001b[31m  { id:'finance',     icon:'💰', label:'Finance',       accessId:'finance',      fn:() => { window.location.href='/finance/'; } },\u001b[39m\n\u001b[31m  { id:'dispatch',    icon:'🚚', label:'Dispatch',      accessId:'dispatch',     fn:() => { window.location.href='/dispatch/'; } },\u001b[39m\n\u001b[31m  { id:'stores',      icon:'📦', label:'Stores',        accessId:'stores',       fn:() => { window.location.href='/stores/'; } },\u001b[39m\n\u001b[31m  { id:'recce',       icon:'📍', label:'Recce',         accessId:'recce',        fn:() => { window.location.href='/recce/'; } },\u001b[39m\n\u001b[31m  { id:'production',  icon:'⚙️', label:'Production',   accessId:'production',   fn:() => { window.location.href='/production/'; } },\u001b[39m\n\u001b[31m  { id:'tours',       icon:'🗺️', label:'Tour Plan',   accessId:'installation', fn:() => { window.location.href='/tour-planner/'; } },\u001b[39m\n\u001b[31m  { id:'installation',icon:'🔧', label:'Installation',  accessId:'installation', fn:() => { window.location.href='/installation/current/'; } },\u001b[39m\n\u001b[31m  { id:'activity',    icon:'🎯', label:'Activity',      accessId:'activity',     fn:() => { window.location.href='/activity/'; } },\u001b[39m\n\u001b[31m];\u001b[39m\n\u001b[31mconst HUB_QA_DEFAULT = ['offer', 'paytm'];\u001b[39m\n\u001b[31mconst HUB_QA_STORE   = '360lm-hub-qa';\u001b[39m\n\u001b[31mlet   _hubQaTempSel  = [];·\u001b[39m\n\u001b[31mfunction getHubQA() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(HUB_QA_STORE));\u001b[39m\n\u001b[31m    if (Array.isArray(s) && s.length) return s;\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m  return [...HUB_QA_DEFAULT];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHubQA() {\u001b[39m\n\u001b[31m  const ids  = getHubQA();\u001b[39m\n\u001b[31m  const grid = document.getElementById('hub-qa-grid');\u001b[39m\n\u001b[31m  if (!grid) return;\u001b[39m\n\u001b[31m  if (!ids.length) {\u001b[39m\n\u001b[31m    grid.innerHTML = `<div style=\\\"color:var(--muted);font-size:13px;grid-column:1/-1;\u001b[39m\n\u001b[31m      text-align:center;padding:16px\\\">Tap ⚙️ to add quick actions</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  grid.innerHTML = ids.map(id => {\u001b[39m\n\u001b[31m    const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!d) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-tile\\\" onclick=\\\"hubQaRun('${id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction hubQaRun(id) {\u001b[39m\n\u001b[31m  const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m  if (d) d.fn();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openHubQaConfig() {\u001b[39m\n\u001b[31m  _hubQaTempSel = [...getHubQA()];\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const visibleDefs = HUB_QA_DEFS.filter(d => accessIds.includes(d.accessId));\u001b[39m\n\u001b[31m  const list = document.getElementById('hub-qa-config-list');\u001b[39m\n\u001b[31m  list.innerHTML = visibleDefs.map(d => {\u001b[39m\n\u001b[31m    const sel = _hubQaTempSel.includes(d.id);\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-item${sel?' selected':''}\\\" id=\\\"hub-qa-item-${d.id}\\\"\u001b[39m\n\u001b[31m        onclick=\\\"toggleHubQaItem('${d.id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-toggle\\\"></div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleHubQaItem(id) {\u001b[39m\n\u001b[31m  const idx = _hubQaTempSel.indexOf(id);\u001b[39m\n\u001b[31m  if (idx >= 0) {\u001b[39m\n\u001b[31m    _hubQaTempSel.splice(idx, 1);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    if (_hubQaTempSel.length >= 6) {\u001b[39m\n\u001b[31m      document.getElementById('hub-qa-cfg-err').textContent = 'Maximum 6 quick actions';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _hubQaTempSel.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  const el = document.getElementById('hub-qa-item-' + id);\u001b[39m\n\u001b[31m  if (el) el.className = 'hub-qa-item' + (_hubQaTempSel.includes(id) ? ' selected' : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction saveHubQaConfig() {\u001b[39m\n\u001b[31m  localStorage.setItem(HUB_QA_STORE, JSON.stringify(_hubQaTempSel));\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.remove('show');\u001b[39m\n\u001b[31m  renderHubQA();\u001b[39m\n\u001b[31m  toast('✅ Quick actions updated');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── KPI FETCH ── */\u001b[39m\n\u001b[31mfunction _hdr(schema) {\u001b[39m\n\u001b[31m  return { 'Content-Type': 'application/json', 'Accept-Profile': schema };\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _safe(p) { return p.catch(() => null); }·\u001b[39m\n\u001b[31masync function loadKPIs() {\u001b[39m\n\u001b[31m  const mon = firstOfMonth();\u001b[39m\n\u001b[31m  const curMonth = new Date().toISOString().slice(0, 7); // YYYY-MM\u001b[39m\n\u001b[31m  const todayIST = (() => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; })();·\u001b[39m\n\u001b[31m  // Reset sections to loading skeletons before fetching (makes refresh visible)\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  [['kpi-actions',3],['kpi-ops',4],['kpi-prod',3],['kpi-inst',3],['kpi-hr',3]].forEach(([id,n]) => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    if (el) el.innerHTML = skel.repeat(n);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  const [\u001b[39m\n\u001b[31m    sheetsRes, claimsRes, salaryRes,\u001b[39m\n\u001b[31m    cashRes,   dispRes,   recceRes,  prodRes,\u001b[39m\n\u001b[31m    instJobsRes, instCtrsRes, storeItemsRes,\u001b[39m\n\u001b[31m    hrAttRes, hrEmpRes, hrConcernRes, hrSlipsRes\u001b[39m\n\u001b[31m  ] = await Promise.all([\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/sheets?status=in.(submitted,escalated)&select=sheet_id`, { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/approved_pending_reimbursement?select=sheet_id`,         { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_register?status=in.(pending,partial)&month=eq.${curMonth}&select=record_id`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/accounts?account_id=eq.petty-cash&select=current_balance`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/dispatches?status=eq.active&select=dispatch_id`,         { headers: _hdr('dispatch') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/submissions?submitted_at=gte.${mon}&select=sub_id`,      { headers: _hdr('recce') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/orders?status=not.in.(dispatched)&select=order_id,status`, { headers: _hdr('production') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/jobs?select=job_id,status`,                              { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/counters?select=counter_id,status`,                      { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/items?active=eq.true&select=id,current_stock,min_stock`, { headers: _hdr('stores') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance?date=eq.${todayIST}&status=eq.present&select=attendance_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/employee_list?select=id`,                               { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance_concerns?status=eq.open&select=concern_id`,  { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_slips?month=eq.${curMonth}&status=eq.finalized&select=slip_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m  ]);·\u001b[39m\n\u001b[31m  const sheets    = sheetsRes?.ok    ? await sheetsRes.json()    : [];\u001b[39m\n\u001b[31m  const claims    = claimsRes?.ok    ? await claimsRes.json()    : [];\u001b[39m\n\u001b[31m  const salary    = salaryRes?.ok    ? await salaryRes.json()    : [];\u001b[39m\n\u001b[31m  const cashArr   = cashRes?.ok      ? await cashRes.json()      : [];\u001b[39m\n\u001b[31m  const disps     = dispRes?.ok      ? await dispRes.json()      : [];\u001b[39m\n\u001b[31m  const recces    = recceRes?.ok     ? await recceRes.json()     : [];\u001b[39m\n\u001b[31m  const prods     = prodRes?.ok      ? await prodRes.json()      : [];\u001b[39m\n\u001b[31m  const instJobs  = instJobsRes?.ok  ? await instJobsRes.json()  : [];\u001b[39m\n\u001b[31m  const instCtrs  = instCtrsRes?.ok  ? await instCtrsRes.json()  : [];\u001b[39m\n\u001b[31m  const storeItems = storeItemsRes?.ok ? await storeItemsRes.json() : [];\u001b[39m\n\u001b[31m  const hrAtt     = hrAttRes?.ok     ? await hrAttRes.json()     : [];\u001b[39m\n\u001b[31m  const hrEmp     = hrEmpRes?.ok     ? await hrEmpRes.json()     : [];\u001b[39m\n\u001b[31m  const hrConcern = hrConcernRes?.ok ? await hrConcernRes.json() : [];\u001b[39m\n\u001b[31m  const hrSlips   = hrSlipsRes?.ok   ? await hrSlipsRes.json()   : [];·\u001b[39m\n\u001b[31m  const cash = Number(cashArr[0]?.current_balance || 0);·\u001b[39m\n\u001b[31m  const actionsEl = document.getElementById('kpi-actions');\u001b[39m\n\u001b[31m  const opsEl     = document.getElementById('kpi-ops');\u001b[39m\n\u001b[31m  const prodEl    = document.getElementById('kpi-prod');\u001b[39m\n\u001b[31m  const instEl    = document.getElementById('kpi-inst');\u001b[39m\n\u001b[31m  const hrEl      = document.getElementById('kpi-hr');\u001b[39m\n\u001b[31m  if (!actionsEl || !opsEl || !prodEl || !instEl) return;·\u001b[39m\n\u001b[31m  // ── Actions Required ────────────────────────────────────────────\u001b[39m\n\u001b[31m  actionsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${sheets.length ? 'alert' : 'ok'}\\\" data-href=\\\"/expense/?tab=approvals\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${sheets.length ? 'red' : 'green'}\\\">${sheets.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Expense Approvals</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${claims.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=claims\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${claims.length ? 'amber' : 'green'}\\\">${claims.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Claims to Reimburse</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${salary.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=salary\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${salary.length ? 'amber' : 'green'}\\\">${salary.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Salary Due This Month</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Operations ──────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const cashColor = cash < 2000 ? 'red' : cash < 5000 ? 'amber' : 'green';\u001b[39m\n\u001b[31m  const cashCard  = cash < 2000 ? 'alert' : cash < 5000 ? 'warn' : 'ok';\u001b[39m\n\u001b[31m  const lowStock = storeItems.filter(i => i.current_stock < i.min_stock).length;\u001b[39m\n\u001b[31m  opsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${cashCard}\\\" data-href=\\\"/finance/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${cashColor}\\\">${fmtINR(cash)}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">${cash < 0 ? '⚠️ ' : ''}Petty Cash</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/dispatch/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${disps.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Dispatches</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/recce/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${recces.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Recce This Month</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${lowStock > 0 ? 'alert' : 'ok'}\\\" data-href=\\\"/stores/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${lowStock > 0 ? 'red' : 'green'}\\\">${lowStock}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Low Stock Items</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Production ───────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const prodPending = prods.filter(o => o.status === 'pending').length;\u001b[39m\n\u001b[31m  const prodActive  = prods.filter(o => o.status === 'in_production' || o.status === 'quality_check').length;\u001b[39m\n\u001b[31m  const prodReady   = prods.filter(o => o.status === 'ready').length;\u001b[39m\n\u001b[31m  prodEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodPending ? 'warn' : 'ok'}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodPending ? 'amber' : 'green'}\\\">${prodPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Pending Orders</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${prodActive}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">In Production</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodReady ? 'ok' : ''}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodReady ? 'green' : ''}\\\">${prodReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Ready to Dispatch</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Installation ─────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const activeJobs  = instJobs.filter(j => j.status === 'active').length;\u001b[39m\n\u001b[31m  const ctrsPending = instCtrs.filter(c => c.status === 'recce_pending' || c.status === 'mockup_pending' || c.status === 'unclear').length;\u001b[39m\n\u001b[31m  const ctrsReady   = instCtrs.filter(c => c.status === 'ready').length;\u001b[39m\n\u001b[31m  instEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${activeJobs ? 'info' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${activeJobs ? 'blue' : 'green'}\\\">${activeJobs}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Jobs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsPending ? 'warn' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsPending ? 'amber' : 'green'}\\\">${ctrsPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Pending</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsReady ? 'ok' : ''}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsReady ? 'green' : ''}\\\">${ctrsReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Ready</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── HR ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31m  if (hrEl) {\u001b[39m\n\u001b[31m    const totalEmp   = hrEmp.length || 0;\u001b[39m\n\u001b[31m    const presentCnt = hrAtt.length;\u001b[39m\n\u001b[31m    const attPct     = totalEmp > 0 ? Math.round(presentCnt / totalEmp * 100) : 0;\u001b[39m\n\u001b[31m    const attColor   = attPct >= 80 ? 'green' : attPct >= 50 ? 'amber' : 'red';\u001b[39m\n\u001b[31m    const attCard    = attPct >= 80 ? 'ok' : attPct >= 50 ? 'warn' : 'alert';\u001b[39m\n\u001b[31m    const concernCnt = hrConcern.length;\u001b[39m\n\u001b[31m    const slipsDue   = hrSlips.length;\u001b[39m\n\u001b[31m    hrEl.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${attCard}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${attColor}\\\">${attPct}%</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Attendance Today</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${concernCnt ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${concernCnt ? 'amber' : 'green'}\\\">${concernCnt}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Open Concerns</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${slipsDue ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${slipsDue ? 'amber' : 'green'}\\\">${slipsDue}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Slips to Pay</div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Wire tap-to-navigate on all KPI cards\u001b[39m\n\u001b[31m  document.querySelectorAll('.kpi-card[data-href]').forEach(card => {\u001b[39m\n\u001b[31m    card.addEventListener('click', () => window.location.href = card.dataset.href);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction firstOfMonth() {\u001b[39m\n\u001b[31m  const d = new Date();\u001b[39m\n\u001b[31m  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGOUT ── */\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  pinBuffer   = '';\u001b[39m\n\u001b[31m  pendingShare = false;\u001b[39m\n\u001b[31m  _loginAttempts = 0; _lockoutUntil = 0; _loginInFlight = false;\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  document.getElementById('login-name').value = '';\u001b[39m\n\u001b[31m  document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m  showScreen('login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── TEAM PINs ── */\u001b[39m\n\u001b[31masync function enterTeamPins() {\u001b[39m\n\u001b[31m  showScreen('team');\u001b[39m\n\u001b[31m  await loadTeamPins();\u001b[39m\n\u001b[31m  // Show icon admin section for harish/pramod\u001b[39m\n\u001b[31m  const sec = document.getElementById('icon-admin-section');\u001b[39m\n\u001b[31m  if (sec) {\u001b[39m\n\u001b[31m    sec.style.display = MGMT.includes(currentUser.id) ? 'block' : 'none';\u001b[39m\n\u001b[31m    if (MGMT.includes(currentUser.id)) refreshSecretDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── ICON ADMIN ────────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst ICON_SECRET_KEY    = 'lm360_icon_secret';\u001b[39m\n\u001b[31mconst ICON_SECRET_INITIAL = 'nlr8Zsqw6XsD0rEXPQ_RfpYiOeNhH-9VglYKoIsElHE';·\u001b[39m\n\u001b[31mfunction getIconSecret() {\u001b[39m\n\u001b[31m  return localStorage.getItem(ICON_SECRET_KEY) || ICON_SECRET_INITIAL;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction setIconSecret(s) {\u001b[39m\n\u001b[31m  localStorage.setItem(ICON_SECRET_KEY, s);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet secretVisible = false;\u001b[39m\n\u001b[31mfunction refreshSecretDisplay() {\u001b[39m\n\u001b[31m  const el = document.getElementById('icon-secret-display');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  const s = getIconSecret();\u001b[39m\n\u001b[31m  el.textContent = secretVisible ? s : s.slice(0,4) + '••••••••••••••••' + s.slice(-4);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleSecretVisible() {\u001b[39m\n\u001b[31m  secretVisible = !secretVisible;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-secret-show');\u001b[39m\n\u001b[31m  if (btn) btn.textContent = secretVisible ? 'Hide' : 'Show';\u001b[39m\n\u001b[31m  refreshSecretDisplay();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function changeIconSecret() {\u001b[39m\n\u001b[31m  const newSecret = document.getElementById('icon-new-secret').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-secret-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (newSecret.length < 16) { errEl.textContent = 'Minimum 16 characters'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/change-secret', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret(), 'X-New-Secret': newSecret }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Failed');\u001b[39m\n\u001b[31m    setIconSecret(newSecret);\u001b[39m\n\u001b[31m    document.getElementById('icon-new-secret').value = '';\u001b[39m\n\u001b[31m    refreshSecretDisplay();\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = '✓ Secret updated';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mdocument.addEventListener('DOMContentLoaded', () => {\u001b[39m\n\u001b[31m  const fileInput = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  if (fileInput) {\u001b[39m\n\u001b[31m    fileInput.addEventListener('change', function() {\u001b[39m\n\u001b[31m      const wrap = document.getElementById('icon-preview-wrap');\u001b[39m\n\u001b[31m      const img  = document.getElementById('icon-preview-img');\u001b[39m\n\u001b[31m      if (this.files[0]) { img.src = URL.createObjectURL(this.files[0]); wrap.style.display = 'block'; }\u001b[39m\n\u001b[31m      else { wrap.style.display = 'none'; }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function uploadIcon() {\u001b[39m\n\u001b[31m  const pwa   = document.getElementById('icon-pwa-select').value;\u001b[39m\n\u001b[31m  const size  = document.getElementById('icon-size-select').value;\u001b[39m\n\u001b[31m  const input = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-upload-err');\u001b[39m\n\u001b[31m  errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (!input.files[0]) { errEl.textContent = 'Choose an image file first'; return; }\u001b[39m\n\u001b[31m  const formData = new FormData();\u001b[39m\n\u001b[31m  formData.append('pwa', pwa);\u001b[39m\n\u001b[31m  formData.append('size', size);\u001b[39m\n\u001b[31m  formData.append('icon', input.files[0]);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret() },\u001b[39m\n\u001b[31m      body: formData\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Upload failed');\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = `✓ Icon updated for ${pwa} (${size}px). Clear browser cache / reinstall to see new icon.`;\u001b[39m\n\u001b[31m    input.value = '';\u001b[39m\n\u001b[31m    document.getElementById('icon-preview-wrap').style.display = 'none';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadTeamPins() {\u001b[39m\n\u001b[31m  const list = document.getElementById('team-list');\u001b[39m\n\u001b[31m  list.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    list.innerHTML = '';\u001b[39m\n\u001b[31m    emps.forEach(emp => {\u001b[39m\n\u001b[31m      const row = document.createElement('div');\u001b[39m\n\u001b[31m      row.className = 'emp-row';\u001b[39m\n\u001b[31m      row.innerHTML = `\u001b[39m\n\u001b[31m        <div style=\\\"min-width:0;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:15px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.name}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.role}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-secondary btn-sm\\\" data-id=\\\"${emp.id}\\\" data-name=\\\"${emp.name}\\\">Set PIN</button>`;\u001b[39m\n\u001b[31m      row.querySelector('button').addEventListener('click', () => openSetPin(emp.id, emp.name));\u001b[39m\n\u001b[31m      list.appendChild(row);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    list.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openSetPin(targetId, targetName) {\u001b[39m\n\u001b[31m  setPinTarget = targetId;\u001b[39m\n\u001b[31m  document.getElementById('setpin-target-name').textContent = targetName;\u001b[39m\n\u001b[31m  ['setpin-new','setpin-confirm','setpin-auth'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-setpin').classList.add('show');\u001b[39m\n\u001b[31m  setTimeout(() => document.getElementById('setpin-new').focus(), 50);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function doSetPin() {\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('setpin-new').value.trim();\u001b[39m\n\u001b[31m  const conf    = document.getElementById('setpin-confirm').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('setpin-auth').value.trim();\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('setpin-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  if (!newPin || !conf || !authPin) { errEl.textContent = 'All fields are required'; return; }\u001b[39m\n\u001b[31m  if (newPin !== conf)              { errEl.textContent = 'New PINs do not match'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))     { errEl.textContent = 'PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-setpin-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_admin_id:  currentUser.id,\u001b[39m\n\u001b[31m        p_admin_pin: authPin,\u001b[39m\n\u001b[31m        p_target_id: setPinTarget,\u001b[39m\n\u001b[31m        p_new_pin:   newPin,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const err = await resp.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Failed to set PIN';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m    toast('✅ PIN updated for ' + document.getElementById('setpin-target-name').textContent);\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Set PIN';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ═══════════════════════════════════════════\u001b[39m\n\u001b[31m   JOBS — Campaign + Tour management\u001b[39m\n\u001b[31m   Access: harish, pramod (campaigns + tours)\u001b[39m\n\u001b[31m           rakesh (tours only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ */\u001b[39m\n\u001b[31mconst INST_RD = { 'Accept-Profile': 'installation' };\u001b[39m\n\u001b[31mconst INST_WR = { 'Content-Type':'application/json','Accept-Profile':'installation','Content-Profile':'installation' };·\u001b[39m\n\u001b[31mlet _currentCampaignId = null;\u001b[39m\n\u001b[31mlet _allEmployees      = [];\u001b[39m\n\u001b[31mlet _editTourId        = null;  // null = create mode, string = edit mode·\u001b[39m\n\u001b[31m// ── Helpers ──────────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _slugify(s) {\u001b[39m\n\u001b[31m  return s.toUpperCase().trim()\u001b[39m\n\u001b[31m    .replace(/[^A-Z0-9]+/g, '_')\u001b[39m\n\u001b[31m    .replace(/^_|_$/g, '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _buildCampaignId(year, brand, client, campaign, phase) {\u001b[39m\n\u001b[31m  const nn = String(phase || 1).padStart(2, '0');\u001b[39m\n\u001b[31m  return `${year}-${_slugify(brand)}-${_slugify(client)}-${_slugify(campaign)}-${nn}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _campStatusBadge(s) {\u001b[39m\n\u001b[31m  const map = { active:'ok', completed:'', on_hold:'warn' };\u001b[39m\n\u001b[31m  const col = { active:'green', completed:'', on_hold:'amber' };\u001b[39m\n\u001b[31m  return `<span class=\\\"kpi-val ${col[s]||''}\\\" style=\\\"font-size:12px;font-weight:700\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _tourStatusBadge(s) {\u001b[39m\n\u001b[31m  const colors = { active:'var(--success)', planned:'var(--info)', completed:'var(--muted)', cancelled:'var(--danger)' };\u001b[39m\n\u001b[31m  return `<span style=\\\"font-size:11px;font-weight:700;padding:2px 8px;border-radius:10px;\u001b[39m\n\u001b[31m    background:var(--card);color:${colors[s]||'var(--muted)'}\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _canCreateCampaign() { return ['harish','pramod'].includes(currentUser?.id); }\u001b[39m\n\u001b[31mfunction _canCreateTour()     { return ['harish','pramod','rakesh'].includes(currentUser?.id); }·\u001b[39m\n\u001b[31m// ── Jobs screen ───────────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterJobs() {\u001b[39m\n\u001b[31m  showScreen('jobs');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').style.display = _canCreateCampaign() ? '' : 'none';\u001b[39m\n\u001b[31m  await loadJobs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadJobs() {\u001b[39m\n\u001b[31m  const el = document.getElementById('jobs-body');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?order=created_at.desc`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?select=tour_id,campaign_id,status`, { headers: INST_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const campaigns = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours     = tResp.ok ? await tResp.json() : [];·\u001b[39m\n\u001b[31m    if (!campaigns.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty\\\"><div style=\\\"font-size:32px\\\">📋</div><div>No campaigns yet</div></div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    el.innerHTML = campaigns.map(c => {\u001b[39m\n\u001b[31m      const cTours  = tours.filter(t => t.campaign_id === c.campaign_id);\u001b[39m\n\u001b[31m      const active  = cTours.filter(t => t.status === 'active').length;\u001b[39m\n\u001b[31m      const total   = cTours.length;\u001b[39m\n\u001b[31m      const statusColors = { active:'var(--success)', on_hold:'var(--accent2)', completed:'var(--muted)' };\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card tappable\\\" style=\\\"padding:14px\\\" onclick=\\\"enterCampaign('${c.campaign_id}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:flex-start;gap:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:15px;font-weight:700\\\">${c.brand} — ${c.campaign_name}\u001b[39m\n\u001b[31m                ${c.phase > 1 ? `<span style=\\\"font-size:11px;color:var(--muted)\\\"> Phase ${c.phase}</span>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:2px\\\">${c.client} · ${c.year}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:4px\\\">\u001b[39m\n\u001b[31m                ${total} tour${total!==1?'s':''} · ${active} active\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;align-items:flex-end;gap:6px\\\">\u001b[39m\n\u001b[31m              <span style=\\\"font-size:11px;font-weight:700;color:${statusColors[c.status]||'var(--muted)'}\\\">\u001b[39m\n\u001b[31m                ${(c.status||'').replace('_',' ')}\u001b[39m\n\u001b[31m              </span>\u001b[39m\n\u001b[31m              <span style=\\\"font-size:18px;color:var(--muted)\\\">›</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-top:8px;word-break:break-all\\\">${c.campaign_id}</div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign detail ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterCampaign(campaignId) {\u001b[39m\n\u001b[31m  _currentCampaignId = campaignId;\u001b[39m\n\u001b[31m  showScreen('campaign');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').style.display = _canCreateTour() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-sub').textContent   = '';\u001b[39m\n\u001b[31m  await loadCampaign(campaignId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCampaign(campaignId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('campaign-body');\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp, empResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(campaignId)}&order=start_date`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const camps = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours = tResp.ok ? await tResp.json() : [];\u001b[39m\n\u001b[31m    _allEmployees = empResp.ok ? await empResp.json() : [];·\u001b[39m\n\u001b[31m    if (!camps.length) { body.innerHTML = '<div class=\\\"empty\\\">Campaign not found</div>'; return; }\u001b[39m\n\u001b[31m    const c = camps[0];·\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-title').textContent = `${c.brand} — ${c.campaign_name}`;\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-sub').textContent   = `${c.client} · ${c.year}${c.phase>1?' · Phase '+c.phase:''}`;·\u001b[39m\n\u001b[31m    const empMap = {};\u001b[39m\n\u001b[31m    _allEmployees.forEach(e => empMap[e.id] = e.name);·\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"padding:12px 14px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Campaign ID</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:10px;color:var(--muted);word-break:break-all;text-align:right;max-width:65%\\\">${c.campaign_id}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Client</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:13px;font-weight:600\\\">${c.client}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${_canCreateCampaign() ? `\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div class=\\\"section-title\\\" style=\\\"margin-bottom:6px\\\">Campaign Status</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m            ${['active','on_hold','completed'].map(s =>\u001b[39m\n\u001b[31m              `<button class=\\\"status-chip${c.status===s?' sel':''}\\\" onclick=\\\"updateCampaignStatus('${c.campaign_id}','${s}')\\\">\u001b[39m\n\u001b[31m                ${s.replace('_',' ')}\u001b[39m\n\u001b[31m              </button>`).join('')}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>` : ''}\u001b[39m\n\u001b[31m      </div>·\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:4px\\\">Tours (${tours.length})</div>\u001b[39m\n\u001b[31m      ${tours.length === 0 ? `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\">\u001b[39m\n\u001b[31m        <div>No tours yet</div>\u001b[39m\n\u001b[31m        ${_canCreateTour() ? '<div style=\\\"font-size:12px\\\">Tap ＋ to add the first tour</div>' : ''}\u001b[39m\n\u001b[31m      </div>` :\u001b[39m\n\u001b[31m      tours.map(t => {\u001b[39m\n\u001b[31m        const empNames = (t.assigned_employees || []).map(id => empMap[id] || id).join(', ') || 'No one assigned';\u001b[39m\n\u001b[31m        const dates    = t.start_date ? `${t.start_date} → ${t.end_date||'?'}` : 'Dates not set';\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m          <div class=\\\"card\\\" style=\\\"padding:14px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;align-items:flex-start;gap:8px\\\">\u001b[39m\n\u001b[31m              <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m                <div style=\\\"font-size:14px;font-weight:700\\\">${t.title || t.tour_id}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">📅 ${dates}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">👥 ${empNames}</div>\u001b[39m\n\u001b[31m                ${t.notes ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">📝 ${t.notes}</div>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              ${_tourStatusBadge(t.status)}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            ${_canCreateTour() ? `\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;gap:6px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m              ${['planned','active','completed'].map(s =>\u001b[39m\n\u001b[31m                `<button class=\\\"status-chip${t.status===s?' sel':''}\\\"\u001b[39m\n\u001b[31m                  onclick=\\\"updateTourStatus('${t.tour_id}','${s}')\\\">${s}</button>`).join('')}\u001b[39m\n\u001b[31m              <button class=\\\"status-chip\\\" onclick=\\\"openEditTour('${t.tour_id}')\\\"\u001b[39m\n\u001b[31m                style=\\\"margin-left:auto;background:var(--surface);border-color:#3a3a6e;color:#8888ff\\\">\u001b[39m\n\u001b[31m                ✏️ Edit\u001b[39m\n\u001b[31m              </button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m            <div style=\\\"font-size:10px;color:var(--muted);margin-top:6px\\\">${t.tour_id}</div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m      <div style=\\\"height:20px\\\"></div>`;\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateTourStatus(tourId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Tour marked ${status}`);\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateCampaignStatus(campaignId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Campaign marked ${status.replace('_',' ')}`);\u001b[39m\n\u001b[31m    loadCampaign(campaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Campaign modal ────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewCampaign() {\u001b[39m\n\u001b[31m  if (!_canCreateCampaign()) { toast('Only Harish or Pramod can create campaigns'); return; }\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('nc-year').value    = new Date().getFullYear();\u001b[39m\n\u001b[31m  document.getElementById('nc-phase').value   = '1';\u001b[39m\n\u001b[31m  document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  document.getElementById('nc-err').textContent     = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-new-campaign').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateCampaignPreview() {\u001b[39m\n\u001b[31m  const year  = document.getElementById('nc-year').value;\u001b[39m\n\u001b[31m  const brand = document.getElementById('nc-brand').value;\u001b[39m\n\u001b[31m  const clt   = document.getElementById('nc-client').value;\u001b[39m\n\u001b[31m  const camp  = document.getElementById('nc-campaign').value;\u001b[39m\n\u001b[31m  const phase = document.getElementById('nc-phase').value;\u001b[39m\n\u001b[31m  if (brand && clt && camp) {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = _buildCampaignId(year, brand, clt, camp, phase);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewCampaign() {\u001b[39m\n\u001b[31m  const year   = Number(document.getElementById('nc-year').value);\u001b[39m\n\u001b[31m  const brand  = document.getElementById('nc-brand').value.trim();\u001b[39m\n\u001b[31m  const client = document.getElementById('nc-client').value.trim();\u001b[39m\n\u001b[31m  const cname  = document.getElementById('nc-campaign').value.trim();\u001b[39m\n\u001b[31m  const phase  = Number(document.getElementById('nc-phase').value) || 1;\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('nc-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const brandEl = document.getElementById('nc-brand');\u001b[39m\n\u001b[31m  const clientEl = document.getElementById('nc-client');\u001b[39m\n\u001b[31m  const cnameEl = document.getElementById('nc-campaign');\u001b[39m\n\u001b[31m  [brandEl, clientEl, cnameEl].forEach(el => el.style.borderColor = '');\u001b[39m\n\u001b[31m  let _missingField = false;\u001b[39m\n\u001b[31m  if (!brand)  { brandEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!client) { clientEl.style.borderColor = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!cname)  { cnameEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (_missingField) { errEl.textContent = 'Brand, Client and Campaign Name are required'; return; }\u001b[39m\n\u001b[31m  const campaignId = _buildCampaignId(year, brand, client, cname, phase);·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nc-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/campaigns`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ campaign_id:campaignId, year, brand, client, campaign_name:cname, phase, created_by:currentUser.id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || 'Failed to create';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show');\u001b[39m\n\u001b[31m    toast(`✅ Campaign created`);\u001b[39m\n\u001b[31m    loadJobs();\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Create'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Tour modal ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _buildEmpCheckboxes(assigned = []) {\u001b[39m\n\u001b[31m  return _allEmployees.map(e =>\u001b[39m\n\u001b[31m    `<label style=\\\"display:flex;align-items:center;gap:10px;padding:8px 10px;background:var(--card);\u001b[39m\n\u001b[31m      border:1px solid var(--border);border-radius:8px;cursor:pointer\\\">\u001b[39m\n\u001b[31m      <input type=\\\"checkbox\\\" value=\\\"${e.id}\\\" ${assigned.includes(e.id)?'checked':''} style=\\\"width:16px;height:16px;accent-color:var(--accent)\\\">\u001b[39m\n\u001b[31m      <span style=\\\"font-size:14px;font-weight:600\\\">${e.name}</span>\u001b[39m\n\u001b[31m      <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${e.role}</span>\u001b[39m\n\u001b[31m    </label>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openNewTour() {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = null;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '🗺️ New Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Create Tour';\u001b[39m\n\u001b[31m  document.getElementById('nt-title').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-start').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-end').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-notes').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(_currentCampaignId)}&select=tour_id`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const existing = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    const nn = String(existing.length + 1).padStart(2, '0');\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T${nn}`;\u001b[39m\n\u001b[31m  } catch(_) {\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T01`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (_allEmployees.length)\u001b[39m\n\u001b[31m    document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes();·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEditTour(tourId) {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = tourId;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '✏️ Edit Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Save Changes';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const tours = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    if (!tours.length) { toast('Tour not found'); return; }\u001b[39m\n\u001b[31m    const t = tours[0];\u001b[39m\n\u001b[31m    document.getElementById('nt-title').value = t.title || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-start').value = t.start_date || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-end').value   = t.end_date   || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-notes').value = t.notes      || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = tourId;\u001b[39m\n\u001b[31m    if (_allEmployees.length)\u001b[39m\n\u001b[31m      document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes(t.assigned_employees || []);\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to load tour'); return; }·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewTour() {\u001b[39m\n\u001b[31m  const title = document.getElementById('nt-title').value.trim();\u001b[39m\n\u001b[31m  const start = document.getElementById('nt-start').value || null;\u001b[39m\n\u001b[31m  const end   = document.getElementById('nt-end').value   || null;\u001b[39m\n\u001b[31m  const notes = document.getElementById('nt-notes').value.trim() || null;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('nt-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const checked = [...document.querySelectorAll('#nt-employees input:checked')].map(cb => cb.value);\u001b[39m\n\u001b[31m  if (!checked.length) { errEl.textContent = 'Assign at least one employee'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nt-save');\u001b[39m\n\u001b[31m  const wasEdit = !!_editTourId;\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = wasEdit ? 'Saving…' : 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editTourId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(_editTourId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      const tourId = document.getElementById('nt-id-preview').textContent;\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          tour_id: tourId, campaign_id: _currentCampaignId,\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes, status: 'planned',\u001b[39m\n\u001b[31m          created_by: currentUser.id,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || (_editTourId ? 'Failed to save' : 'Failed to create');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show');\u001b[39m\n\u001b[31m    toast(_editTourId ? '✅ Tour updated' : '✅ Tour created');\u001b[39m\n\u001b[31m    _editTourId = null;\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = wasEdit ? 'Save Changes' : 'Create Tour';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── EVENT WIRING ── */\u001b[39m\n\u001b[31mfunction wireEvents() {\u001b[39m\n\u001b[31m  // Jobs screens\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-back').addEventListener('click',     () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-refresh').addEventListener('click',  loadJobs);\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').addEventListener('click',  openNewCampaign);\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-back').addEventListener('click', () => showScreen('jobs'));\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-refresh').addEventListener('click', () => loadCampaign(_currentCampaignId));\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').addEventListener('click',      openNewTour);·\u001b[39m\n\u001b[31m  // New Campaign modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-save').addEventListener('click', saveNewCampaign);\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign','nc-year','nc-phase'].forEach(id =>\u001b[39m\n\u001b[31m    document.getElementById(id).addEventListener('input', updateCampaignPreview));·\u001b[39m\n\u001b[31m  // New Tour modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').addEventListener('click', saveNewTour);·\u001b[39m\n\u001b[31m  document.getElementById('btn-team-back').addEventListener('click', () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-team-refresh').addEventListener('click', loadTeamPins);\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-cancel').addEventListener('click', () => {\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-save').addEventListener('click', doSetPin);·\u001b[39m\n\u001b[31m  // Set PIN — progressive field reveal\u001b[39m\n\u001b[31m  document.getElementById('setpin-new').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const val = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const showConf = val.length === 4;\u001b[39m\n\u001b[31m    document.getElementById('setpin-confirm-group').style.display = showConf ? '' : 'none';\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m    if (!showConf) { document.getElementById('setpin-confirm').value = ''; document.getElementById('setpin-auth').value = ''; }\u001b[39m\n\u001b[31m    if (showConf) setTimeout(() => document.getElementById('setpin-confirm').focus(), 20);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const newVal = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const conf   = document.getElementById('setpin-confirm').value;\u001b[39m\n\u001b[31m    const showAuth = conf.length === 4 && conf === newVal;\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = showAuth ? '' : 'none';\u001b[39m\n\u001b[31m    if (!showAuth) document.getElementById('setpin-auth').value = '';\u001b[39m\n\u001b[31m    if (showAuth) setTimeout(() => document.getElementById('setpin-auth').focus(), 20);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // Hub AI config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-save').addEventListener('click',   saveHubAiConfig);\u001b[39m\n\u001b[31m  document.getElementById('btn-check-keys').addEventListener('click',    checkApiKeyStatus);·\u001b[39m\n\u001b[31m  // Hub QA config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-qa').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-save').addEventListener('click', saveHubQaConfig);·\u001b[39m\n\u001b[31m  // Share picker\u001b[39m\n\u001b[31m  document.getElementById('share-to-expense').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/expense/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-recce').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/recce/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-installation').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/installation/current/?share=1';\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-04: employee dropdown change resets PIN buffer and lockout\u001b[39m\n\u001b[31m  document.getElementById('login-name').addEventListener('change', () => {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-07: ESC key closes any open modal\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (e.key === 'Escape') {\u001b[39m\n\u001b[31m      document.querySelectorAll('.modal-overlay.show').forEach(m => m.classList.remove('show'));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-11: keyboard digit/backspace input on login screen\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (!document.getElementById('screen-login').classList.contains('active')) return;\u001b[39m\n\u001b[31m    if (e.key >= '0' && e.key <= '9') { handlePinKey(e.key); }\u001b[39m\n\u001b[31m    else if (e.key === 'Backspace') { e.preventDefault(); handlePinKey('B'); }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-02/B-08: re-check session TTL when user returns to the tab\u001b[39m\n\u001b[31m  document.addEventListener('visibilitychange', () => {\u001b[39m\n\u001b[31m    if (document.visibilityState === 'visible' && currentUser) {\u001b[39m\n\u001b[31m      if (!loadSession()) doLogout();\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── BOOT ── */\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  wireEvents();·\u001b[39m\n\u001b[31m  if (location.search.includes('share=1')) {\u001b[39m\n\u001b[31m    pendingShare = true;\u001b[39m\n\u001b[31m    history.replaceState({}, '', '/hub/');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Always populate the login dropdown (needed if user logs out later)\u001b[39m\n\u001b[31m  initLogin(); // fire-and-forget — does not block home screen path·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess) {\u001b[39m\n\u001b[31m    currentUser = { id: sess.empId, name: sess.name, role: sess.role };\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  setTimeout(() => showScreen('login'), 1500);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SERVICE WORKER: install + update ── */\u001b[39m\n\u001b[31mif ('serviceWorker' in navigator) {\u001b[39m\n\u001b[31m  navigator.serviceWorker.register('/hub/sw.js').then(reg => {·\u001b[39m\n\u001b[31m    // Install banner: show when browser signals app is installable\u001b[39m\n\u001b[31m    let deferredInstall = null;\u001b[39m\n\u001b[31m    window.addEventListener('beforeinstallprompt', e => {\u001b[39m\n\u001b[31m      e.preventDefault();\u001b[39m\n\u001b[31m      deferredInstall = e;\u001b[39m\n\u001b[31m      if (!window.matchMedia('(display-mode: standalone)').matches) {\u001b[39m\n\u001b[31m        const delay = document.getElementById('update-banner').classList.contains('show') ? 3500 : 0;\u001b[39m\n\u001b[31m        setTimeout(() => document.getElementById('install-banner').classList.add('show'), delay);\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    window.addEventListener('appinstalled', () => {\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install').addEventListener('click', async () => {\u001b[39m\n\u001b[31m      if (!deferredInstall) return;\u001b[39m\n\u001b[31m      deferredInstall.prompt();\u001b[39m\n\u001b[31m      await deferredInstall.userChoice;\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m    // Update banner: show when a new SW version is waiting to activate\u001b[39m\n\u001b[31m    function showUpdateBanner() {\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.add('show');\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (reg.waiting) showUpdateBanner();·\u001b[39m\n\u001b[31m    reg.addEventListener('updatefound', () => {\u001b[39m\n\u001b[31m      const newSW = reg.installing;\u001b[39m\n\u001b[31m      newSW.addEventListener('statechange', () => {\u001b[39m\n\u001b[31m        if (newSW.state === 'installed' && navigator.serviceWorker.controller) {\u001b[39m\n\u001b[31m          showUpdateBanner();\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    document.getElementById('btn-update').addEventListener('click', () => {\u001b[39m\n\u001b[31m      if (reg.waiting) {\u001b[39m\n\u001b[31m        reg.waiting.postMessage({ type: 'SKIP_WAITING' });\u001b[39m\n\u001b[31m        navigator.serviceWorker.addEventListener('controllerchange', () => {\u001b[39m\n\u001b[31m          window.location.reload();\u001b[39m\n\u001b[31m        }, { once: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-update-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m  }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mboot();\u001b[39m\n\u001b[31m</script>··\u001b[39m\n\u001b[31m</body></html>\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:331:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 331
                        },
                        "snippet": "  329 |     await page.goto(BASE_URL);\n  330 |     const src = await page.content();\n> 331 |     expect(src).toContain('recce-v10');\n      |                 ^\n  332 |     expect(src).not.toContain('recce-v9');\n  333 |   });\n  334 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 331
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"recce-v10\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html><html lang=\\\"en\\\"><head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#FF6B35\\\">\u001b[39m\n\u001b[31m<meta name=\\\"mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-capable\\\" content=\\\"yes\\\">\u001b[39m\n\u001b[31m<meta name=\\\"apple-mobile-web-app-status-bar-style\\\" content=\\\"black-translucent\\\">\u001b[39m\n\u001b[31m<title>360LM</title>\u001b[39m\n\u001b[31m<meta name=\\\"description\\\" content=\\\"360LM Operations Hub — employee tools\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/hub/manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"icon\\\" href=\\\"/hub/icons/icon-192.png\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--surface:#161616;--card:#1e1e1e;--border:#2a2a2a;\u001b[39m\n\u001b[31m  --accent:#FF6B35;--accent2:#FFB800;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --danger:#e74c3c;--success:#2ecc71;--info:#3498db;\u001b[39m\n\u001b[31m  --radius:12px;--font:'Segoe UI',system-ui,sans-serif;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:var(--font);min-height:100vh;overflow-x:hidden}\u001b[39m\n\u001b[31m.screen{display:none;min-height:100vh;flex-direction:column}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── HEADER ── */\u001b[39m\n\u001b[31m.header{background:var(--surface);border-bottom:1px solid var(--border);padding:14px 16px;\u001b[39m\n\u001b[31m  display:flex;align-items:center;gap:12px;position:sticky;top:0;z-index:10;flex-shrink:0}\u001b[39m\n\u001b[31m.header h1{font-size:18px;font-weight:700;color:var(--accent);flex:1}\u001b[39m\n\u001b[31m.header .sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.btn-icon{background:none;border:none;color:var(--muted);font-size:22px;cursor:pointer;\u001b[39m\n\u001b[31m  padding:4px;border-radius:8px;line-height:1;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m.btn-icon:active{background:var(--border)}·\u001b[39m\n\u001b[31m/* ── BODY ── */\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:16px}·\u001b[39m\n\u001b[31m/* ── SPLASH ── */\u001b[39m\n\u001b[31m#screen-splash{align-items:center;justify-content:center;gap:8px}\u001b[39m\n\u001b[31m.splash-logo{font-size:72px;font-weight:900;color:var(--accent);letter-spacing:-2px;line-height:1}\u001b[39m\n\u001b[31m.splash-sub{font-size:15px;color:var(--muted);letter-spacing:2px;text-transform:uppercase}\u001b[39m\n\u001b[31m.splash-dot{width:8px;height:8px;border-radius:50%;background:var(--accent);\u001b[39m\n\u001b[31m  animation:pulse 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes pulse{0%,100%{opacity:.3;transform:scale(.8)}50%{opacity:1;transform:scale(1)}}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  min-height:100dvh;padding:24px;gap:20px;overflow-y:auto}\u001b[39m\n\u001b[31m.login-logo{font-size:44px;font-weight:900;color:var(--accent);letter-spacing:-1px}\u001b[39m\n\u001b[31m.login-sub{font-size:13px;color:var(--muted);text-transform:uppercase;letter-spacing:2px}\u001b[39m\n\u001b[31m.form-group{width:100%;max-width:360px;display:flex;flex-direction:column;gap:8px}\u001b[39m\n\u001b[31m.form-group label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px}\u001b[39m\n\u001b[31mselect{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31mselect:focus{border-color:var(--accent)}·\u001b[39m\n\u001b[31m/* ── PIN PAD ── */\u001b[39m\n\u001b[31m.pin-display{display:flex;gap:14px;justify-content:center;margin:4px 0}\u001b[39m\n\u001b[31m.pin-dot{width:16px;height:16px;border-radius:50%;border:2px solid var(--muted);background:transparent;transition:.15s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.pin-pad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;max-width:260px;margin:0 auto;width:100%}\u001b[39m\n\u001b[31m.pin-key{background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  font-size:22px;font-weight:600;padding:16px;border-radius:var(--radius);cursor:pointer;\u001b[39m\n\u001b[31m  text-align:center;line-height:1;user-select:none}\u001b[39m\n\u001b[31m.pin-key:active{background:var(--border)}\u001b[39m\n\u001b[31m.pin-key.clr{color:var(--danger);font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.pin-key.back{color:var(--accent2)}\u001b[39m\n\u001b[31m.login-err{font-size:13px;color:var(--danger);text-align:center;min-height:18px}\u001b[39m\n\u001b[31m@keyframes shake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-6px)}40%,80%{transform:translateX(6px)}}\u001b[39m\n\u001b[31m.pin-display.shake{animation:shake .4s ease}·\u001b[39m\n\u001b[31m/* ── KPI CARDS ── */\u001b[39m\n\u001b[31m.kpi-row{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\u001b[39m\n\u001b[31m.kpi-4col{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m#home-body{gap:10px}\u001b[39m\n\u001b[31m.kpi-card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:10px 8px;text-align:center;border-left:3px solid var(--accent);cursor:default}\u001b[39m\n\u001b[31m.kpi-card.alert{border-left-color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-card.warn{border-left-color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-card.ok{border-left-color:var(--success)}\u001b[39m\n\u001b[31m.kpi-card.info{border-left-color:var(--info)}\u001b[39m\n\u001b[31m.kpi-val{font-size:18px;font-weight:900;color:var(--accent);line-height:1.2}\u001b[39m\n\u001b[31m.kpi-val.red{color:var(--danger)}\u001b[39m\n\u001b[31m.kpi-val.amber{color:var(--accent2)}\u001b[39m\n\u001b[31m.kpi-val.green{color:var(--success)}\u001b[39m\n\u001b[31m.kpi-val.blue{color:var(--info)}\u001b[39m\n\u001b[31m.kpi-lbl{font-size:10px;color:var(--muted);margin-top:4px;text-transform:uppercase;letter-spacing:.4px;line-height:1.3}\u001b[39m\n\u001b[31m.kpi-skel{background:var(--border);border-radius:4px;height:24px;margin-bottom:4px;\u001b[39m\n\u001b[31m  animation:shimmer 1.2s ease-in-out infinite}\u001b[39m\n\u001b[31m@keyframes shimmer{0%,100%{opacity:.5}50%{opacity:1}}·\u001b[39m\n\u001b[31m/* ── SECTION TITLE ── */\u001b[39m\n\u001b[31m.section-title{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.8px}·\u001b[39m\n\u001b[31m/* ── MGMT HOME: MAIN TILES ── */\u001b[39m\n\u001b[31m.hub-main-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px}\u001b[39m\n\u001b[31m.hub-main-tile{background:var(--card);border:2px solid var(--border);border-radius:18px;\u001b[39m\n\u001b[31m  padding:36px 12px 28px;text-align:center;cursor:pointer;transition:background .15s,border-color .15s}\u001b[39m\n\u001b[31m.hub-main-tile:active{background:#1a2a1a;border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-main-icon{font-size:40px;line-height:1;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-main-label{font-size:15px;font-weight:800;color:var(--text)}\u001b[39m\n\u001b[31m/* ── MGMT HOME: QUICK ACTIONS ── */\u001b[39m\n\u001b[31m.hub-qa-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}\u001b[39m\n\u001b[31m.hub-qa-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(88px,1fr));gap:10px}\u001b[39m\n\u001b[31m.hub-qa-tile{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px 8px;text-align:center;cursor:pointer;display:flex;flex-direction:column;\u001b[39m\n\u001b[31m  align-items:center;gap:6px;transition:background .15s}\u001b[39m\n\u001b[31m.hub-qa-tile:active{background:var(--border)}\u001b[39m\n\u001b[31m.hub-qa-icon{font-size:26px;line-height:1}\u001b[39m\n\u001b[31m.hub-qa-label{font-size:11px;font-weight:700;color:var(--text);line-height:1.3}\u001b[39m\n\u001b[31m.hub-qa-item{display:flex;align-items:center;gap:12px;padding:10px 0;\u001b[39m\n\u001b[31m  border-bottom:1px solid var(--border);cursor:pointer}\u001b[39m\n\u001b[31m.hub-qa-item:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.hub-qa-item-icon{font-size:22px;width:32px;text-align:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item-label{flex:1;font-size:14px;font-weight:600}\u001b[39m\n\u001b[31m.hub-qa-item-toggle{width:20px;height:20px;border-radius:4px;border:2px solid var(--border);\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle{background:var(--accent);border-color:var(--accent)}\u001b[39m\n\u001b[31m.hub-qa-item.selected .hub-qa-item-toggle::after{content:'✓';color:#fff;font-weight:700;font-size:12px}·\u001b[39m\n\u001b[31m/* ── TILE GRID ── */\u001b[39m\n\u001b[31m.tile-grid{display:grid;gap:12px}\u001b[39m\n\u001b[31m.tile-grid.mgmt{grid-template-columns:repeat(4,1fr)}\u001b[39m\n\u001b[31m.tile-grid.field{grid-template-columns:repeat(2,1fr)}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:18px 8px;display:flex;flex-direction:column;align-items:center;gap:8px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s;position:relative}\u001b[39m\n\u001b[31m.tile:active{background:var(--border);transform:scale(.97)}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px;line-height:1}\u001b[39m\n\u001b[31m.tile-label{font-size:11px;font-weight:700;color:var(--text);text-align:center;text-transform:uppercase;letter-spacing:.4px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-icon{font-size:24px}\u001b[39m\n\u001b[31m.tile.mgmt .tile-label{font-size:10px}\u001b[39m\n\u001b[31m.tile.coming{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.tile.coming:active{transform:none;background:var(--card)}\u001b[39m\n\u001b[31m.soon-badge{position:absolute;top:6px;right:6px;background:var(--accent2);color:#000;\u001b[39m\n\u001b[31m  font-size:8px;font-weight:800;padding:2px 5px;border-radius:20px;text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── USER BADGE ── */\u001b[39m\n\u001b[31m.user-badge{display:flex;align-items:center;gap:12px}\u001b[39m\n\u001b[31m.avatar{width:44px;height:44px;background:var(--accent);border-radius:50%;\u001b[39m\n\u001b[31m  display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:800;\u001b[39m\n\u001b[31m  color:#fff;flex-shrink:0;text-transform:uppercase}\u001b[39m\n\u001b[31m.user-name{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.user-role{font-size:12px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── TEAM PINS ── */\u001b[39m\n\u001b[31m.emp-row{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px}\u001b[39m\n\u001b[31m.btn{display:flex;align-items:center;justify-content:center;gap:6px;border:none;\u001b[39m\n\u001b[31m  border-radius:var(--radius);font-size:15px;font-weight:700;padding:12px 18px;\u001b[39m\n\u001b[31m  cursor:pointer;width:100%;transition:.15s;font-family:var(--font)}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--card);border:1px solid var(--border);color:var(--text)}\u001b[39m\n\u001b[31m.btn-sm{padding:7px 12px;font-size:12px;font-weight:700;width:auto;flex-shrink:0}\u001b[39m\n\u001b[31m.info-note{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:12px 14px;font-size:13px;color:var(--muted);line-height:1.5}·\u001b[39m\n\u001b[31m/* ── MODAL ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:200;\u001b[39m\n\u001b[31m  align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;\u001b[39m\n\u001b[31m  padding:24px;width:100%;max-width:480px;display:flex;flex-direction:column;gap:14px;\u001b[39m\n\u001b[31m  max-height:90dvh;overflow-y:auto;-webkit-overflow-scrolling:touch}\u001b[39m\n\u001b[31m.modal-title{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.modal-box input[type=password]{\u001b[39m\n\u001b[31m  width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  padding:14px;border-radius:var(--radius);font-size:16px;font-family:var(--font);outline:none}\u001b[39m\n\u001b[31m.modal-box input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.action-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px}·\u001b[39m\n\u001b[31m/* ── SHARE PICKER ── */\u001b[39m\n\u001b[31m#screen-share .body{align-items:center;justify-content:center;gap:24px}\u001b[39m\n\u001b[31m.share-prompt{font-size:15px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.share-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;width:100%;max-width:360px}\u001b[39m\n\u001b[31m.share-tile{background:var(--card);border:2px solid var(--border);border-radius:var(--radius);\u001b[39m\n\u001b[31m  padding:24px 8px;display:flex;flex-direction:column;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  cursor:pointer;user-select:none;transition:.15s}\u001b[39m\n\u001b[31m.share-tile:active{background:var(--border);transform:scale(.97);border-color:var(--accent)}\u001b[39m\n\u001b[31m.share-tile-icon{font-size:36px;line-height:1}\u001b[39m\n\u001b[31m.share-tile-label{font-size:12px;font-weight:700;color:var(--text);text-align:center;\u001b[39m\n\u001b[31m  text-transform:uppercase;letter-spacing:.5px}·\u001b[39m\n\u001b[31m/* ── INSTALL BANNER ── */\u001b[39m\n\u001b[31m#install-banner{display:none;position:fixed;bottom:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:linear-gradient(135deg,#1a1a1a,#0d2010);\u001b[39m\n\u001b[31m  border-top:1px solid var(--accent);padding:14px 16px;\u001b[39m\n\u001b[31m  align-items:center;gap:12px;box-shadow:0 -4px 24px rgba(0,0,0,.7)}\u001b[39m\n\u001b[31m#install-banner.show{display:flex}\u001b[39m\n\u001b[31m#install-banner .ib-icon{font-size:30px;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-text{flex:1}\u001b[39m\n\u001b[31m#install-banner .ib-title{font-size:14px;font-weight:700;color:var(--text)}\u001b[39m\n\u001b[31m#install-banner .ib-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#install-banner .ib-btn{background:var(--accent);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#install-banner .ib-close{background:none;border:none;color:var(--muted);font-size:20px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── UPDATE BANNER ── */\u001b[39m\n\u001b[31m#update-banner{display:none;position:fixed;top:0;left:0;right:0;z-index:400;\u001b[39m\n\u001b[31m  background:#1a2a1a;border-bottom:2px solid var(--success);\u001b[39m\n\u001b[31m  padding:12px 16px;align-items:center;gap:12px}\u001b[39m\n\u001b[31m#update-banner.show{display:flex}\u001b[39m\n\u001b[31m#update-banner .ub-text{flex:1;font-size:14px;font-weight:600;color:var(--text)}\u001b[39m\n\u001b[31m#update-banner .ub-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m#update-banner .ub-btn{background:var(--success);color:#fff;border:none;border-radius:8px;\u001b[39m\n\u001b[31m  padding:8px 14px;font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m#update-banner .ub-close{background:none;border:none;color:var(--muted);font-size:18px;cursor:pointer;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#222;color:#fff;padding:10px 20px;border-radius:20px;font-size:14px;font-weight:600;\u001b[39m\n\u001b[31m  opacity:0;transition:.3s;pointer-events:none;white-space:nowrap;z-index:500}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;\u001b[39m\n\u001b[31m  padding:40px;color:var(--muted);font-size:14px;gap:12px}\u001b[39m\n\u001b[31m.spinner{width:36px;height:36px;border:3px solid var(--border);border-top-color:var(--accent);\u001b[39m\n\u001b[31m  border-radius:50%;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* ── CARD (jobs / campaign screens) ── */\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius)}\u001b[39m\n\u001b[31m.card.tappable:active{border-color:var(--accent);opacity:.85}·\u001b[39m\n\u001b[31m/* ── STATUS CHIPS (tour / campaign) ── */\u001b[39m\n\u001b[31m.status-chip{font-size:11px;padding:4px 11px;border-radius:8px;cursor:pointer;\u001b[39m\n\u001b[31m  background:var(--card);color:var(--text);border:1px solid var(--border);font-weight:600;\u001b[39m\n\u001b[31m  font-family:var(--font);transition:.15s}\u001b[39m\n\u001b[31m.status-chip.sel{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.status-chip:active{opacity:.8}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SPLASH\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-splash\\\" class=\\\"screen active\\\">\u001b[39m\n\u001b[31m  <div class=\\\"splash-logo\\\">360LM</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-sub\\\">Operations Hub</div>\u001b[39m\n\u001b[31m  <div class=\\\"splash-dot\\\" style=\\\"margin-top:16px\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     LOGIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-login\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-wrap\\\">\u001b[39m\n\u001b[31m    <div class=\\\"login-logo\\\">360LM</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">Operations Hub</div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Select Your Name</label>\u001b[39m\n\u001b[31m      <select id=\\\"login-name\\\"><option value=\\\"\\\">— Choose employee —</option><option value=\\\"amit\\\">Amit</option><option value=\\\"ashok\\\">Ashok</option><option value=\\\"gurpreet\\\">Gurpreet</option><option value=\\\"harish\\\">Harish Kumar Lal</option><option value=\\\"mukesh\\\">Mukesh</option><option value=\\\"narender\\\">Narender</option><option value=\\\"pramod\\\">Pramod Narang</option><option value=\\\"rakesh\\\">Rakesh</option><option value=\\\"sachin\\\">Sachin</option><option value=\\\"shambhu\\\">Shambhu</option><option value=\\\"umashankar\\\">Umashankar</option></select>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m      <label style=\\\"text-align:center\\\">Enter PIN</label>\u001b[39m\n\u001b[31m      <div class=\\\"pin-display\\\" id=\\\"pin-display\\\">\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd0\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd1\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd2\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dot\\\" id=\\\"pd3\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"login-err\\\" id=\\\"login-err\\\"></div>\u001b[39m\n\u001b[31m      <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-1\\\" data-k=\\\"1\\\" aria-label=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-2\\\" data-k=\\\"2\\\" aria-label=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-3\\\" data-k=\\\"3\\\" aria-label=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-4\\\" data-k=\\\"4\\\" aria-label=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-5\\\" data-k=\\\"5\\\" aria-label=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-6\\\" data-k=\\\"6\\\" aria-label=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-7\\\" data-k=\\\"7\\\" aria-label=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-8\\\" data-k=\\\"8\\\" aria-label=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-9\\\" data-k=\\\"9\\\" aria-label=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key clr\\\" id=\\\"pk-C\\\" data-k=\\\"C\\\" aria-label=\\\"Clear\\\">CLR</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key\\\" id=\\\"pk-0\\\" data-k=\\\"0\\\" aria-label=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m        <button class=\\\"pin-key back\\\" id=\\\"pk-B\\\" data-k=\\\"B\\\" aria-label=\\\"Backspace\\\">⌫</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     HOME  (built dynamically)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\" id=\\\"home-header\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"home-body\\\">\u001b[39m\n\u001b[31m    <!-- filled by buildHome() -->\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SHARE PICKER\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-share\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>Share Image To</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">Choose where to send this photo</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"share-prompt\\\">Which tool should receive this image?</div>\u001b[39m\n\u001b[31m    <div class=\\\"share-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-expense\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🧾</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Expenses</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-recce\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">📍</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Recce</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"share-tile\\\" id=\\\"share-to-installation\\\">\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-icon\\\">🔧</div>\u001b[39m\n\u001b[31m        <div class=\\\"share-tile-label\\\">Installation</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     TEAM PINs  (admin only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-team\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-back\\\" style=\\\"font-size:20px\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Team PINs</h1><div class=\\\"sub\\\">Set employee login PINs</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-team-refresh\\\" title=\\\"Refresh\\\">🔄</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"info-note\\\">PINs are stored encrypted — current PIN cannot be viewed. You can set a new PIN for any employee below.</div>\u001b[39m\n\u001b[31m    <div id=\\\"team-list\\\"></div>·\u001b[39m\n\u001b[31m    <!-- Icon Admin — Harish & Pramod only -->\u001b[39m\n\u001b[31m    <div id=\\\"icon-admin-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:8px\\\">🎨 PWA Icons (Admin)</div>\u001b[39m\n\u001b[31m      <div class=\\\"info-note\\\" style=\\\"margin-bottom:10px\\\">Upload a new icon for any PWA. Upload both 192×192 and 512×512 sizes. PNG/JPG/WEBP accepted — auto-resized on server.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;flex-direction:column;gap:10px\\\">·\u001b[39m\n\u001b[31m        <!-- Secret management -->\u001b[39m\n\u001b[31m        <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:12px;padding:14px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px\\\">🔑 Admin Secret</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:center;gap:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"font-family:monospace;font-size:13px;color:var(--accent);background:var(--bg);\u001b[39m\n\u001b[31m              border-radius:6px;padding:8px 12px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\" id=\\\"icon-secret-display\\\">—</div>\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"toggleSecretVisible()\\\" id=\\\"btn-secret-show\\\">Show</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:10px\\\">Change secret (min 16 chars):</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m            <input id=\\\"icon-new-secret\\\" type=\\\"text\\\" placeholder=\\\"Enter new secret…\\\" style=\\\"flex:1;background:var(--bg);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m              padding:10px 12px;border-radius:8px;font-size:13px;font-family:monospace;outline:none\\\">\u001b[39m\n\u001b[31m            <button class=\\\"btn btn-secondary btn-sm\\\" onclick=\\\"changeIconSecret()\\\" style=\\\"white-space:nowrap\\\">Update</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div id=\\\"icon-secret-err\\\" style=\\\"font-size:11px;color:var(--danger);min-height:14px;margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>·\u001b[39m\n\u001b[31m        <!-- Upload form -->\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Select PWA</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-pwa-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"hub\\\">Hub</option>\u001b[39m\n\u001b[31m            <option value=\\\"expense\\\">Expenses</option>\u001b[39m\n\u001b[31m            <option value=\\\"finance\\\">Finance</option>\u001b[39m\n\u001b[31m            <option value=\\\"installation\\\">Installation</option>\u001b[39m\n\u001b[31m            <option value=\\\"recce\\\">Recce</option>\u001b[39m\n\u001b[31m            <option value=\\\"dispatch\\\">Dispatch</option>\u001b[39m\n\u001b[31m            <option value=\\\"production\\\">Production</option>\u001b[39m\n\u001b[31m            <option value=\\\"vehicle\\\">Vehicle Log</option>\u001b[39m\n\u001b[31m            <option value=\\\"vrs\\\">Vehicle Request</option>\u001b[39m\n\u001b[31m            <option value=\\\"sales\\\">Sales</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Icon Size</label>\u001b[39m\n\u001b[31m          <select id=\\\"icon-size-select\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:15px;outline:none\\\">\u001b[39m\n\u001b[31m            <option value=\\\"192\\\">192 × 192 (standard)</option>\u001b[39m\n\u001b[31m            <option value=\\\"512\\\">512 × 512 (large)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m          <label style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px\\\">Choose Image File</label>\u001b[39m\n\u001b[31m          <input type=\\\"file\\\" id=\\\"icon-file-input\\\" accept=\\\"image/png,image/jpeg,image/webp\\\" style=\\\"background:var(--card);border:1px solid var(--border);color:var(--text);padding:12px;border-radius:12px;font-size:14px;width:100%\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-preview-wrap\\\" style=\\\"display:none;text-align:center\\\">\u001b[39m\n\u001b[31m          <img id=\\\"icon-preview-img\\\" style=\\\"width:96px;height:96px;border-radius:16px;border:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"icon-upload-err\\\" style=\\\"font-size:12px;color:var(--danger);min-height:16px\\\"></div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" onclick=\\\"uploadIcon()\\\">Upload Icon</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     MODAL: Admin set PIN\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-setpin\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🔑 Set PIN — <span id=\\\"setpin-target-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>New PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Enter new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-confirm-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Confirm New PIN</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-confirm\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Repeat new PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\" id=\\\"setpin-auth-group\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"setpin-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"Your own current PIN\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"setpin-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-setpin-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-setpin-save\\\">Set PIN</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     JOBS — Campaign list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-jobs\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1>Jobs</h1><div class=\\\"sub\\\">Campaigns &amp; Tours</div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-jobs-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh jobs\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-campaign\\\" title=\\\"New Campaign\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"jobs-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     CAMPAIGN DETAIL — Tour list\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<div id=\\\"screen-campaign\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"header\\\">\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-back\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 id=\\\"campaign-hdr-title\\\">Campaign</h1><div class=\\\"sub\\\" id=\\\"campaign-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-campaign-refresh\\\" title=\\\"Refresh\\\" aria-label=\\\"Refresh campaign\\\" style=\\\"color:var(--text)\\\">🔄</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-new-tour\\\" title=\\\"New Tour\\\" style=\\\"font-size:20px;color:var(--accent)\\\">＋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"campaign-body\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Campaign -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-campaign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">📋 New Campaign</div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Year</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-year\\\" min=\\\"2024\\\" max=\\\"2030\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Phase #</div>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"nc-phase\\\" value=\\\"1\\\" min=\\\"1\\\" max=\\\"99\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Brand <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-brand\\\" placeholder=\\\"e.g. Lenovo\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Client <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-client\\\" placeholder=\\\"e.g. Lenovo India\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Campaign Name <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nc-campaign\\\" placeholder=\\\"e.g. Chandigarh BTL\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Generated Campaign ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nc-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nc-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nc-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nc-save\\\">Create</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: New Tour -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-new-tour\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px;max-height:88vh;overflow-y:auto\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🗺️ New Tour</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Tour Title</div>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"nt-title\\\" placeholder=\\\"e.g. Chandigarh Week 1\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:15px;font-family:inherit\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"display:grid;grid-template-columns:1fr 1fr;gap:10px\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Start Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-start\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">End Date</div>\u001b[39m\n\u001b[31m        <input type=\\\"date\\\" id=\\\"nt-end\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:6px\\\">Assign Employees <span style=\\\"color:var(--danger)\\\">*</span></div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-employees\\\" style=\\\"display:flex;flex-direction:column;gap:6px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"color:var(--muted);font-size:13px\\\">Loading…</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:4px\\\">Notes</div>\u001b[39m\n\u001b[31m      <textarea id=\\\"nt-notes\\\" rows=\\\"2\\\" placeholder=\\\"Optional instructions or remarks\\\" style=\\\"width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:10px 12px;border-radius:10px;font-size:14px;font-family:inherit;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px\\\">Tour ID</div>\u001b[39m\n\u001b[31m      <div id=\\\"nt-id-preview\\\" style=\\\"font-size:12px;color:var(--accent);font-weight:700;word-break:break-all\\\">—</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"nt-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" id=\\\"btn-nt-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" id=\\\"btn-nt-save\\\">Create Tour</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- UPDATE BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"update-banner\\\">\u001b[39m\n\u001b[31m  <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ub-text\\\">App update available</div>\u001b[39m\n\u001b[31m    <div class=\\\"ub-sub\\\">Tap to refresh and get the latest version</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ub-btn\\\" id=\\\"btn-update\\\">Update now</button>\u001b[39m\n\u001b[31m  <button class=\\\"ub-close\\\" id=\\\"btn-update-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- INSTALL BANNER -->\u001b[39m\n\u001b[31m<div id=\\\"install-banner\\\">\u001b[39m\n\u001b[31m  <div class=\\\"ib-icon\\\">📲</div>\u001b[39m\n\u001b[31m  <div class=\\\"ib-text\\\">\u001b[39m\n\u001b[31m    <div class=\\\"ib-title\\\">Install 360LM Hub</div>\u001b[39m\n\u001b[31m    <div class=\\\"ib-sub\\\">Add to home screen for the best experience</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <button class=\\\"ib-btn\\\" id=\\\"btn-install\\\">Install</button>\u001b[39m\n\u001b[31m  <button class=\\\"ib-close\\\" id=\\\"btn-install-dismiss\\\">✕</button>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: AI Config (mgmt only) -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-ai\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">🤖 AI Config — Screenshot Extraction</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>Tesseract OCR</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-tesseract\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Extraction Fallback</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-enabled\\\">\u001b[39m\n\u001b[31m        <option value=\\\"true\\\">Enabled</option>\u001b[39m\n\u001b[31m        <option value=\\\"false\\\">Disabled</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-group\\\">\u001b[39m\n\u001b[31m      <label>AI Model</label>\u001b[39m\n\u001b[31m      <select id=\\\"hub-ai-model\\\">\u001b[39m\n\u001b[31m        <option value=\\\"haiku\\\">Haiku (fast, cost-effective)</option>\u001b[39m\n\u001b[31m        <option value=\\\"sonnet\\\">Sonnet (higher accuracy)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-ai-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-ai-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"border-top:1px solid var(--border);padding-top:12px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">API Key Health</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px;margin-bottom:10px\\\">Live check — result is always the same for all users</div>\u001b[39m\n\u001b[31m      <div id=\\\"api-key-status-wrap\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-check-keys\\\" style=\\\"margin-top:10px;width:100%\\\">Check API Keys</button>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:14px;border-top:1px solid var(--border);padding-top:12px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px\\\">View Usage &amp; Billing</div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Anthropic</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/usage\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.anthropic.com/settings/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m          <span style=\\\"font-size:12px;color:var(--text);width:84px;flex-shrink:0\\\">Google Maps</span>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/google/maps-apis/api-list\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">📊 Usage ↗</a>\u001b[39m\n\u001b[31m          <a href=\\\"https://console.cloud.google.com/billing\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:12px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:20px;color:var(--text);text-decoration:none;white-space:nowrap\\\">💳 Billing ↗</a>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- MODAL: Hub Quick-Action Config -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-hub-qa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"gap:12px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">⚡ Quick Actions</div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-config-list\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"hub-qa-cfg-err\\\" style=\\\"color:var(--danger);font-size:13px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"action-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary btn-sm\\\" id=\\\"btn-hub-qa-cancel\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary btn-sm\\\" id=\\\"btn-hub-qa-save\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ═══════════════════════════════════════════\u001b[39m\n\u001b[31m     SCRIPT\u001b[39m\n\u001b[31m══════════════════════════════════════════════ -->\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31m/* ── CONFIG ── */\u001b[39m\n\u001b[31mconst CFG = {\u001b[39m\n\u001b[31m  api:        '/db',\u001b[39m\n\u001b[31m  apiHeaders: { 'Content-Type': 'application/json', 'Accept-Profile': 'expense' },\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31m/* ── SERVER ACCESS MAP (fetched from hub-access.json, overrides ACCESS for non-mgmt) ── */\u001b[39m\n\u001b[31mlet _serverAccess = {};\u001b[39m\n\u001b[31masync function loadHubAccess() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/hub/hub-access.json?t=' + Date.now());\u001b[39m\n\u001b[31m    if (r.ok) _serverAccess = await r.json();\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── ROLE ACCESS MAP (hardcoded fallback) ── */\u001b[39m\n\u001b[31mconst ACCESS = {\u001b[39m\n\u001b[31m  harish:     ['expense','installation','finance','recce','dispatch','production','jobs','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  pramod:     ['expense','installation','finance','recce','dispatch','production','client','vehicle','vrs','team-pins','sales','stores','admin','activity','hr'],\u001b[39m\n\u001b[31m  rakesh:     ['installation','expense','production','jobs','vehicle','vrs','sales','hr'],\u001b[39m\n\u001b[31m  ashok:      ['expense','production','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  shambhu:    ['finance','expense','vehicle','vrs','stores','activity','hr'],\u001b[39m\n\u001b[31m  gurpreet:   ['expense','dispatch','vehicle','vrs','stores','hr'],\u001b[39m\n\u001b[31m  sachin:     ['installation','expense','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  mukesh:     ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  umashankar: ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  narender:   ['installation','expense','recce','vehicle','vrs','hr'],\u001b[39m\n\u001b[31m  amit:       ['vehicle','vrs','hr'],\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst TILES = [\u001b[39m\n\u001b[31m  { id:'expense',      label:'Expenses',     icon:'🧾', url:'/expense/',               live:true  },\u001b[39m\n\u001b[31m  { id:'installation', label:'Installation', icon:'🔧', url:'/installation/current/',  live:true  },\u001b[39m\n\u001b[31m  { id:'finance',      label:'Finance',      icon:'💰', url:'/finance/',               live:true  },\u001b[39m\n\u001b[31m  { id:'recce',        label:'Recce',        icon:'📍', url:'/recce/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'dispatch',     label:'Dispatch',     icon:'🚚', url:'/dispatch/',              live:true  },\u001b[39m\n\u001b[31m  { id:'production',   label:'Production',   icon:'⚙️', url:'/production/',            live:true  },\u001b[39m\n\u001b[31m  { id:'jobs',         label:'Jobs',         icon:'📋', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'vehicle',      label:'Vehicle Log',  icon:'🚐', url:'/vehicle/',               live:true  },\u001b[39m\n\u001b[31m  { id:'vrs',          label:'Veh. Request', icon:'📋', url:'/vrs/',                   live:true  },\u001b[39m\n\u001b[31m  { id:'sales',        label:'Sales',        icon:'💼', url:'/sales/',                 live:true  },\u001b[39m\n\u001b[31m  { id:'stores',       label:'Stores',       icon:'📦', url:'/stores/',                live:true  },\u001b[39m\n\u001b[31m  { id:'client',       label:'Client',       icon:'👤', url:'/client/',                live:true  },\u001b[39m\n\u001b[31m  { id:'team-pins',    label:'Team PINs',    icon:'🔑', url:null,                      live:true  },\u001b[39m\n\u001b[31m  { id:'admin',        label:'Admin',        icon:'🛠️', url:'/admin/',                  live:true  },\u001b[39m\n\u001b[31m  { id:'activity',     label:'Activity',     icon:'🎯', url:'/activity/',               live:true  },\u001b[39m\n\u001b[31m  { id:'hr',           label:'HR',           icon:'👥', url:'/hr/',                     live:true  },\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31mconst MGMT = ['harish', 'pramod'];·\u001b[39m\n\u001b[31m/* ── STATE ── */\u001b[39m\n\u001b[31mlet currentUser     = null;\u001b[39m\n\u001b[31mlet pinBuffer       = '';\u001b[39m\n\u001b[31mlet setPinTarget    = null;\u001b[39m\n\u001b[31mlet pendingShare    = false;\u001b[39m\n\u001b[31mlet _loginAttempts  = 0;\u001b[39m\n\u001b[31mlet _lockoutUntil   = 0;\u001b[39m\n\u001b[31mlet _loginInFlight  = false;·\u001b[39m\n\u001b[31m/* ── UTILS ── */\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => { s.classList.remove('active'); s.style.display = ''; });\u001b[39m\n\u001b[31m  document.getElementById('screen-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet toastTimer;\u001b[39m\n\u001b[31mfunction toast(msg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.classList.add('show');\u001b[39m\n\u001b[31m  clearTimeout(toastTimer);\u001b[39m\n\u001b[31m  toastTimer = setTimeout(() => el.classList.remove('show'), 2800);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fmtINR(n) {\u001b[39m\n\u001b[31m  if (n >= 100000) return '₹' + (n / 100000).toFixed(1) + 'L';\u001b[39m\n\u001b[31m  if (n >= 1000)   return '₹' + (n / 1000).toFixed(1) + 'K';\u001b[39m\n\u001b[31m  return '₹' + Math.round(n);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SESSION ── */\u001b[39m\n\u001b[31mconst SESSION_KEY  = 'lm360-session';\u001b[39m\n\u001b[31mconst EXP_SES_KEY  = '360exp-session';\u001b[39m\n\u001b[31mconst SESSION_TTL  = 12 * 3600 * 1000;·\u001b[39m\n\u001b[31mfunction saveSession(user) {\u001b[39m\n\u001b[31m  const payload = { empId: user.id, name: user.name, role: user.role, loginAt: new Date().toISOString(), source: 'hub' };\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m  localStorage.setItem(EXP_SES_KEY, JSON.stringify(payload));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearSession() {\u001b[39m\n\u001b[31m  localStorage.removeItem(SESSION_KEY);\u001b[39m\n\u001b[31m  localStorage.removeItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const raw = localStorage.getItem(SESSION_KEY) || localStorage.getItem(EXP_SES_KEY);\u001b[39m\n\u001b[31m    if (!raw) return null;\u001b[39m\n\u001b[31m    const s = JSON.parse(raw);\u001b[39m\n\u001b[31m    if (!s.empId || !s.name || !s.role) return null;\u001b[39m\n\u001b[31m    if (Date.now() - new Date(s.loginAt).getTime() > SESSION_TTL) { clearSession(); return null; }\u001b[39m\n\u001b[31m    return s;\u001b[39m\n\u001b[31m  } catch (_) { return null; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGIN FLOW ── */\u001b[39m\n\u001b[31masync function initLogin() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('login-name');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— Choose employee —</option>'; // clear before repopulating\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    emps.forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    [\u001b[39m\n\u001b[31m      { id:'amit',        name:'Amit'             },\u001b[39m\n\u001b[31m      { id:'ashok',       name:'Ashok'            },\u001b[39m\n\u001b[31m      { id:'gurpreet',    name:'Gurpreet'         },\u001b[39m\n\u001b[31m      { id:'harish',      name:'Harish Kumar Lal' },\u001b[39m\n\u001b[31m      { id:'mukesh',      name:'Mukesh'           },\u001b[39m\n\u001b[31m      { id:'narender',    name:'Narender'         },\u001b[39m\n\u001b[31m      { id:'pramod',      name:'Pramod Narang'    },\u001b[39m\n\u001b[31m      { id:'rakesh',      name:'Rakesh'           },\u001b[39m\n\u001b[31m      { id:'sachin',      name:'Sachin'           },\u001b[39m\n\u001b[31m      { id:'shambhu',     name:'Shambhu'          },\u001b[39m\n\u001b[31m      { id:'umashankar',  name:'Umashankar'       },\u001b[39m\n\u001b[31m    ].forEach(e => {\u001b[39m\n\u001b[31m      const o = document.createElement('option');\u001b[39m\n\u001b[31m      o.value = e.id;\u001b[39m\n\u001b[31m      o.textContent = e.name;\u001b[39m\n\u001b[31m      sel.appendChild(o);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  document.querySelectorAll('.pin-key').forEach(btn =>\u001b[39m\n\u001b[31m    btn.addEventListener('click', () => handlePinKey(btn.dataset.k))\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handlePinKey(k) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (_loginInFlight) return;\u001b[39m\n\u001b[31m  // Lockout guard\u001b[39m\n\u001b[31m  if (_lockoutUntil && Date.now() < _lockoutUntil) {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    errEl.textContent = `Too many attempts — wait ${secs}s`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (_lockoutUntil) { _lockoutUntil = 0; _loginAttempts = 0; }\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (k === 'C') { pinBuffer = ''; }\u001b[39m\n\u001b[31m  else if (k === 'B') { pinBuffer = pinBuffer.slice(0, -1); }\u001b[39m\n\u001b[31m  else {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (!empId) { toast('Select your name first'); return; }\u001b[39m\n\u001b[31m    if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  if (pinBuffer.length === 4) {\u001b[39m\n\u001b[31m    const empId = document.getElementById('login-name').value;\u001b[39m\n\u001b[31m    if (empId) attemptLogin(empId, pinBuffer);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updatePinDisplay() {\u001b[39m\n\u001b[31m  for (let i = 0; i < 4; i++)\u001b[39m\n\u001b[31m    document.getElementById('pd' + i).classList.toggle('filled', i < pinBuffer.length);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _shakePin() {\u001b[39m\n\u001b[31m  const pd = document.getElementById('pin-display');\u001b[39m\n\u001b[31m  pd.classList.add('shake');\u001b[39m\n\u001b[31m  setTimeout(() => pd.classList.remove('shake'), 400);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _startLockout() {\u001b[39m\n\u001b[31m  _lockoutUntil = Date.now() + 30000;\u001b[39m\n\u001b[31m  _loginAttempts = 0;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  const tick = () => {\u001b[39m\n\u001b[31m    const secs = Math.ceil((_lockoutUntil - Date.now()) / 1000);\u001b[39m\n\u001b[31m    if (secs > 0) { errEl.textContent = `Too many attempts — wait ${secs}s`; setTimeout(tick, 500); }\u001b[39m\n\u001b[31m    else { _lockoutUntil = 0; errEl.textContent = ''; }\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  tick();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function attemptLogin(empId, pin) {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  _loginInFlight = true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pin }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      _loginAttempts++;\u001b[39m\n\u001b[31m      _shakePin();\u001b[39m\n\u001b[31m      if (_loginAttempts >= 5) { _startLockout(); return; }\u001b[39m\n\u001b[31m      errEl.textContent = `Wrong PIN — ${5 - _loginAttempts} attempt${5 - _loginAttempts !== 1 ? 's' : ''} left`;\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    currentUser = { id: user.id, name: user.name, role: user.role };\u001b[39m\n\u001b[31m    saveSession(currentUser);\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — check connection';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    _loginInFlight = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── HOME SCREEN ── */\u001b[39m\n\u001b[31masync function buildHome() {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const body   = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  // Management users always use hardcoded ACCESS; others prefer server-side hub-access.json\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const tiles  = accessIds.map(id => TILES.find(t => t.id === id)).filter(Boolean);·\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"avatar\\\">${initials}</div>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1 style=\\\"font-size:16px\\\">${currentUser.name}</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\">${currentUser.role}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${isMgmt ? '<button class=\\\"btn-icon\\\" id=\\\"btn-hub-ai\\\" title=\\\"AI Config\\\" style=\\\"font-size:18px\\\">🤖</button>' : ''}\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m  if (isMgmt) document.getElementById('btn-hub-ai').addEventListener('click', openHubAiConfig);·\u001b[39m\n\u001b[31m  if (isMgmt) {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"hub-main-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-dash\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">📊</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">Dashboard</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"hub-main-tile\\\" id=\\\"hub-tile-tools\\\">\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-icon\\\">🧰</div>\u001b[39m\n\u001b[31m          <div class=\\\"hub-main-label\\\">All Tools</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-header\\\">\u001b[39m\n\u001b[31m        <div class=\\\"section-title\\\" style=\\\"margin:0\\\">Quick Actions</div>\u001b[39m\n\u001b[31m        <button class=\\\"btn-icon\\\" id=\\\"btn-hub-qa-cfg\\\" title=\\\"Configure Quick Actions\\\" style=\\\"font-size:18px;padding:8px;min-width:44px;min-height:44px\\\">⚙️</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-grid\\\" id=\\\"hub-qa-grid\\\"></div>`;\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-dash').addEventListener('click',  () => enterHubDash(tiles));\u001b[39m\n\u001b[31m    document.getElementById('hub-tile-tools').addEventListener('click', () => enterHubTools(tiles));\u001b[39m\n\u001b[31m    document.getElementById('btn-hub-qa-cfg').addEventListener('click', openHubQaConfig);\u001b[39m\n\u001b[31m    renderHubQA();\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"tile-grid field\\\" id=\\\"tile-grid\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"text-align:center;font-size:12px;color:var(--muted);padding-top:8px\\\">Tap a tile to open your tool</div>`;\u001b[39m\n\u001b[31m    renderTiles(tiles, 'field');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderTiles(tiles, mode) {\u001b[39m\n\u001b[31m  const grid = document.getElementById('tile-grid');\u001b[39m\n\u001b[31m  tiles.forEach(tile => {\u001b[39m\n\u001b[31m    const div = document.createElement('div');\u001b[39m\n\u001b[31m    div.className = 'tile ' + mode + (tile.live ? '' : ' coming');\u001b[39m\n\u001b[31m    div.innerHTML = `<div class=\\\"tile-icon\\\">${tile.icon}</div><div class=\\\"tile-label\\\">${tile.label}</div>` +\u001b[39m\n\u001b[31m      (tile.live ? '' : '<div class=\\\"soon-badge\\\">Soon</div>');\u001b[39m\n\u001b[31m    div.addEventListener('click', () => handleTile(tile));\u001b[39m\n\u001b[31m    grid.appendChild(div);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction handleTile(tile) {\u001b[39m\n\u001b[31m  if (!tile.live) { toast('Coming soon'); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'team-pins') { enterTeamPins(); return; }\u001b[39m\n\u001b[31m  if (tile.id === 'jobs')      { enterJobs();     return; }\u001b[39m\n\u001b[31m  window.location.href = tile.url;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT SUB-VIEWS ── */\u001b[39m\n\u001b[31mfunction _mgmtHeader(title) {\u001b[39m\n\u001b[31m  const header = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const initials = currentUser.name.split(' ').map(w => w[0]).join('').slice(0,2).toUpperCase();\u001b[39m\n\u001b[31m  header.innerHTML = `\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-subview-back\\\" style=\\\"font-size:20px;font-weight:900\\\">←</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\"><h1 style=\\\"font-size:16px\\\">${title}</h1></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-icon\\\" id=\\\"btn-logout\\\" title=\\\"Log out\\\" aria-label=\\\"Log out\\\" style=\\\"font-size:18px\\\">🚪</button>`;\u001b[39m\n\u001b[31m  document.getElementById('btn-subview-back').addEventListener('click', () => buildHome());\u001b[39m\n\u001b[31m  document.getElementById('btn-logout').addEventListener('click', doLogout);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubDash(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('Dashboard');\u001b[39m\n\u001b[31m  const hdr = document.getElementById('home-header');\u001b[39m\n\u001b[31m  const refreshBtn = document.createElement('button');\u001b[39m\n\u001b[31m  refreshBtn.className = 'btn-icon';\u001b[39m\n\u001b[31m  refreshBtn.title = 'Refresh dashboard';\u001b[39m\n\u001b[31m  refreshBtn.setAttribute('aria-label', 'Refresh dashboard');\u001b[39m\n\u001b[31m  refreshBtn.style.cssText = 'font-size:18px';\u001b[39m\n\u001b[31m  refreshBtn.textContent = '🔄';\u001b[39m\n\u001b[31m  refreshBtn.addEventListener('click', loadKPIs);\u001b[39m\n\u001b[31m  hdr.insertBefore(refreshBtn, hdr.querySelector('#btn-logout'));\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Actions Required</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-actions\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Operations</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row kpi-4col\\\" id=\\\"kpi-ops\\\">${skel}${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Production</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-prod\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">Installation</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-inst\\\">${skel}${skel}${skel}</div>\u001b[39m\n\u001b[31m    <div class=\\\"section-title\\\">HR</div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-row\\\" id=\\\"kpi-hr\\\">${skel}${skel}${skel}</div>`;\u001b[39m\n\u001b[31m  loadKPIs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enterHubTools(tiles) {\u001b[39m\n\u001b[31m  _mgmtHeader('All Tools');\u001b[39m\n\u001b[31m  const body = document.getElementById('home-body');\u001b[39m\n\u001b[31m  body.innerHTML = `<div class=\\\"tile-grid mgmt\\\" id=\\\"tile-grid\\\"></div>`;\u001b[39m\n\u001b[31m  renderTiles(tiles, 'mgmt');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT AI CONFIG ── */\u001b[39m\n\u001b[31mconst HUB_FIN_HDR = { 'Content-Type': 'application/json', 'Accept-Profile': 'finance' };·\u001b[39m\n\u001b[31masync function openHubAiConfig() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r    = await fetch(`${CFG.api}/upi_config`, { headers: HUB_FIN_HDR });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const get  = key => rows.find(x => x.key === key)?.value;\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-tesseract').value = get('tesseract_enabled') || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-enabled').value   = get('ai_enabled')        || 'true';\u001b[39m\n\u001b[31m    document.getElementById('hub-ai-model').value     = get('ai_model')           || 'haiku';\u001b[39m\n\u001b[31m  } catch(_) { toast('Could not load AI config'); return; }\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-ai').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveHubAiConfig() {\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-hub-ai-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  const pairs = [\u001b[39m\n\u001b[31m    { key:'tesseract_enabled', value: document.getElementById('hub-ai-tesseract').value },\u001b[39m\n\u001b[31m    { key:'ai_enabled',        value: document.getElementById('hub-ai-enabled').value },\u001b[39m\n\u001b[31m    { key:'ai_model',          value: document.getElementById('hub-ai-model').value },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const results = await Promise.all(pairs.map(p =>\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/upi_config?key=eq.${p.key}`, {\u001b[39m\n\u001b[31m        method: 'PATCH',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'finance', 'Prefer': 'return=minimal' },\u001b[39m\n\u001b[31m        body: JSON.stringify({ value: p.value }),\u001b[39m\n\u001b[31m      })\u001b[39m\n\u001b[31m    ));\u001b[39m\n\u001b[31m    if (results.some(r => !r.ok)) throw new Error('partial failure');\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show');\u001b[39m\n\u001b[31m    toast('✅ AI config saved');\u001b[39m\n\u001b[31m  } catch(_) { toast('Save failed'); }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Save'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function checkApiKeyStatus() {\u001b[39m\n\u001b[31m  const btn  = document.getElementById('btn-check-keys');\u001b[39m\n\u001b[31m  const wrap = document.getElementById('api-key-status-wrap');\u001b[39m\n\u001b[31m  btn.textContent = 'Checking…';\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  wrap.innerHTML = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch('/tour-ai/key-status');\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Proxy error ' + r.status);\u001b[39m\n\u001b[31m    const d = await r.json();\u001b[39m\n\u001b[31m    function mkBadge(info, label, link) {\u001b[39m\n\u001b[31m      const col  = info.ok ? 'var(--success)' : 'var(--danger)';\u001b[39m\n\u001b[31m      const icon = info.ok ? '✅' : '❌';\u001b[39m\n\u001b[31m      const note = info.ok ? (info.model || 'Valid') : (info.error || 'Error');\u001b[39m\n\u001b[31m      const hint = info.hint ? ' · key ' + info.hint : '';\u001b[39m\n\u001b[31m      return `<div style=\\\"background:var(--card);border:1px solid var(--border);border-left:3px solid ${col};border-radius:8px;padding:10px 12px;display:flex;align-items:center;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:13px;font-weight:600;color:var(--text)\\\">${icon} ${label}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${note}${hint}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <a href=\\\"${link}\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\" style=\\\"font-size:11px;color:var(--info);text-decoration:none;white-space:nowrap\\\">Billing ↗</a>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    wrap.innerHTML =\u001b[39m\n\u001b[31m      mkBadge(d.anthropic, 'Anthropic API',    'https://console.anthropic.com/settings/billing') +\u001b[39m\n\u001b[31m      mkBadge(d.maps,      'Google Maps API',  'https://console.cloud.google.com/billing');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    wrap.innerHTML = `<div style=\\\"font-size:12px;color:var(--danger)\\\">Error: ${e.message}</div>`;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.textContent = 'Check Again';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── MGMT QUICK ACTIONS ── */\u001b[39m\n\u001b[31mconst HUB_QA_DEFS = [\u001b[39m\n\u001b[31m  { id:'offer',       icon:'📋', label:'New Offer',    accessId:'sales',        fn:() => { window.location.href='/sales/'; } },\u001b[39m\n\u001b[31m  { id:'paytm',       icon:'📱', label:'UPI Pay',       accessId:'finance',      fn:() => { window.location.href='/finance/?qa=upi'; } },\u001b[39m\n\u001b[31m  { id:'expense',     icon:'🧾', label:'Expenses',      accessId:'expense',      fn:() => { window.location.href='/expense/'; } },\u001b[39m\n\u001b[31m  { id:'finance',     icon:'💰', label:'Finance',       accessId:'finance',      fn:() => { window.location.href='/finance/'; } },\u001b[39m\n\u001b[31m  { id:'dispatch',    icon:'🚚', label:'Dispatch',      accessId:'dispatch',     fn:() => { window.location.href='/dispatch/'; } },\u001b[39m\n\u001b[31m  { id:'stores',      icon:'📦', label:'Stores',        accessId:'stores',       fn:() => { window.location.href='/stores/'; } },\u001b[39m\n\u001b[31m  { id:'recce',       icon:'📍', label:'Recce',         accessId:'recce',        fn:() => { window.location.href='/recce/'; } },\u001b[39m\n\u001b[31m  { id:'production',  icon:'⚙️', label:'Production',   accessId:'production',   fn:() => { window.location.href='/production/'; } },\u001b[39m\n\u001b[31m  { id:'tours',       icon:'🗺️', label:'Tour Plan',   accessId:'installation', fn:() => { window.location.href='/tour-planner/'; } },\u001b[39m\n\u001b[31m  { id:'installation',icon:'🔧', label:'Installation',  accessId:'installation', fn:() => { window.location.href='/installation/current/'; } },\u001b[39m\n\u001b[31m  { id:'activity',    icon:'🎯', label:'Activity',      accessId:'activity',     fn:() => { window.location.href='/activity/'; } },\u001b[39m\n\u001b[31m];\u001b[39m\n\u001b[31mconst HUB_QA_DEFAULT = ['offer', 'paytm'];\u001b[39m\n\u001b[31mconst HUB_QA_STORE   = '360lm-hub-qa';\u001b[39m\n\u001b[31mlet   _hubQaTempSel  = [];·\u001b[39m\n\u001b[31mfunction getHubQA() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(HUB_QA_STORE));\u001b[39m\n\u001b[31m    if (Array.isArray(s) && s.length) return s;\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m  return [...HUB_QA_DEFAULT];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHubQA() {\u001b[39m\n\u001b[31m  const ids  = getHubQA();\u001b[39m\n\u001b[31m  const grid = document.getElementById('hub-qa-grid');\u001b[39m\n\u001b[31m  if (!grid) return;\u001b[39m\n\u001b[31m  if (!ids.length) {\u001b[39m\n\u001b[31m    grid.innerHTML = `<div style=\\\"color:var(--muted);font-size:13px;grid-column:1/-1;\u001b[39m\n\u001b[31m      text-align:center;padding:16px\\\">Tap ⚙️ to add quick actions</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  grid.innerHTML = ids.map(id => {\u001b[39m\n\u001b[31m    const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!d) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-tile\\\" onclick=\\\"hubQaRun('${id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction hubQaRun(id) {\u001b[39m\n\u001b[31m  const d = HUB_QA_DEFS.find(x => x.id === id);\u001b[39m\n\u001b[31m  if (d) d.fn();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openHubQaConfig() {\u001b[39m\n\u001b[31m  _hubQaTempSel = [...getHubQA()];\u001b[39m\n\u001b[31m  const isMgmt = MGMT.includes(currentUser.id);\u001b[39m\n\u001b[31m  const accessIds = isMgmt\u001b[39m\n\u001b[31m    ? (ACCESS[currentUser.id] || ['expense'])\u001b[39m\n\u001b[31m    : (_serverAccess[currentUser.id] || ACCESS[currentUser.id] || ['expense']);\u001b[39m\n\u001b[31m  const visibleDefs = HUB_QA_DEFS.filter(d => accessIds.includes(d.accessId));\u001b[39m\n\u001b[31m  const list = document.getElementById('hub-qa-config-list');\u001b[39m\n\u001b[31m  list.innerHTML = visibleDefs.map(d => {\u001b[39m\n\u001b[31m    const sel = _hubQaTempSel.includes(d.id);\u001b[39m\n\u001b[31m    return `<div class=\\\"hub-qa-item${sel?' selected':''}\\\" id=\\\"hub-qa-item-${d.id}\\\"\u001b[39m\n\u001b[31m        onclick=\\\"toggleHubQaItem('${d.id}')\\\">\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-icon\\\">${d.icon}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-label\\\">${d.label}</div>\u001b[39m\n\u001b[31m      <div class=\\\"hub-qa-item-toggle\\\"></div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleHubQaItem(id) {\u001b[39m\n\u001b[31m  const idx = _hubQaTempSel.indexOf(id);\u001b[39m\n\u001b[31m  if (idx >= 0) {\u001b[39m\n\u001b[31m    _hubQaTempSel.splice(idx, 1);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    if (_hubQaTempSel.length >= 6) {\u001b[39m\n\u001b[31m      document.getElementById('hub-qa-cfg-err').textContent = 'Maximum 6 quick actions';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _hubQaTempSel.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('hub-qa-cfg-err').textContent = '';\u001b[39m\n\u001b[31m  const el = document.getElementById('hub-qa-item-' + id);\u001b[39m\n\u001b[31m  if (el) el.className = 'hub-qa-item' + (_hubQaTempSel.includes(id) ? ' selected' : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction saveHubQaConfig() {\u001b[39m\n\u001b[31m  localStorage.setItem(HUB_QA_STORE, JSON.stringify(_hubQaTempSel));\u001b[39m\n\u001b[31m  document.getElementById('modal-hub-qa').classList.remove('show');\u001b[39m\n\u001b[31m  renderHubQA();\u001b[39m\n\u001b[31m  toast('✅ Quick actions updated');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── KPI FETCH ── */\u001b[39m\n\u001b[31mfunction _hdr(schema) {\u001b[39m\n\u001b[31m  return { 'Content-Type': 'application/json', 'Accept-Profile': schema };\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _safe(p) { return p.catch(() => null); }·\u001b[39m\n\u001b[31masync function loadKPIs() {\u001b[39m\n\u001b[31m  const mon = firstOfMonth();\u001b[39m\n\u001b[31m  const curMonth = new Date().toISOString().slice(0, 7); // YYYY-MM\u001b[39m\n\u001b[31m  const todayIST = (() => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; })();·\u001b[39m\n\u001b[31m  // Reset sections to loading skeletons before fetching (makes refresh visible)\u001b[39m\n\u001b[31m  const skel = `<div class=\\\"kpi-card\\\"><div class=\\\"kpi-skel\\\"></div><div class=\\\"kpi-lbl\\\">Loading…</div></div>`;\u001b[39m\n\u001b[31m  [['kpi-actions',3],['kpi-ops',4],['kpi-prod',3],['kpi-inst',3],['kpi-hr',3]].forEach(([id,n]) => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    if (el) el.innerHTML = skel.repeat(n);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  const [\u001b[39m\n\u001b[31m    sheetsRes, claimsRes, salaryRes,\u001b[39m\n\u001b[31m    cashRes,   dispRes,   recceRes,  prodRes,\u001b[39m\n\u001b[31m    instJobsRes, instCtrsRes, storeItemsRes,\u001b[39m\n\u001b[31m    hrAttRes, hrEmpRes, hrConcernRes, hrSlipsRes\u001b[39m\n\u001b[31m  ] = await Promise.all([\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/sheets?status=in.(submitted,escalated)&select=sheet_id`, { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/approved_pending_reimbursement?select=sheet_id`,         { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_register?status=in.(pending,partial)&month=eq.${curMonth}&select=record_id`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/accounts?account_id=eq.petty-cash&select=current_balance`, { headers: _hdr('finance') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/dispatches?status=eq.active&select=dispatch_id`,         { headers: _hdr('dispatch') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/submissions?submitted_at=gte.${mon}&select=sub_id`,      { headers: _hdr('recce') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/orders?status=not.in.(dispatched)&select=order_id,status`, { headers: _hdr('production') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/jobs?select=job_id,status`,                              { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/counters?select=counter_id,status`,                      { headers: _hdr('installation') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/items?active=eq.true&select=id,current_stock,min_stock`, { headers: _hdr('stores') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance?date=eq.${todayIST}&status=eq.present&select=attendance_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/employee_list?select=id`,                               { headers: _hdr('expense') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/attendance_concerns?status=eq.open&select=concern_id`,  { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m    _safe(fetch(`${CFG.api}/salary_slips?month=eq.${curMonth}&status=eq.finalized&select=slip_id`, { headers: _hdr('hr') })),\u001b[39m\n\u001b[31m  ]);·\u001b[39m\n\u001b[31m  const sheets    = sheetsRes?.ok    ? await sheetsRes.json()    : [];\u001b[39m\n\u001b[31m  const claims    = claimsRes?.ok    ? await claimsRes.json()    : [];\u001b[39m\n\u001b[31m  const salary    = salaryRes?.ok    ? await salaryRes.json()    : [];\u001b[39m\n\u001b[31m  const cashArr   = cashRes?.ok      ? await cashRes.json()      : [];\u001b[39m\n\u001b[31m  const disps     = dispRes?.ok      ? await dispRes.json()      : [];\u001b[39m\n\u001b[31m  const recces    = recceRes?.ok     ? await recceRes.json()     : [];\u001b[39m\n\u001b[31m  const prods     = prodRes?.ok      ? await prodRes.json()      : [];\u001b[39m\n\u001b[31m  const instJobs  = instJobsRes?.ok  ? await instJobsRes.json()  : [];\u001b[39m\n\u001b[31m  const instCtrs  = instCtrsRes?.ok  ? await instCtrsRes.json()  : [];\u001b[39m\n\u001b[31m  const storeItems = storeItemsRes?.ok ? await storeItemsRes.json() : [];\u001b[39m\n\u001b[31m  const hrAtt     = hrAttRes?.ok     ? await hrAttRes.json()     : [];\u001b[39m\n\u001b[31m  const hrEmp     = hrEmpRes?.ok     ? await hrEmpRes.json()     : [];\u001b[39m\n\u001b[31m  const hrConcern = hrConcernRes?.ok ? await hrConcernRes.json() : [];\u001b[39m\n\u001b[31m  const hrSlips   = hrSlipsRes?.ok   ? await hrSlipsRes.json()   : [];·\u001b[39m\n\u001b[31m  const cash = Number(cashArr[0]?.current_balance || 0);·\u001b[39m\n\u001b[31m  const actionsEl = document.getElementById('kpi-actions');\u001b[39m\n\u001b[31m  const opsEl     = document.getElementById('kpi-ops');\u001b[39m\n\u001b[31m  const prodEl    = document.getElementById('kpi-prod');\u001b[39m\n\u001b[31m  const instEl    = document.getElementById('kpi-inst');\u001b[39m\n\u001b[31m  const hrEl      = document.getElementById('kpi-hr');\u001b[39m\n\u001b[31m  if (!actionsEl || !opsEl || !prodEl || !instEl) return;·\u001b[39m\n\u001b[31m  // ── Actions Required ────────────────────────────────────────────\u001b[39m\n\u001b[31m  actionsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${sheets.length ? 'alert' : 'ok'}\\\" data-href=\\\"/expense/?tab=approvals\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${sheets.length ? 'red' : 'green'}\\\">${sheets.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Expense Approvals</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${claims.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=claims\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${claims.length ? 'amber' : 'green'}\\\">${claims.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Claims to Reimburse</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${salary.length ? 'warn' : 'ok'}\\\" data-href=\\\"/finance/?tab=salary\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${salary.length ? 'amber' : 'green'}\\\">${salary.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Salary Due This Month</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Operations ──────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const cashColor = cash < 2000 ? 'red' : cash < 5000 ? 'amber' : 'green';\u001b[39m\n\u001b[31m  const cashCard  = cash < 2000 ? 'alert' : cash < 5000 ? 'warn' : 'ok';\u001b[39m\n\u001b[31m  const lowStock = storeItems.filter(i => i.current_stock < i.min_stock).length;\u001b[39m\n\u001b[31m  opsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${cashCard}\\\" data-href=\\\"/finance/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${cashColor}\\\">${fmtINR(cash)}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">${cash < 0 ? '⚠️ ' : ''}Petty Cash</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/dispatch/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${disps.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Dispatches</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/recce/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${recces.length}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Recce This Month</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${lowStock > 0 ? 'alert' : 'ok'}\\\" data-href=\\\"/stores/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${lowStock > 0 ? 'red' : 'green'}\\\">${lowStock}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Low Stock Items</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Production ───────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const prodPending = prods.filter(o => o.status === 'pending').length;\u001b[39m\n\u001b[31m  const prodActive  = prods.filter(o => o.status === 'in_production' || o.status === 'quality_check').length;\u001b[39m\n\u001b[31m  const prodReady   = prods.filter(o => o.status === 'ready').length;\u001b[39m\n\u001b[31m  prodEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodPending ? 'warn' : 'ok'}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodPending ? 'amber' : 'green'}\\\">${prodPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Pending Orders</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card info\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val blue\\\">${prodActive}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">In Production</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${prodReady ? 'ok' : ''}\\\" data-href=\\\"/production/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${prodReady ? 'green' : ''}\\\">${prodReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Ready to Dispatch</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── Installation ─────────────────────────────────────────────────\u001b[39m\n\u001b[31m  const activeJobs  = instJobs.filter(j => j.status === 'active').length;\u001b[39m\n\u001b[31m  const ctrsPending = instCtrs.filter(c => c.status === 'recce_pending' || c.status === 'mockup_pending' || c.status === 'unclear').length;\u001b[39m\n\u001b[31m  const ctrsReady   = instCtrs.filter(c => c.status === 'ready').length;\u001b[39m\n\u001b[31m  instEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${activeJobs ? 'info' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${activeJobs ? 'blue' : 'green'}\\\">${activeJobs}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Active Jobs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsPending ? 'warn' : 'ok'}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsPending ? 'amber' : 'green'}\\\">${ctrsPending}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Pending</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"kpi-card ${ctrsReady ? 'ok' : ''}\\\" data-href=\\\"/installation/current/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m      <div class=\\\"kpi-val ${ctrsReady ? 'green' : ''}\\\">${ctrsReady}</div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-lbl\\\">Counters Ready</div>\u001b[39m\n\u001b[31m    </div>`;·\u001b[39m\n\u001b[31m  // ── HR ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31m  if (hrEl) {\u001b[39m\n\u001b[31m    const totalEmp   = hrEmp.length || 0;\u001b[39m\n\u001b[31m    const presentCnt = hrAtt.length;\u001b[39m\n\u001b[31m    const attPct     = totalEmp > 0 ? Math.round(presentCnt / totalEmp * 100) : 0;\u001b[39m\n\u001b[31m    const attColor   = attPct >= 80 ? 'green' : attPct >= 50 ? 'amber' : 'red';\u001b[39m\n\u001b[31m    const attCard    = attPct >= 80 ? 'ok' : attPct >= 50 ? 'warn' : 'alert';\u001b[39m\n\u001b[31m    const concernCnt = hrConcern.length;\u001b[39m\n\u001b[31m    const slipsDue   = hrSlips.length;\u001b[39m\n\u001b[31m    hrEl.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${attCard}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${attColor}\\\">${attPct}%</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Attendance Today</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${concernCnt ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${concernCnt ? 'amber' : 'green'}\\\">${concernCnt}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Open Concerns</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"kpi-card ${slipsDue ? 'warn' : 'ok'}\\\" data-href=\\\"/hr/\\\" style=\\\"cursor:pointer\\\">\u001b[39m\n\u001b[31m        <div class=\\\"kpi-val ${slipsDue ? 'amber' : 'green'}\\\">${slipsDue}</div>\u001b[39m\n\u001b[31m        <div class=\\\"kpi-lbl\\\">Slips to Pay</div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Wire tap-to-navigate on all KPI cards\u001b[39m\n\u001b[31m  document.querySelectorAll('.kpi-card[data-href]').forEach(card => {\u001b[39m\n\u001b[31m    card.addEventListener('click', () => window.location.href = card.dataset.href);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction firstOfMonth() {\u001b[39m\n\u001b[31m  const d = new Date();\u001b[39m\n\u001b[31m  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── LOGOUT ── */\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  pinBuffer   = '';\u001b[39m\n\u001b[31m  pendingShare = false;\u001b[39m\n\u001b[31m  _loginAttempts = 0; _lockoutUntil = 0; _loginInFlight = false;\u001b[39m\n\u001b[31m  updatePinDisplay();\u001b[39m\n\u001b[31m  document.getElementById('login-name').value = '';\u001b[39m\n\u001b[31m  document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m  showScreen('login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── TEAM PINs ── */\u001b[39m\n\u001b[31masync function enterTeamPins() {\u001b[39m\n\u001b[31m  showScreen('team');\u001b[39m\n\u001b[31m  await loadTeamPins();\u001b[39m\n\u001b[31m  // Show icon admin section for harish/pramod\u001b[39m\n\u001b[31m  const sec = document.getElementById('icon-admin-section');\u001b[39m\n\u001b[31m  if (sec) {\u001b[39m\n\u001b[31m    sec.style.display = MGMT.includes(currentUser.id) ? 'block' : 'none';\u001b[39m\n\u001b[31m    if (MGMT.includes(currentUser.id)) refreshSecretDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── ICON ADMIN ────────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst ICON_SECRET_KEY    = 'lm360_icon_secret';\u001b[39m\n\u001b[31mconst ICON_SECRET_INITIAL = 'nlr8Zsqw6XsD0rEXPQ_RfpYiOeNhH-9VglYKoIsElHE';·\u001b[39m\n\u001b[31mfunction getIconSecret() {\u001b[39m\n\u001b[31m  return localStorage.getItem(ICON_SECRET_KEY) || ICON_SECRET_INITIAL;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction setIconSecret(s) {\u001b[39m\n\u001b[31m  localStorage.setItem(ICON_SECRET_KEY, s);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet secretVisible = false;\u001b[39m\n\u001b[31mfunction refreshSecretDisplay() {\u001b[39m\n\u001b[31m  const el = document.getElementById('icon-secret-display');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  const s = getIconSecret();\u001b[39m\n\u001b[31m  el.textContent = secretVisible ? s : s.slice(0,4) + '••••••••••••••••' + s.slice(-4);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleSecretVisible() {\u001b[39m\n\u001b[31m  secretVisible = !secretVisible;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-secret-show');\u001b[39m\n\u001b[31m  if (btn) btn.textContent = secretVisible ? 'Hide' : 'Show';\u001b[39m\n\u001b[31m  refreshSecretDisplay();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function changeIconSecret() {\u001b[39m\n\u001b[31m  const newSecret = document.getElementById('icon-new-secret').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-secret-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (newSecret.length < 16) { errEl.textContent = 'Minimum 16 characters'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/change-secret', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret(), 'X-New-Secret': newSecret }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Failed');\u001b[39m\n\u001b[31m    setIconSecret(newSecret);\u001b[39m\n\u001b[31m    document.getElementById('icon-new-secret').value = '';\u001b[39m\n\u001b[31m    refreshSecretDisplay();\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = '✓ Secret updated';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mdocument.addEventListener('DOMContentLoaded', () => {\u001b[39m\n\u001b[31m  const fileInput = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  if (fileInput) {\u001b[39m\n\u001b[31m    fileInput.addEventListener('change', function() {\u001b[39m\n\u001b[31m      const wrap = document.getElementById('icon-preview-wrap');\u001b[39m\n\u001b[31m      const img  = document.getElementById('icon-preview-img');\u001b[39m\n\u001b[31m      if (this.files[0]) { img.src = URL.createObjectURL(this.files[0]); wrap.style.display = 'block'; }\u001b[39m\n\u001b[31m      else { wrap.style.display = 'none'; }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function uploadIcon() {\u001b[39m\n\u001b[31m  const pwa   = document.getElementById('icon-pwa-select').value;\u001b[39m\n\u001b[31m  const size  = document.getElementById('icon-size-select').value;\u001b[39m\n\u001b[31m  const input = document.getElementById('icon-file-input');\u001b[39m\n\u001b[31m  const errEl = document.getElementById('icon-upload-err');\u001b[39m\n\u001b[31m  errEl.style.color = 'var(--danger)';\u001b[39m\n\u001b[31m  errEl.textContent = '';\u001b[39m\n\u001b[31m  if (!input.files[0]) { errEl.textContent = 'Choose an image file first'; return; }\u001b[39m\n\u001b[31m  const formData = new FormData();\u001b[39m\n\u001b[31m  formData.append('pwa', pwa);\u001b[39m\n\u001b[31m  formData.append('size', size);\u001b[39m\n\u001b[31m  formData.append('icon', input.files[0]);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch('/icon-admin/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'X-Admin-Secret': getIconSecret() },\u001b[39m\n\u001b[31m      body: formData\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const json = await resp.json();\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(json.error || 'Upload failed');\u001b[39m\n\u001b[31m    errEl.style.color = 'var(--success)';\u001b[39m\n\u001b[31m    errEl.textContent = `✓ Icon updated for ${pwa} (${size}px). Clear browser cache / reinstall to see new icon.`;\u001b[39m\n\u001b[31m    input.value = '';\u001b[39m\n\u001b[31m    document.getElementById('icon-preview-wrap').style.display = 'none';\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadTeamPins() {\u001b[39m\n\u001b[31m  const list = document.getElementById('team-list');\u001b[39m\n\u001b[31m  list.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const emps = await resp.json();\u001b[39m\n\u001b[31m    list.innerHTML = '';\u001b[39m\n\u001b[31m    emps.forEach(emp => {\u001b[39m\n\u001b[31m      const row = document.createElement('div');\u001b[39m\n\u001b[31m      row.className = 'emp-row';\u001b[39m\n\u001b[31m      row.innerHTML = `\u001b[39m\n\u001b[31m        <div style=\\\"min-width:0;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:15px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.name}</div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${emp.role}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-secondary btn-sm\\\" data-id=\\\"${emp.id}\\\" data-name=\\\"${emp.name}\\\">Set PIN</button>`;\u001b[39m\n\u001b[31m      row.querySelector('button').addEventListener('click', () => openSetPin(emp.id, emp.name));\u001b[39m\n\u001b[31m      list.appendChild(row);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    list.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openSetPin(targetId, targetName) {\u001b[39m\n\u001b[31m  setPinTarget = targetId;\u001b[39m\n\u001b[31m  document.getElementById('setpin-target-name').textContent = targetName;\u001b[39m\n\u001b[31m  ['setpin-new','setpin-confirm','setpin-auth'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('setpin-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-setpin').classList.add('show');\u001b[39m\n\u001b[31m  setTimeout(() => document.getElementById('setpin-new').focus(), 50);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function doSetPin() {\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('setpin-new').value.trim();\u001b[39m\n\u001b[31m  const conf    = document.getElementById('setpin-confirm').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('setpin-auth').value.trim();\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('setpin-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  if (!newPin || !conf || !authPin) { errEl.textContent = 'All fields are required'; return; }\u001b[39m\n\u001b[31m  if (newPin !== conf)              { errEl.textContent = 'New PINs do not match'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))     { errEl.textContent = 'PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-setpin-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: CFG.apiHeaders,\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_admin_id:  currentUser.id,\u001b[39m\n\u001b[31m        p_admin_pin: authPin,\u001b[39m\n\u001b[31m        p_target_id: setPinTarget,\u001b[39m\n\u001b[31m        p_new_pin:   newPin,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const err = await resp.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Failed to set PIN';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m    toast('✅ PIN updated for ' + document.getElementById('setpin-target-name').textContent);\u001b[39m\n\u001b[31m  } catch (_) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Set PIN';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ═══════════════════════════════════════════\u001b[39m\n\u001b[31m   JOBS — Campaign + Tour management\u001b[39m\n\u001b[31m   Access: harish, pramod (campaigns + tours)\u001b[39m\n\u001b[31m           rakesh (tours only)\u001b[39m\n\u001b[31m══════════════════════════════════════════════ */\u001b[39m\n\u001b[31mconst INST_RD = { 'Accept-Profile': 'installation' };\u001b[39m\n\u001b[31mconst INST_WR = { 'Content-Type':'application/json','Accept-Profile':'installation','Content-Profile':'installation' };·\u001b[39m\n\u001b[31mlet _currentCampaignId = null;\u001b[39m\n\u001b[31mlet _allEmployees      = [];\u001b[39m\n\u001b[31mlet _editTourId        = null;  // null = create mode, string = edit mode·\u001b[39m\n\u001b[31m// ── Helpers ──────────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _slugify(s) {\u001b[39m\n\u001b[31m  return s.toUpperCase().trim()\u001b[39m\n\u001b[31m    .replace(/[^A-Z0-9]+/g, '_')\u001b[39m\n\u001b[31m    .replace(/^_|_$/g, '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _buildCampaignId(year, brand, client, campaign, phase) {\u001b[39m\n\u001b[31m  const nn = String(phase || 1).padStart(2, '0');\u001b[39m\n\u001b[31m  return `${year}-${_slugify(brand)}-${_slugify(client)}-${_slugify(campaign)}-${nn}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _campStatusBadge(s) {\u001b[39m\n\u001b[31m  const map = { active:'ok', completed:'', on_hold:'warn' };\u001b[39m\n\u001b[31m  const col = { active:'green', completed:'', on_hold:'amber' };\u001b[39m\n\u001b[31m  return `<span class=\\\"kpi-val ${col[s]||''}\\\" style=\\\"font-size:12px;font-weight:700\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _tourStatusBadge(s) {\u001b[39m\n\u001b[31m  const colors = { active:'var(--success)', planned:'var(--info)', completed:'var(--muted)', cancelled:'var(--danger)' };\u001b[39m\n\u001b[31m  return `<span style=\\\"font-size:11px;font-weight:700;padding:2px 8px;border-radius:10px;\u001b[39m\n\u001b[31m    background:var(--card);color:${colors[s]||'var(--muted)'}\\\">${s||'—'}</span>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _canCreateCampaign() { return ['harish','pramod'].includes(currentUser?.id); }\u001b[39m\n\u001b[31mfunction _canCreateTour()     { return ['harish','pramod','rakesh'].includes(currentUser?.id); }·\u001b[39m\n\u001b[31m// ── Jobs screen ───────────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterJobs() {\u001b[39m\n\u001b[31m  showScreen('jobs');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').style.display = _canCreateCampaign() ? '' : 'none';\u001b[39m\n\u001b[31m  await loadJobs();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadJobs() {\u001b[39m\n\u001b[31m  const el = document.getElementById('jobs-body');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?order=created_at.desc`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?select=tour_id,campaign_id,status`, { headers: INST_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const campaigns = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours     = tResp.ok ? await tResp.json() : [];·\u001b[39m\n\u001b[31m    if (!campaigns.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty\\\"><div style=\\\"font-size:32px\\\">📋</div><div>No campaigns yet</div></div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    el.innerHTML = campaigns.map(c => {\u001b[39m\n\u001b[31m      const cTours  = tours.filter(t => t.campaign_id === c.campaign_id);\u001b[39m\n\u001b[31m      const active  = cTours.filter(t => t.status === 'active').length;\u001b[39m\n\u001b[31m      const total   = cTours.length;\u001b[39m\n\u001b[31m      const statusColors = { active:'var(--success)', on_hold:'var(--accent2)', completed:'var(--muted)' };\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card tappable\\\" style=\\\"padding:14px\\\" onclick=\\\"enterCampaign('${c.campaign_id}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;align-items:flex-start;gap:10px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:15px;font-weight:700\\\">${c.brand} — ${c.campaign_name}\u001b[39m\n\u001b[31m                ${c.phase > 1 ? `<span style=\\\"font-size:11px;color:var(--muted)\\\"> Phase ${c.phase}</span>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:2px\\\">${c.client} · ${c.year}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:12px;color:var(--muted);margin-top:4px\\\">\u001b[39m\n\u001b[31m                ${total} tour${total!==1?'s':''} · ${active} active\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;align-items:flex-end;gap:6px\\\">\u001b[39m\n\u001b[31m              <span style=\\\"font-size:11px;font-weight:700;color:${statusColors[c.status]||'var(--muted)'}\\\">\u001b[39m\n\u001b[31m                ${(c.status||'').replace('_',' ')}\u001b[39m\n\u001b[31m              </span>\u001b[39m\n\u001b[31m              <span style=\\\"font-size:18px;color:var(--muted)\\\">›</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-top:8px;word-break:break-all\\\">${c.campaign_id}</div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign detail ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function enterCampaign(campaignId) {\u001b[39m\n\u001b[31m  _currentCampaignId = campaignId;\u001b[39m\n\u001b[31m  showScreen('campaign');\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').style.display = _canCreateTour() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('campaign-hdr-sub').textContent   = '';\u001b[39m\n\u001b[31m  await loadCampaign(campaignId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCampaign(campaignId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('campaign-body');\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"spinner\\\"></div></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [cResp, tResp, empResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(campaignId)}&order=start_date`, { headers: INST_RD }),\u001b[39m\n\u001b[31m      fetch(`${CFG.api}/employee_list`, { headers: CFG.apiHeaders }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const camps = cResp.ok ? await cResp.json() : [];\u001b[39m\n\u001b[31m    const tours = tResp.ok ? await tResp.json() : [];\u001b[39m\n\u001b[31m    _allEmployees = empResp.ok ? await empResp.json() : [];·\u001b[39m\n\u001b[31m    if (!camps.length) { body.innerHTML = '<div class=\\\"empty\\\">Campaign not found</div>'; return; }\u001b[39m\n\u001b[31m    const c = camps[0];·\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-title').textContent = `${c.brand} — ${c.campaign_name}`;\u001b[39m\n\u001b[31m    document.getElementById('campaign-hdr-sub').textContent   = `${c.client} · ${c.year}${c.phase>1?' · Phase '+c.phase:''}`;·\u001b[39m\n\u001b[31m    const empMap = {};\u001b[39m\n\u001b[31m    _allEmployees.forEach(e => empMap[e.id] = e.name);·\u001b[39m\n\u001b[31m    body.innerHTML = `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"padding:12px 14px;display:flex;flex-direction:column;gap:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Campaign ID</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:10px;color:var(--muted);word-break:break-all;text-align:right;max-width:65%\\\">${c.campaign_id}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;justify-content:space-between;align-items:center\\\">\u001b[39m\n\u001b[31m          <span class=\\\"section-title\\\">Client</span>\u001b[39m\n\u001b[31m          <span style=\\\"font-size:13px;font-weight:600\\\">${c.client}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${_canCreateCampaign() ? `\u001b[39m\n\u001b[31m        <div>\u001b[39m\n\u001b[31m          <div class=\\\"section-title\\\" style=\\\"margin-bottom:6px\\\">Campaign Status</div>\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m            ${['active','on_hold','completed'].map(s =>\u001b[39m\n\u001b[31m              `<button class=\\\"status-chip${c.status===s?' sel':''}\\\" onclick=\\\"updateCampaignStatus('${c.campaign_id}','${s}')\\\">\u001b[39m\n\u001b[31m                ${s.replace('_',' ')}\u001b[39m\n\u001b[31m              </button>`).join('')}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>` : ''}\u001b[39m\n\u001b[31m      </div>·\u001b[39m\n\u001b[31m      <div class=\\\"section-title\\\" style=\\\"margin-top:4px\\\">Tours (${tours.length})</div>\u001b[39m\n\u001b[31m      ${tours.length === 0 ? `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\">\u001b[39m\n\u001b[31m        <div>No tours yet</div>\u001b[39m\n\u001b[31m        ${_canCreateTour() ? '<div style=\\\"font-size:12px\\\">Tap ＋ to add the first tour</div>' : ''}\u001b[39m\n\u001b[31m      </div>` :\u001b[39m\n\u001b[31m      tours.map(t => {\u001b[39m\n\u001b[31m        const empNames = (t.assigned_employees || []).map(id => empMap[id] || id).join(', ') || 'No one assigned';\u001b[39m\n\u001b[31m        const dates    = t.start_date ? `${t.start_date} → ${t.end_date||'?'}` : 'Dates not set';\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m          <div class=\\\"card\\\" style=\\\"padding:14px\\\">\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;align-items:flex-start;gap:8px\\\">\u001b[39m\n\u001b[31m              <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m                <div style=\\\"font-size:14px;font-weight:700\\\">${t.title || t.tour_id}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">📅 ${dates}</div>\u001b[39m\n\u001b[31m                <div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">👥 ${empNames}</div>\u001b[39m\n\u001b[31m                ${t.notes ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">📝 ${t.notes}</div>` : ''}\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              ${_tourStatusBadge(t.status)}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            ${_canCreateTour() ? `\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;gap:6px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m              ${['planned','active','completed'].map(s =>\u001b[39m\n\u001b[31m                `<button class=\\\"status-chip${t.status===s?' sel':''}\\\"\u001b[39m\n\u001b[31m                  onclick=\\\"updateTourStatus('${t.tour_id}','${s}')\\\">${s}</button>`).join('')}\u001b[39m\n\u001b[31m              <button class=\\\"status-chip\\\" onclick=\\\"openEditTour('${t.tour_id}')\\\"\u001b[39m\n\u001b[31m                style=\\\"margin-left:auto;background:var(--surface);border-color:#3a3a6e;color:#8888ff\\\">\u001b[39m\n\u001b[31m                ✏️ Edit\u001b[39m\n\u001b[31m              </button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m            <div style=\\\"font-size:10px;color:var(--muted);margin-top:6px\\\">${t.tour_id}</div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m      <div style=\\\"height:20px\\\"></div>`;\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"color:var(--danger)\\\">Failed to load</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateTourStatus(tourId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Tour marked ${status}`);\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function updateCampaignStatus(campaignId, status) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${CFG.api}/campaigns?campaign_id=eq.${encodeURIComponent(campaignId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ status }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    toast(`✅ Campaign marked ${status.replace('_',' ')}`);\u001b[39m\n\u001b[31m    loadCampaign(campaignId);\u001b[39m\n\u001b[31m  } catch(_) { toast('Update failed'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Campaign modal ────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewCampaign() {\u001b[39m\n\u001b[31m  if (!_canCreateCampaign()) { toast('Only Harish or Pramod can create campaigns'); return; }\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('nc-year').value    = new Date().getFullYear();\u001b[39m\n\u001b[31m  document.getElementById('nc-phase').value   = '1';\u001b[39m\n\u001b[31m  document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  document.getElementById('nc-err').textContent     = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-new-campaign').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateCampaignPreview() {\u001b[39m\n\u001b[31m  const year  = document.getElementById('nc-year').value;\u001b[39m\n\u001b[31m  const brand = document.getElementById('nc-brand').value;\u001b[39m\n\u001b[31m  const clt   = document.getElementById('nc-client').value;\u001b[39m\n\u001b[31m  const camp  = document.getElementById('nc-campaign').value;\u001b[39m\n\u001b[31m  const phase = document.getElementById('nc-phase').value;\u001b[39m\n\u001b[31m  if (brand && clt && camp) {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = _buildCampaignId(year, brand, clt, camp, phase);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('nc-preview').textContent = '—';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewCampaign() {\u001b[39m\n\u001b[31m  const year   = Number(document.getElementById('nc-year').value);\u001b[39m\n\u001b[31m  const brand  = document.getElementById('nc-brand').value.trim();\u001b[39m\n\u001b[31m  const client = document.getElementById('nc-client').value.trim();\u001b[39m\n\u001b[31m  const cname  = document.getElementById('nc-campaign').value.trim();\u001b[39m\n\u001b[31m  const phase  = Number(document.getElementById('nc-phase').value) || 1;\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('nc-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const brandEl = document.getElementById('nc-brand');\u001b[39m\n\u001b[31m  const clientEl = document.getElementById('nc-client');\u001b[39m\n\u001b[31m  const cnameEl = document.getElementById('nc-campaign');\u001b[39m\n\u001b[31m  [brandEl, clientEl, cnameEl].forEach(el => el.style.borderColor = '');\u001b[39m\n\u001b[31m  let _missingField = false;\u001b[39m\n\u001b[31m  if (!brand)  { brandEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!client) { clientEl.style.borderColor = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (!cname)  { cnameEl.style.borderColor  = 'var(--danger)'; _missingField = true; }\u001b[39m\n\u001b[31m  if (_missingField) { errEl.textContent = 'Brand, Client and Campaign Name are required'; return; }\u001b[39m\n\u001b[31m  const campaignId = _buildCampaignId(year, brand, client, cname, phase);·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nc-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/campaigns`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m      body: JSON.stringify({ campaign_id:campaignId, year, brand, client, campaign_name:cname, phase, created_by:currentUser.id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || 'Failed to create';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show');\u001b[39m\n\u001b[31m    toast(`✅ Campaign created`);\u001b[39m\n\u001b[31m    loadJobs();\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally { btn.disabled = false; btn.textContent = 'Create'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Tour modal ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction _buildEmpCheckboxes(assigned = []) {\u001b[39m\n\u001b[31m  return _allEmployees.map(e =>\u001b[39m\n\u001b[31m    `<label style=\\\"display:flex;align-items:center;gap:10px;padding:8px 10px;background:var(--card);\u001b[39m\n\u001b[31m      border:1px solid var(--border);border-radius:8px;cursor:pointer\\\">\u001b[39m\n\u001b[31m      <input type=\\\"checkbox\\\" value=\\\"${e.id}\\\" ${assigned.includes(e.id)?'checked':''} style=\\\"width:16px;height:16px;accent-color:var(--accent)\\\">\u001b[39m\n\u001b[31m      <span style=\\\"font-size:14px;font-weight:600\\\">${e.name}</span>\u001b[39m\n\u001b[31m      <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${e.role}</span>\u001b[39m\n\u001b[31m    </label>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openNewTour() {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = null;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '🗺️ New Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Create Tour';\u001b[39m\n\u001b[31m  document.getElementById('nt-title').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-start').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-end').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-notes').value = '';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?campaign_id=eq.${encodeURIComponent(_currentCampaignId)}&select=tour_id`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const existing = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    const nn = String(existing.length + 1).padStart(2, '0');\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T${nn}`;\u001b[39m\n\u001b[31m  } catch(_) {\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = `${_currentCampaignId}-T01`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (_allEmployees.length)\u001b[39m\n\u001b[31m    document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes();·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEditTour(tourId) {\u001b[39m\n\u001b[31m  if (!_canCreateTour()) { toast('Not authorised'); return; }\u001b[39m\n\u001b[31m  _editTourId = tourId;\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').querySelector('.modal-title').textContent = '✏️ Edit Tour';\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').textContent = 'Save Changes';\u001b[39m\n\u001b[31m  document.getElementById('nt-err').textContent = '';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(tourId)}`, { headers: INST_RD });\u001b[39m\n\u001b[31m    const tours = resp.ok ? await resp.json() : [];\u001b[39m\n\u001b[31m    if (!tours.length) { toast('Tour not found'); return; }\u001b[39m\n\u001b[31m    const t = tours[0];\u001b[39m\n\u001b[31m    document.getElementById('nt-title').value = t.title || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-start').value = t.start_date || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-end').value   = t.end_date   || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-notes').value = t.notes      || '';\u001b[39m\n\u001b[31m    document.getElementById('nt-id-preview').textContent = tourId;\u001b[39m\n\u001b[31m    if (_allEmployees.length)\u001b[39m\n\u001b[31m      document.getElementById('nt-employees').innerHTML = _buildEmpCheckboxes(t.assigned_employees || []);\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to load tour'); return; }·\u001b[39m\n\u001b[31m  document.getElementById('modal-new-tour').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewTour() {\u001b[39m\n\u001b[31m  const title = document.getElementById('nt-title').value.trim();\u001b[39m\n\u001b[31m  const start = document.getElementById('nt-start').value || null;\u001b[39m\n\u001b[31m  const end   = document.getElementById('nt-end').value   || null;\u001b[39m\n\u001b[31m  const notes = document.getElementById('nt-notes').value.trim() || null;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('nt-err');\u001b[39m\n\u001b[31m  errEl.textContent = '';·\u001b[39m\n\u001b[31m  const checked = [...document.querySelectorAll('#nt-employees input:checked')].map(cb => cb.value);\u001b[39m\n\u001b[31m  if (!checked.length) { errEl.textContent = 'Assign at least one employee'; return; }·\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-nt-save');\u001b[39m\n\u001b[31m  const wasEdit = !!_editTourId;\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = wasEdit ? 'Saving…' : 'Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editTourId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours?tour_id=eq.${encodeURIComponent(_editTourId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      const tourId = document.getElementById('nt-id-preview').textContent;\u001b[39m\n\u001b[31m      resp = await fetch(`${CFG.api}/tours`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: INST_WR,\u001b[39m\n\u001b[31m        body: JSON.stringify({\u001b[39m\n\u001b[31m          tour_id: tourId, campaign_id: _currentCampaignId,\u001b[39m\n\u001b[31m          title: title || null, assigned_employees: checked,\u001b[39m\n\u001b[31m          start_date: start, end_date: end, notes, status: 'planned',\u001b[39m\n\u001b[31m          created_by: currentUser.id,\u001b[39m\n\u001b[31m        }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      const e = await resp.json().catch(()=>({}));\u001b[39m\n\u001b[31m      errEl.textContent = e.message || (_editTourId ? 'Failed to save' : 'Failed to create');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show');\u001b[39m\n\u001b[31m    toast(_editTourId ? '✅ Tour updated' : '✅ Tour created');\u001b[39m\n\u001b[31m    _editTourId = null;\u001b[39m\n\u001b[31m    loadCampaign(_currentCampaignId);\u001b[39m\n\u001b[31m  } catch(e) { errEl.textContent = 'Network error'; }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = wasEdit ? 'Save Changes' : 'Create Tour';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── EVENT WIRING ── */\u001b[39m\n\u001b[31mfunction wireEvents() {\u001b[39m\n\u001b[31m  // Jobs screens\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-back').addEventListener('click',     () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-jobs-refresh').addEventListener('click',  loadJobs);\u001b[39m\n\u001b[31m  document.getElementById('btn-new-campaign').addEventListener('click',  openNewCampaign);\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-back').addEventListener('click', () => showScreen('jobs'));\u001b[39m\n\u001b[31m  document.getElementById('btn-campaign-refresh').addEventListener('click', () => loadCampaign(_currentCampaignId));\u001b[39m\n\u001b[31m  document.getElementById('btn-new-tour').addEventListener('click',      openNewTour);·\u001b[39m\n\u001b[31m  // New Campaign modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-campaign').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nc-save').addEventListener('click', saveNewCampaign);\u001b[39m\n\u001b[31m  ['nc-brand','nc-client','nc-campaign','nc-year','nc-phase'].forEach(id =>\u001b[39m\n\u001b[31m    document.getElementById(id).addEventListener('input', updateCampaignPreview));·\u001b[39m\n\u001b[31m  // New Tour modal\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-new-tour').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-nt-save').addEventListener('click', saveNewTour);·\u001b[39m\n\u001b[31m  document.getElementById('btn-team-back').addEventListener('click', () => showScreen('home'));\u001b[39m\n\u001b[31m  document.getElementById('btn-team-refresh').addEventListener('click', loadTeamPins);\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-cancel').addEventListener('click', () => {\u001b[39m\n\u001b[31m    document.getElementById('modal-setpin').classList.remove('show');\u001b[39m\n\u001b[31m    setPinTarget = null;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('btn-setpin-save').addEventListener('click', doSetPin);·\u001b[39m\n\u001b[31m  // Set PIN — progressive field reveal\u001b[39m\n\u001b[31m  document.getElementById('setpin-new').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const val = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const showConf = val.length === 4;\u001b[39m\n\u001b[31m    document.getElementById('setpin-confirm-group').style.display = showConf ? '' : 'none';\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = 'none';\u001b[39m\n\u001b[31m    if (!showConf) { document.getElementById('setpin-confirm').value = ''; document.getElementById('setpin-auth').value = ''; }\u001b[39m\n\u001b[31m    if (showConf) setTimeout(() => document.getElementById('setpin-confirm').focus(), 20);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('setpin-confirm').addEventListener('input', () => {\u001b[39m\n\u001b[31m    const newVal = document.getElementById('setpin-new').value;\u001b[39m\n\u001b[31m    const conf   = document.getElementById('setpin-confirm').value;\u001b[39m\n\u001b[31m    const showAuth = conf.length === 4 && conf === newVal;\u001b[39m\n\u001b[31m    document.getElementById('setpin-auth-group').style.display = showAuth ? '' : 'none';\u001b[39m\n\u001b[31m    if (!showAuth) document.getElementById('setpin-auth').value = '';\u001b[39m\n\u001b[31m    if (showAuth) setTimeout(() => document.getElementById('setpin-auth').focus(), 20);\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // Hub AI config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-ai').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-ai-save').addEventListener('click',   saveHubAiConfig);\u001b[39m\n\u001b[31m  document.getElementById('btn-check-keys').addEventListener('click',    checkApiKeyStatus);·\u001b[39m\n\u001b[31m  // Hub QA config modal\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-cancel').addEventListener('click', () =>\u001b[39m\n\u001b[31m    document.getElementById('modal-hub-qa').classList.remove('show'));\u001b[39m\n\u001b[31m  document.getElementById('btn-hub-qa-save').addEventListener('click', saveHubQaConfig);·\u001b[39m\n\u001b[31m  // Share picker\u001b[39m\n\u001b[31m  document.getElementById('share-to-expense').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/expense/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-recce').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/recce/?share=1';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('share-to-installation').addEventListener('click', () => {\u001b[39m\n\u001b[31m    window.location.href = '/installation/current/?share=1';\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-04: employee dropdown change resets PIN buffer and lockout\u001b[39m\n\u001b[31m  document.getElementById('login-name').addEventListener('change', () => {\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m    document.getElementById('login-err').textContent = '';\u001b[39m\n\u001b[31m    _loginAttempts = 0; _lockoutUntil = 0;\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-07: ESC key closes any open modal\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (e.key === 'Escape') {\u001b[39m\n\u001b[31m      document.querySelectorAll('.modal-overlay.show').forEach(m => m.classList.remove('show'));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-11: keyboard digit/backspace input on login screen\u001b[39m\n\u001b[31m  document.addEventListener('keydown', e => {\u001b[39m\n\u001b[31m    if (!document.getElementById('screen-login').classList.contains('active')) return;\u001b[39m\n\u001b[31m    if (e.key >= '0' && e.key <= '9') { handlePinKey(e.key); }\u001b[39m\n\u001b[31m    else if (e.key === 'Backspace') { e.preventDefault(); handlePinKey('B'); }\u001b[39m\n\u001b[31m  });·\u001b[39m\n\u001b[31m  // B-02/B-08: re-check session TTL when user returns to the tab\u001b[39m\n\u001b[31m  document.addEventListener('visibilitychange', () => {\u001b[39m\n\u001b[31m    if (document.visibilityState === 'visible' && currentUser) {\u001b[39m\n\u001b[31m      if (!loadSession()) doLogout();\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── BOOT ── */\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  wireEvents();·\u001b[39m\n\u001b[31m  if (location.search.includes('share=1')) {\u001b[39m\n\u001b[31m    pendingShare = true;\u001b[39m\n\u001b[31m    history.replaceState({}, '', '/hub/');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Always populate the login dropdown (needed if user logs out later)\u001b[39m\n\u001b[31m  initLogin(); // fire-and-forget — does not block home screen path·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess) {\u001b[39m\n\u001b[31m    currentUser = { id: sess.empId, name: sess.name, role: sess.role };\u001b[39m\n\u001b[31m    if (pendingShare) { showScreen('share'); return; }\u001b[39m\n\u001b[31m    await loadHubAccess();\u001b[39m\n\u001b[31m    await buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  setTimeout(() => showScreen('login'), 1500);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── SERVICE WORKER: install + update ── */\u001b[39m\n\u001b[31mif ('serviceWorker' in navigator) {\u001b[39m\n\u001b[31m  navigator.serviceWorker.register('/hub/sw.js').then(reg => {·\u001b[39m\n\u001b[31m    // Install banner: show when browser signals app is installable\u001b[39m\n\u001b[31m    let deferredInstall = null;\u001b[39m\n\u001b[31m    window.addEventListener('beforeinstallprompt', e => {\u001b[39m\n\u001b[31m      e.preventDefault();\u001b[39m\n\u001b[31m      deferredInstall = e;\u001b[39m\n\u001b[31m      if (!window.matchMedia('(display-mode: standalone)').matches) {\u001b[39m\n\u001b[31m        const delay = document.getElementById('update-banner').classList.contains('show') ? 3500 : 0;\u001b[39m\n\u001b[31m        setTimeout(() => document.getElementById('install-banner').classList.add('show'), delay);\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    window.addEventListener('appinstalled', () => {\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install').addEventListener('click', async () => {\u001b[39m\n\u001b[31m      if (!deferredInstall) return;\u001b[39m\n\u001b[31m      deferredInstall.prompt();\u001b[39m\n\u001b[31m      await deferredInstall.userChoice;\u001b[39m\n\u001b[31m      deferredInstall = null;\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show');\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-install-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('install-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m    // Update banner: show when a new SW version is waiting to activate\u001b[39m\n\u001b[31m    function showUpdateBanner() {\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.add('show');\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (reg.waiting) showUpdateBanner();·\u001b[39m\n\u001b[31m    reg.addEventListener('updatefound', () => {\u001b[39m\n\u001b[31m      const newSW = reg.installing;\u001b[39m\n\u001b[31m      newSW.addEventListener('statechange', () => {\u001b[39m\n\u001b[31m        if (newSW.state === 'installed' && navigator.serviceWorker.controller) {\u001b[39m\n\u001b[31m          showUpdateBanner();\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    document.getElementById('btn-update').addEventListener('click', () => {\u001b[39m\n\u001b[31m      if (reg.waiting) {\u001b[39m\n\u001b[31m        reg.waiting.postMessage({ type: 'SKIP_WAITING' });\u001b[39m\n\u001b[31m        navigator.serviceWorker.addEventListener('controllerchange', () => {\u001b[39m\n\u001b[31m          window.location.reload();\u001b[39m\n\u001b[31m        }, { once: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    document.getElementById('btn-update-dismiss').addEventListener('click', () =>\u001b[39m\n\u001b[31m      document.getElementById('update-banner').classList.remove('show'));·\u001b[39m\n\u001b[31m  }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mboot();\u001b[39m\n\u001b[31m</script>··\u001b[39m\n\u001b[31m</body></html>\"\u001b[39m\n\n  329 |     await page.goto(BASE_URL);\n  330 |     const src = await page.content();\n> 331 |     expect(src).toContain('recce-v10');\n      |                 ^\n  332 |     expect(src).not.toContain('recce-v9');\n  333 |   });\n  334 | });\n    at /var/www/360lm/tests/recce.spec.js:331:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-05-20T06:23:55.572Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-5e896-tos-SW-version-is-recce-v10-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-5e896-tos-SW-version-is-recce-v10-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 331
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-56341d142ec8d9325545",
              "file": "recce.spec.js",
              "line": 328,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — v9 Sync Text (B5)",
          "file": "recce.spec.js",
          "line": 337,
          "column": 6,
          "specs": [
            {
              "title": "B5: last-sync-txt shows \"Loaded HH:MM\" after admin fetch",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3467,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:22:49.285Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-5d8091a756965e8306bc",
              "file": "recce.spec.js",
              "line": 342,
              "column": 3
            },
            {
              "title": "B5: last-sync-txt shows \"Loaded HH:MM\" after admin fetch",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 5,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4026,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-20T06:23:58.261Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-bcbabcaa47cb3989fcce",
              "file": "recce.spec.js",
              "line": 342,
              "column": 3
            }
          ]
        }
      ]
    }
  ],
  "errors": [],
  "stats": {
    "startTime": "2026-05-20T06:21:44.993Z",
    "duration": 137846.166,
    "expected": 50,
    "skipped": 0,
    "unexpected": 2,
    "flaky": 0
  }
}
